diff --git a/source/collada/XMLFix.cpp b/source/collada/XMLFix.cpp index 2fd4430fb8..43c6f77d04 100644 --- a/source/collada/XMLFix.cpp +++ b/source/collada/XMLFix.cpp @@ -1,172 +1,172 @@ -#include "precompiled.h" - -#include "XMLFix.h" - -#include "CommonConvert.h" - -#include "FUtils/FUXmlParser.h" - -/* - -Things that are fixed here: - ----- - -3ds Max "file://" image URLs - -Identifier: /COLLADA/asset/contributor/authoring_tool = "FBX COLLADA exporter" - -Problem: /COLLADA/library_images/image/init_from = "file://" which crashes some versions of FCollada - -Fix: Delete the whole library_images subtree, since we never use it anyway. -Then delete library_effects and library_materials too, to avoid broken references. - ----- - -3ds Max broken material references - -Identifier: /COLLADA/asset/contributor/authoring_tool = "FBX COLLADA exporter" - -Problem: /COLLADA/library_visual_scenes/.../instance_material/@target sometimes -refers to non-existent material IDs. - -Fix: Delete the whole bind_material subtree, since we never use it anyway. - ----- - -*/ - -static xmlNode* findChildElement(xmlNode* node, const char* name) -{ - for (xmlNode* child = node->children; child; child = child->next) - { - if (child->type == XML_ELEMENT_NODE && strcmp((const char*)child->name, name) == 0) - return child; - } - return NULL; -} - -static bool applyFBXFixesNode(xmlNode* node) -{ - bool changed = false; - for (xmlNode* child = node->children; child; child = child->next) - { - if (child->type == XML_ELEMENT_NODE) - { - if (strcmp((const char*)child->name, "node") == 0) - { - if (applyFBXFixesNode(child)) - changed = true; - } - else if (strcmp((const char*)child->name, "instance_geometry") == 0) - { - xmlNode* bind_material = findChildElement(child, "bind_material"); - if (! bind_material) continue; - Log(LOG_INFO, "Found a bind_material to delete"); - xmlUnlinkNode(bind_material); - xmlFreeNode(bind_material); - - changed = true; - } - } - } - return changed; -} - -static bool applyFBXFixes(xmlNode* root) -{ - Log(LOG_INFO, "Applying fixes for 3ds Max exporter"); - - bool changed = false; - - xmlNode* library_images = findChildElement(root, "library_images"); - if (library_images) - { - Log(LOG_INFO, "Found library_images to delete"); - xmlUnlinkNode(library_images); - xmlFreeNode(library_images); - changed = true; - } - - xmlNode* library_materials = findChildElement(root, "library_materials"); - if (library_materials) - { - Log(LOG_INFO, "Found library_materials to delete"); - xmlUnlinkNode(library_materials); - xmlFreeNode(library_materials); - changed = true; - } - - xmlNode* library_effects = findChildElement(root, "library_effects"); - if (library_effects) - { - Log(LOG_INFO, "Found library_effects to delete"); - xmlUnlinkNode(library_effects); - xmlFreeNode(library_effects); - changed = true; - } - - xmlNode* library_visual_scenes = findChildElement(root, "library_visual_scenes"); - if (library_visual_scenes) // (Assume there's only one of these) - { - xmlNode* visual_scene = findChildElement(library_visual_scenes, "visual_scene"); - if (visual_scene) // (Assume there's only one of these) - { - for (xmlNode* child = visual_scene->children; child; child = child->next) - { - if (child->type == XML_ELEMENT_NODE && strcmp((const char*)child->name, "node") == 0) - if (applyFBXFixesNode(child)) - changed = true; - } - } - } - - return changed; -} - -static bool processDocument(xmlNode* root) -{ - xmlNode* asset = findChildElement(root, "asset"); - if (! asset) return false; - xmlNode* contributor = findChildElement(asset, "contributor"); - if (! contributor) return false; - xmlNode* authoring_tool = findChildElement(contributor, "authoring_tool"); - if (! authoring_tool) return false; - - xmlNode* authoring_tool_text = authoring_tool->children; - if (! authoring_tool_text) return false; - if (authoring_tool_text->type != XML_TEXT_NODE) return false; - xmlChar* toolname = authoring_tool_text->content; - Log(LOG_INFO, "Authoring tool: %s", toolname); - if (strcmp((const char*)toolname, "FBX COLLADA exporter") == 0) - return applyFBXFixes(root); - else - return false; -} - -void FixBrokenXML(const char* text, const char** out, size_t* outSize) -{ - Log(LOG_INFO, "Running FixBrokenXML"); - - size_t textSize = strlen(text); - xmlDocPtr doc = xmlParseMemory(text, textSize); - - xmlNode* root = xmlDocGetRootElement(doc); - if (root && processDocument(root)) - { - // Reserialising the document, then parsing it again inside FCollada, is a bit ugly; - // but it's the only way I can see to make it work through FCollada's public API - xmlChar* mem = NULL; - int size = -1; - xmlDocDumpFormatMemory(doc, &mem, &size, 0); - *out = (const char*)mem; - *outSize = size; - } - else - { - *out = text; - *outSize = textSize; - } - - xmlFreeDoc(doc); -} +#include "precompiled.h" + +#include "XMLFix.h" + +#include "CommonConvert.h" + +#include "FUtils/FUXmlParser.h" + +/* + +Things that are fixed here: + +---- + +3ds Max "file://" image URLs + +Identifier: /COLLADA/asset/contributor/authoring_tool = "FBX COLLADA exporter" + +Problem: /COLLADA/library_images/image/init_from = "file://" which crashes some versions of FCollada + +Fix: Delete the whole library_images subtree, since we never use it anyway. +Then delete library_effects and library_materials too, to avoid broken references. + +---- + +3ds Max broken material references + +Identifier: /COLLADA/asset/contributor/authoring_tool = "FBX COLLADA exporter" + +Problem: /COLLADA/library_visual_scenes/.../instance_material/@target sometimes +refers to non-existent material IDs. + +Fix: Delete the whole bind_material subtree, since we never use it anyway. + +---- + +*/ + +static xmlNode* findChildElement(xmlNode* node, const char* name) +{ + for (xmlNode* child = node->children; child; child = child->next) + { + if (child->type == XML_ELEMENT_NODE && strcmp((const char*)child->name, name) == 0) + return child; + } + return NULL; +} + +static bool applyFBXFixesNode(xmlNode* node) +{ + bool changed = false; + for (xmlNode* child = node->children; child; child = child->next) + { + if (child->type == XML_ELEMENT_NODE) + { + if (strcmp((const char*)child->name, "node") == 0) + { + if (applyFBXFixesNode(child)) + changed = true; + } + else if (strcmp((const char*)child->name, "instance_geometry") == 0) + { + xmlNode* bind_material = findChildElement(child, "bind_material"); + if (! bind_material) continue; + Log(LOG_INFO, "Found a bind_material to delete"); + xmlUnlinkNode(bind_material); + xmlFreeNode(bind_material); + + changed = true; + } + } + } + return changed; +} + +static bool applyFBXFixes(xmlNode* root) +{ + Log(LOG_INFO, "Applying fixes for 3ds Max exporter"); + + bool changed = false; + + xmlNode* library_images = findChildElement(root, "library_images"); + if (library_images) + { + Log(LOG_INFO, "Found library_images to delete"); + xmlUnlinkNode(library_images); + xmlFreeNode(library_images); + changed = true; + } + + xmlNode* library_materials = findChildElement(root, "library_materials"); + if (library_materials) + { + Log(LOG_INFO, "Found library_materials to delete"); + xmlUnlinkNode(library_materials); + xmlFreeNode(library_materials); + changed = true; + } + + xmlNode* library_effects = findChildElement(root, "library_effects"); + if (library_effects) + { + Log(LOG_INFO, "Found library_effects to delete"); + xmlUnlinkNode(library_effects); + xmlFreeNode(library_effects); + changed = true; + } + + xmlNode* library_visual_scenes = findChildElement(root, "library_visual_scenes"); + if (library_visual_scenes) // (Assume there's only one of these) + { + xmlNode* visual_scene = findChildElement(library_visual_scenes, "visual_scene"); + if (visual_scene) // (Assume there's only one of these) + { + for (xmlNode* child = visual_scene->children; child; child = child->next) + { + if (child->type == XML_ELEMENT_NODE && strcmp((const char*)child->name, "node") == 0) + if (applyFBXFixesNode(child)) + changed = true; + } + } + } + + return changed; +} + +static bool processDocument(xmlNode* root) +{ + xmlNode* asset = findChildElement(root, "asset"); + if (! asset) return false; + xmlNode* contributor = findChildElement(asset, "contributor"); + if (! contributor) return false; + xmlNode* authoring_tool = findChildElement(contributor, "authoring_tool"); + if (! authoring_tool) return false; + + xmlNode* authoring_tool_text = authoring_tool->children; + if (! authoring_tool_text) return false; + if (authoring_tool_text->type != XML_TEXT_NODE) return false; + xmlChar* toolname = authoring_tool_text->content; + Log(LOG_INFO, "Authoring tool: %s", toolname); + if (strcmp((const char*)toolname, "FBX COLLADA exporter") == 0) + return applyFBXFixes(root); + else + return false; +} + +void FixBrokenXML(const char* text, const char** out, size_t* outSize) +{ + Log(LOG_INFO, "Running FixBrokenXML"); + + size_t textSize = strlen(text); + xmlDocPtr doc = xmlParseMemory(text, textSize); + + xmlNode* root = xmlDocGetRootElement(doc); + if (root && processDocument(root)) + { + // Reserialising the document, then parsing it again inside FCollada, is a bit ugly; + // but it's the only way I can see to make it work through FCollada's public API + xmlChar* mem = NULL; + int size = -1; + xmlDocDumpFormatMemory(doc, &mem, &size, 0); + *out = (const char*)mem; + *outSize = size; + } + else + { + *out = text; + *outSize = textSize; + } + + xmlFreeDoc(doc); +} diff --git a/source/collada/XMLFix.h b/source/collada/XMLFix.h index b4e8a38b69..17e8e92082 100644 --- a/source/collada/XMLFix.h +++ b/source/collada/XMLFix.h @@ -1,12 +1,12 @@ -#ifndef XMLFIX_INCLUDED -#define XMLFIX_INCLUDED - -/** - * Fixes some errors in COLLADA XML files that would otherwise prevent - * FCollada from loading them successfully. - * 'out' is either a new XML document, which must be freed with xmlFree; - * otherwise it is equal to 'text' if no changes were made. - */ -void FixBrokenXML(const char* text, const char** out, size_t* outSize); - -#endif // XMLFIX_INCLUDED +#ifndef XMLFIX_INCLUDED +#define XMLFIX_INCLUDED + +/** + * Fixes some errors in COLLADA XML files that would otherwise prevent + * FCollada from loading them successfully. + * 'out' is either a new XML document, which must be freed with xmlFree; + * otherwise it is equal to 'text' if no changes were made. + */ +void FixBrokenXML(const char* text, const char** out, size_t* outSize); + +#endif // XMLFIX_INCLUDED diff --git a/source/maths/Noise.h b/source/maths/Noise.h index 01a3bdae0d..87d9345b08 100644 --- a/source/maths/Noise.h +++ b/source/maths/Noise.h @@ -1,53 +1,53 @@ -/** - * ========================================================================= - * File : Noise.h - * Project : 0 A.D. - * Description : 2D and 3D seamless Perlin noise - * ========================================================================= - */ - -// Based on http://www.cs.cmu.edu/~mzucker/code/perlin-noise-math-faq.html -// and http://mrl.nyu.edu/~perlin/paper445.pdf. -// Not optimized for speed yet. - -#ifndef INCLUDED_NOISE -#define INCLUDED_NOISE - -#include "Vector2D.h" -#include "Vector3D.h" -#include "MathUtil.h" - -class Noise2D -{ - /// Frequency in X and Y - int freq; - - /// freq*freq random gradient vectors in the unit cube - CVector2D_Maths** grads; -public: - Noise2D(int freq); - ~Noise2D(); - - /// Evaluate the noise function at a given point - float operator() (float x, float y); -}; - -class Noise3D -{ - /// Frequency in X and Y - int freq; - - /// Frequency in Z (vertical frequency) - int vfreq; - - /// freq*freq*vfreq random gradient vectors in the unit cube - CVector3D*** grads; -public: - Noise3D(int freq, int vfreq); - ~Noise3D(); - - /// Evaluate the noise function at a given point - float operator() (float x, float y, float z); -}; - -#endif +/** + * ========================================================================= + * File : Noise.h + * Project : 0 A.D. + * Description : 2D and 3D seamless Perlin noise + * ========================================================================= + */ + +// Based on http://www.cs.cmu.edu/~mzucker/code/perlin-noise-math-faq.html +// and http://mrl.nyu.edu/~perlin/paper445.pdf. +// Not optimized for speed yet. + +#ifndef INCLUDED_NOISE +#define INCLUDED_NOISE + +#include "Vector2D.h" +#include "Vector3D.h" +#include "MathUtil.h" + +class Noise2D +{ + /// Frequency in X and Y + int freq; + + /// freq*freq random gradient vectors in the unit cube + CVector2D_Maths** grads; +public: + Noise2D(int freq); + ~Noise2D(); + + /// Evaluate the noise function at a given point + float operator() (float x, float y); +}; + +class Noise3D +{ + /// Frequency in X and Y + int freq; + + /// Frequency in Z (vertical frequency) + int vfreq; + + /// freq*freq*vfreq random gradient vectors in the unit cube + CVector3D*** grads; +public: + Noise3D(int freq, int vfreq); + ~Noise3D(); + + /// Evaluate the noise function at a given point + float operator() (float x, float y, float z); +}; + +#endif diff --git a/source/maths/tests/test_Matrix3d.h b/source/maths/tests/test_Matrix3d.h index 4173910042..3c200b9cdc 100644 --- a/source/maths/tests/test_Matrix3d.h +++ b/source/maths/tests/test_Matrix3d.h @@ -1,68 +1,68 @@ -#include "lib/self_test.h" - -#include -#include -#include "maths/Matrix3D.h" -#include "maths/Quaternion.h" - -class TestMatrix : public CxxTest::TestSuite -{ -public: - void test_inverse() - { - CMatrix3D m; - srand(0); - for (int i = 0; i < 4; ++i) - { - for (int j = 0; j < 16; ++j) - { - m._data[j] = -1.0f + 2.0f*(rand()/(float)RAND_MAX); - } - CMatrix3D n; - m.GetInverse(n); - m *= n; - // verify identity has 1s on diagonal and 0 otherwise - for (int x = 0; x < 4; ++x) - { - for (int y = 0; y < 4; ++y) - { - const float expected = (x==y)? 1.0f : 0.0f; - TS_ASSERT_DELTA(m(x,y), expected, 0.0001f); - } - } - } - } - - void test_quats() - { - srand(0); - for (int i = 0; i < 4; ++i) - { - CQuaternion q; - q.FromEulerAngles( - -6.28f + 12.56f*(rand()/(float)RAND_MAX), - -6.28f + 12.56f*(rand()/(float)RAND_MAX), - -6.28f + 12.56f*(rand()/(float)RAND_MAX) - ); - CMatrix3D m; - q.ToMatrix(m); - CQuaternion q2 = m.GetRotation(); - - // Quaternions (x,y,z,w) and (-x,-y,-z,-w) are equivalent when - // interpreted as rotations, so it doesn't matter which we get - const bool ok_oneway = - feq(q2.m_W, q.m_W) && - feq(q2.m_V.X, q.m_V.X) && - feq(q2.m_V.Y, q.m_V.Y) && - feq(q2.m_V.Z, q.m_V.Z); - const bool ok_otherway = - feq(q2.m_W, -q.m_W) && - feq(q2.m_V.X, -q.m_V.X) && - feq(q2.m_V.Y, -q.m_V.Y) && - feq(q2.m_V.Z, -q.m_V.Z); - TS_ASSERT(ok_oneway ^ ok_otherway); - } - } -}; - - +#include "lib/self_test.h" + +#include +#include +#include "maths/Matrix3D.h" +#include "maths/Quaternion.h" + +class TestMatrix : public CxxTest::TestSuite +{ +public: + void test_inverse() + { + CMatrix3D m; + srand(0); + for (int i = 0; i < 4; ++i) + { + for (int j = 0; j < 16; ++j) + { + m._data[j] = -1.0f + 2.0f*(rand()/(float)RAND_MAX); + } + CMatrix3D n; + m.GetInverse(n); + m *= n; + // verify identity has 1s on diagonal and 0 otherwise + for (int x = 0; x < 4; ++x) + { + for (int y = 0; y < 4; ++y) + { + const float expected = (x==y)? 1.0f : 0.0f; + TS_ASSERT_DELTA(m(x,y), expected, 0.0001f); + } + } + } + } + + void test_quats() + { + srand(0); + for (int i = 0; i < 4; ++i) + { + CQuaternion q; + q.FromEulerAngles( + -6.28f + 12.56f*(rand()/(float)RAND_MAX), + -6.28f + 12.56f*(rand()/(float)RAND_MAX), + -6.28f + 12.56f*(rand()/(float)RAND_MAX) + ); + CMatrix3D m; + q.ToMatrix(m); + CQuaternion q2 = m.GetRotation(); + + // Quaternions (x,y,z,w) and (-x,-y,-z,-w) are equivalent when + // interpreted as rotations, so it doesn't matter which we get + const bool ok_oneway = + feq(q2.m_W, q.m_W) && + feq(q2.m_V.X, q.m_V.X) && + feq(q2.m_V.Y, q.m_V.Y) && + feq(q2.m_V.Z, q.m_V.Z); + const bool ok_otherway = + feq(q2.m_W, -q.m_W) && + feq(q2.m_V.X, -q.m_V.X) && + feq(q2.m_V.Y, -q.m_V.Y) && + feq(q2.m_V.Z, -q.m_V.Z); + TS_ASSERT(ok_oneway ^ ok_otherway); + } + } +}; + + diff --git a/source/network/NetClient.cpp b/source/network/NetClient.cpp index 10bd741f11..a081c8b866 100644 --- a/source/network/NetClient.cpp +++ b/source/network/NetClient.cpp @@ -1,682 +1,682 @@ -/** - *----------------------------------------------------------------------------- - * 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 "scripting/DOMEvent.h" -#include "scripting/JSConversions.h" -#include "scripting/ScriptableObject.h" -#include "ps/CStr.h" -#include "ps/CLogger.h" -#include "ps/CConsole.h" -#include "ps/Game.h" -#include "ps/Globals.h" -#include "ps/GameAttributes.h" -#include "simulation/Simulation.h" - -// DECLARATIONS - -#define LOG_CAT_NET "net" - -CNetClient *g_NetClient=NULL; -extern int fps; - -//----------------------------------------------------------------------------- -// Name: CServerPlayer() -// Desc: Constructor -//----------------------------------------------------------------------------- -CServerPlayer::CServerPlayer( uint sessionID, const CStr& nickname ) -{ - m_SessionID = sessionID; - m_Nickname = nickname; -} - -//----------------------------------------------------------------------------- -// Name: ~CServerPlayer() -// Desc: Destructor -//----------------------------------------------------------------------------- -CServerPlayer::~CServerPlayer( void ) -{ -} - -//----------------------------------------------------------------------------- -// Name: ScriptingInit() -// Desc: -//----------------------------------------------------------------------------- -void CServerPlayer::ScriptingInit( void ) -{ - AddProperty(L"id", &CServerPlayer::m_SessionID, true); - AddProperty(L"name", &CServerPlayer::m_Nickname, true); - - CJSObject::ScriptingInit( "NetClient_ServerSession" ); -} - -//----------------------------------------------------------------------------- -// Name: CNetClient() -// Desc: Constructor -//----------------------------------------------------------------------------- -CNetClient::CNetClient( CGame* pGame, CGameAttributes* pGameAttribs ) -: m_JsPlayers( &m_Players ) -{ - m_pLocalPlayerSlot = NULL; - m_pGame = pGame; - m_pGameAttributes = pGameAttribs; - m_TurnPending = false; - - //ONCE( ScriptingInit(); ); - - m_pGame->GetSimulation()->SetTurnManager(this); - - g_ScriptingHost.SetGlobal("g_NetClient", OBJECT_TO_JSVAL(GetScript())); -} - -//----------------------------------------------------------------------------- -// 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; - } - - m_Players.clear(); - - g_ScriptingHost.SetGlobal("g_NetClient", JSVAL_NULL); -} - -//----------------------------------------------------------------------------- -// Name: ScriptingInit() -// Desc: -//----------------------------------------------------------------------------- -void CNetClient::ScriptingInit() -{ - AddMethod("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(L"playerName", &CNetClient::m_Nickname); - //AddProperty(L"sessionId", &CNetClient::m_SessionID); - - AddProperty(L"sessions", &CNetClient::m_JsPlayers); - - CJSMap< PlayerMap >::ScriptingInit("NetClient_SessionMap"); - CJSObject::ScriptingInit("NetClient"); - //CGameAttributes::ScriptingInit(); - //CServerPlayer::ScriptingInit(); -} - -//----------------------------------------------------------------------------- -// 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 = new FsmActionCtx; - if ( !pContext ) return false; - - 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_GOTO, NCS_INGAME, (void*)&OnInGame, pContext ); - pSession->AddTransition( NCS_INGAME, ( uint )NMT_PATROL, NCS_INGAME, (void*)&OnInGame, pContext ); - pSession->AddTransition( NCS_INGAME, ( uint )NMT_ADD_WAYPOINT, NCS_INGAME, (void*)&OnInGame, pContext ); - pSession->AddTransition( NCS_INGAME, ( uint )NMT_CONTACT_ACTION, NCS_INGAME, (void*)&OnInGame, pContext ); - pSession->AddTransition( NCS_INGAME, ( uint )NMT_PRODUCE, NCS_INGAME, (void*)&OnInGame, pContext ); - pSession->AddTransition( NCS_INGAME, ( uint )NMT_PLACE_OBJECT, NCS_INGAME, (void*)&OnInGame, pContext ); - pSession->AddTransition( NCS_INGAME, ( uint )NMT_RUN, NCS_INGAME, (void*)&OnInGame, pContext ); - pSession->AddTransition( NCS_INGAME, ( uint )NMT_SET_RALLY_POINT, NCS_INGAME, (void*)&OnInGame, pContext ); - pSession->AddTransition( NCS_INGAME, ( uint )NMT_SET_STANCE, NCS_INGAME, (void*)&OnInGame, pContext ); - pSession->AddTransition( NCS_INGAME, ( uint )NMT_NOTIFY_REQUEST, NCS_INGAME, (void*)&OnInGame, pContext ); - pSession->AddTransition( NCS_INGAME, ( uint )NMT_FORMATION_GOTO, NCS_INGAME, (void*)&OnInGame, pContext ); - pSession->AddTransition( NCS_INGAME, ( uint )NMT_FORMATION_CONTACT_ACTION, NCS_INGAME, (void*)&OnInGame, pContext ); - pSession->AddTransition( NCS_INGAME, ( uint )NMT_END_COMMAND_BATCH, NCS_INGAME, (void*)&OnInGame, pContext ); - - // Set first state - pSession->SetFirstState( NCS_CONNECT ); - - return true; -} - -//----------------------------------------------------------------------------- -// Name: HandleConnect() -// Desc: Called when the client successfully connected to server -//----------------------------------------------------------------------------- -bool CNetClient::HandleConnect( CNetSession* pSession ) -{ - // Validate parameters - if ( !pSession ) return false; - - return true; -} - -//----------------------------------------------------------------------------- -// Name: HandleDisconnect() -// Desc: Called when the client disconnected from the server -//----------------------------------------------------------------------------- -bool CNetClient::HandleDisconnect( CNetSession *pSession ) -{ - // Validate parameters - if ( !pSession ) return false; - - return true; -} - -//----------------------------------------------------------------------------- -// Name: OnError() -// Desc: -//----------------------------------------------------------------------------- -bool CNetClient::OnError( void* pContext, CFsmEvent* pEvent ) -{ - // Validate parameters - if ( !pEvent || !pContext ) return false; - - // Error event? - if ( pEvent->GetType() != NMT_ERROR ) return true; - - CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost; - assert( pClient ); - - CErrorMessage* pMessage = ( CErrorMessage* )pEvent->GetParamRef(); - if ( pMessage ) - { - LOG( CLogger::Error, LOG_CAT_NET, "CNetClient::OnError(): Error description %s", pMessage->m_Error ); - - if ( pClient->m_OnConnectComplete.Defined() ) - { - CConnectCompleteEvent connectComplete( ( CStrW )pMessage->m_Error, false ); - pClient->m_OnConnectComplete.DispatchEvent( pClient->GetScript(), &connectComplete ); - } - } - - return true; -} - -//----------------------------------------------------------------------------- -// Name: OnPlayer() -// Desc: -//----------------------------------------------------------------------------- -bool CNetClient::OnPlayerJoin( void* pContext, CFsmEvent* pEvent ) -{ - // Validate parameters - if ( !pEvent || !pContext ) return false; - - // Connect event? - if ( pEvent->GetType() != NMT_PLAYER_JOIN ) return true; - - CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost; - assert( pClient ); - - CPlayerJoinMessage* pMessage = ( CPlayerJoinMessage* )pEvent->GetParamRef(); - if ( pMessage ) - { - for ( uint i = 0; i < pMessage->m_Clients.size(); i++ ) - { - pClient->OnPlayer( pMessage->m_Clients[ i ].m_SessionID, pMessage->m_Clients[ i ].m_Name ); - } - - if ( pClient->m_OnConnectComplete.Defined() ) - { - CConnectCompleteEvent connectComplete( ( CStrW )PS_OK, true ); - pClient->m_OnConnectComplete.DispatchEvent( pClient->GetScript(), &connectComplete ); - } - } - - return true; -} - -//----------------------------------------------------------------------------- -// 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; - - assert( pClient ); - 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; - ( ( CNetHost* )pClient )->SendMessage( pSession, &handshake ); - } - break; - - case NMT_SERVER_HANDSHAKE_RESPONSE: - { - CAuthenticateMessage authenticate; - authenticate.m_Name = pClient->m_Nickname; - authenticate.m_Password = pClient->m_Password; - ( ( CNetHost* )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; - - assert( pClient ); - assert( pSession ); - - if ( pEvent->GetType() == 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_CAT_NET, "CNetClient::OnAuthenticate(): Authentication result: %ls", pMessage->m_Message.c_str() ); - - pSession->SetID( pMessage->m_SessionID ); - - LOG(CLogger::Error, LOG_CAT_NET, "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; - - assert( pClient ); - assert( pSession ); - - switch ( pEvent->GetType() ) - { - - //CHAIN(BaseHandler); - //CHAIN(ChatHandler); - -// case NMT_GAME_START: - -// pClient->StartGame(); -// break; - - 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_CAT_NET, "Invalid slot assignment %s", 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_COMMAND_FIRST && pMessage->GetType() < NMT_COMMAND_LAST ) - { - pClient->QueueIncomingMessage( pMessage ); - - return true; - } - - if ( pMessage->GetType() == NMT_END_COMMAND_BATCH ) - { - CEndCommandBatchMessage* pMessage = ( CEndCommandBatchMessage* )pEvent->GetParamRef(); - if ( !pMessage ) return false; - - pClient->SetTurnLength( 1, pMessage->m_TurnLength ); - - pClient->m_TurnPending = true; - - // FIXME When the command batch has ended, we should start accepting - // commands for the next turn. This will be accomplished by calling - // NewTurn. *BUT* we shouldn't prematurely proceed game simulation - // since this will produce jerky playback (everything expects a sim - // turn to have a certain duration). - - // We should make sure that any commands received after this message - // are queued in the next batch (#2 instead of #1). If we're already - // putting everything new in batch 2 - we should fast-forward a bit to - // catch up with the server. - } - } - - 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: -//----------------------------------------------------------------------------- -bool CNetClient::OnStartGame( void* pContext, CFsmEvent* pEvent ) -{ - // Validate parameters - if ( !pEvent || !pContext ) return false; - - CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost; - - if ( pClient->m_OnStartGame.Defined() ) - { - CStartGameEvent event; - pClient->m_OnStartGame.DispatchEvent( pClient->GetScript(), &event ); - } - - 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_CAT_NET, "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; - - // Send an end-of-batch message for turn 0 to signal that we're ready. - CEndCommandBatchMessage endBatch; - endBatch.m_TurnLength = 1000 / g_frequencyFilter->StableFrequency(); - - CNetSession* pSession = GetSession( 0 ); - CNetHost::SendMessage( pSession, &endBatch ); - - return 0; -} - -//----------------------------------------------------------------------------- -// Name: GetLocalPlayer() -// Desc: -//----------------------------------------------------------------------------- -CPlayer* CNetClient::GetLocalPlayer() -{ - return m_pLocalPlayerSlot->GetPlayer(); -} - -//----------------------------------------------------------------------------- -// Name: NewTurn() -// Desc: -//----------------------------------------------------------------------------- -void CNetClient::NewTurn() -{ - CScopeLock lock(m_Mutex); - - RotateBatches(); - ClearBatch(2); - m_TurnPending = false; - - //debug_printf("In NewTurn - sending ack\n"); - CEndCommandBatchMessage* pMsg = new CEndCommandBatchMessage; - pMsg->m_TurnLength=1000/g_frequencyFilter->StableFrequency(); // JW: it'd probably be nicer to get the FPS as a parameter - - CNetSession* pSession = GetSession( 0 ); - CNetHost::SendMessage( pSession, pMsg ); -} - -//----------------------------------------------------------------------------- -// Name: QueueLocalCommand() -// Desc: -//----------------------------------------------------------------------------- -void CNetClient::QueueLocalCommand( CNetMessage* pMessage ) -{ - if ( !pMessage ) return; - - // Don't save these locally, since they'll be bounced by the server anyway - CNetSession* pSession = GetSession( 0 ); - CNetHost::SendMessage( pSession, pMessage ); -} - -//----------------------------------------------------------------------------- -// Name: QueueIncomingMessage() -// Desc: -//----------------------------------------------------------------------------- -void CNetClient::QueueIncomingMessage( CNetMessage* pMessage ) -{ - CScopeLock lock( m_Mutex ); - - QueueMessage( 2, pMessage ); -} +/** + *----------------------------------------------------------------------------- + * 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 "scripting/DOMEvent.h" +#include "scripting/JSConversions.h" +#include "scripting/ScriptableObject.h" +#include "ps/CStr.h" +#include "ps/CLogger.h" +#include "ps/CConsole.h" +#include "ps/Game.h" +#include "ps/Globals.h" +#include "ps/GameAttributes.h" +#include "simulation/Simulation.h" + +// DECLARATIONS + +#define LOG_CAT_NET "net" + +CNetClient *g_NetClient=NULL; +extern int fps; + +//----------------------------------------------------------------------------- +// Name: CServerPlayer() +// Desc: Constructor +//----------------------------------------------------------------------------- +CServerPlayer::CServerPlayer( uint sessionID, const CStr& nickname ) +{ + m_SessionID = sessionID; + m_Nickname = nickname; +} + +//----------------------------------------------------------------------------- +// Name: ~CServerPlayer() +// Desc: Destructor +//----------------------------------------------------------------------------- +CServerPlayer::~CServerPlayer( void ) +{ +} + +//----------------------------------------------------------------------------- +// Name: ScriptingInit() +// Desc: +//----------------------------------------------------------------------------- +void CServerPlayer::ScriptingInit( void ) +{ + AddProperty(L"id", &CServerPlayer::m_SessionID, true); + AddProperty(L"name", &CServerPlayer::m_Nickname, true); + + CJSObject::ScriptingInit( "NetClient_ServerSession" ); +} + +//----------------------------------------------------------------------------- +// Name: CNetClient() +// Desc: Constructor +//----------------------------------------------------------------------------- +CNetClient::CNetClient( CGame* pGame, CGameAttributes* pGameAttribs ) +: m_JsPlayers( &m_Players ) +{ + m_pLocalPlayerSlot = NULL; + m_pGame = pGame; + m_pGameAttributes = pGameAttribs; + m_TurnPending = false; + + //ONCE( ScriptingInit(); ); + + m_pGame->GetSimulation()->SetTurnManager(this); + + g_ScriptingHost.SetGlobal("g_NetClient", OBJECT_TO_JSVAL(GetScript())); +} + +//----------------------------------------------------------------------------- +// 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; + } + + m_Players.clear(); + + g_ScriptingHost.SetGlobal("g_NetClient", JSVAL_NULL); +} + +//----------------------------------------------------------------------------- +// Name: ScriptingInit() +// Desc: +//----------------------------------------------------------------------------- +void CNetClient::ScriptingInit() +{ + AddMethod("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(L"playerName", &CNetClient::m_Nickname); + //AddProperty(L"sessionId", &CNetClient::m_SessionID); + + AddProperty(L"sessions", &CNetClient::m_JsPlayers); + + CJSMap< PlayerMap >::ScriptingInit("NetClient_SessionMap"); + CJSObject::ScriptingInit("NetClient"); + //CGameAttributes::ScriptingInit(); + //CServerPlayer::ScriptingInit(); +} + +//----------------------------------------------------------------------------- +// 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 = new FsmActionCtx; + if ( !pContext ) return false; + + 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_GOTO, NCS_INGAME, (void*)&OnInGame, pContext ); + pSession->AddTransition( NCS_INGAME, ( uint )NMT_PATROL, NCS_INGAME, (void*)&OnInGame, pContext ); + pSession->AddTransition( NCS_INGAME, ( uint )NMT_ADD_WAYPOINT, NCS_INGAME, (void*)&OnInGame, pContext ); + pSession->AddTransition( NCS_INGAME, ( uint )NMT_CONTACT_ACTION, NCS_INGAME, (void*)&OnInGame, pContext ); + pSession->AddTransition( NCS_INGAME, ( uint )NMT_PRODUCE, NCS_INGAME, (void*)&OnInGame, pContext ); + pSession->AddTransition( NCS_INGAME, ( uint )NMT_PLACE_OBJECT, NCS_INGAME, (void*)&OnInGame, pContext ); + pSession->AddTransition( NCS_INGAME, ( uint )NMT_RUN, NCS_INGAME, (void*)&OnInGame, pContext ); + pSession->AddTransition( NCS_INGAME, ( uint )NMT_SET_RALLY_POINT, NCS_INGAME, (void*)&OnInGame, pContext ); + pSession->AddTransition( NCS_INGAME, ( uint )NMT_SET_STANCE, NCS_INGAME, (void*)&OnInGame, pContext ); + pSession->AddTransition( NCS_INGAME, ( uint )NMT_NOTIFY_REQUEST, NCS_INGAME, (void*)&OnInGame, pContext ); + pSession->AddTransition( NCS_INGAME, ( uint )NMT_FORMATION_GOTO, NCS_INGAME, (void*)&OnInGame, pContext ); + pSession->AddTransition( NCS_INGAME, ( uint )NMT_FORMATION_CONTACT_ACTION, NCS_INGAME, (void*)&OnInGame, pContext ); + pSession->AddTransition( NCS_INGAME, ( uint )NMT_END_COMMAND_BATCH, NCS_INGAME, (void*)&OnInGame, pContext ); + + // Set first state + pSession->SetFirstState( NCS_CONNECT ); + + return true; +} + +//----------------------------------------------------------------------------- +// Name: HandleConnect() +// Desc: Called when the client successfully connected to server +//----------------------------------------------------------------------------- +bool CNetClient::HandleConnect( CNetSession* pSession ) +{ + // Validate parameters + if ( !pSession ) return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Name: HandleDisconnect() +// Desc: Called when the client disconnected from the server +//----------------------------------------------------------------------------- +bool CNetClient::HandleDisconnect( CNetSession *pSession ) +{ + // Validate parameters + if ( !pSession ) return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Name: OnError() +// Desc: +//----------------------------------------------------------------------------- +bool CNetClient::OnError( void* pContext, CFsmEvent* pEvent ) +{ + // Validate parameters + if ( !pEvent || !pContext ) return false; + + // Error event? + if ( pEvent->GetType() != NMT_ERROR ) return true; + + CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost; + assert( pClient ); + + CErrorMessage* pMessage = ( CErrorMessage* )pEvent->GetParamRef(); + if ( pMessage ) + { + LOG( CLogger::Error, LOG_CAT_NET, "CNetClient::OnError(): Error description %s", pMessage->m_Error ); + + if ( pClient->m_OnConnectComplete.Defined() ) + { + CConnectCompleteEvent connectComplete( ( CStrW )pMessage->m_Error, false ); + pClient->m_OnConnectComplete.DispatchEvent( pClient->GetScript(), &connectComplete ); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: OnPlayer() +// Desc: +//----------------------------------------------------------------------------- +bool CNetClient::OnPlayerJoin( void* pContext, CFsmEvent* pEvent ) +{ + // Validate parameters + if ( !pEvent || !pContext ) return false; + + // Connect event? + if ( pEvent->GetType() != NMT_PLAYER_JOIN ) return true; + + CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost; + assert( pClient ); + + CPlayerJoinMessage* pMessage = ( CPlayerJoinMessage* )pEvent->GetParamRef(); + if ( pMessage ) + { + for ( uint i = 0; i < pMessage->m_Clients.size(); i++ ) + { + pClient->OnPlayer( pMessage->m_Clients[ i ].m_SessionID, pMessage->m_Clients[ i ].m_Name ); + } + + if ( pClient->m_OnConnectComplete.Defined() ) + { + CConnectCompleteEvent connectComplete( ( CStrW )PS_OK, true ); + pClient->m_OnConnectComplete.DispatchEvent( pClient->GetScript(), &connectComplete ); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// 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; + + assert( pClient ); + 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; + ( ( CNetHost* )pClient )->SendMessage( pSession, &handshake ); + } + break; + + case NMT_SERVER_HANDSHAKE_RESPONSE: + { + CAuthenticateMessage authenticate; + authenticate.m_Name = pClient->m_Nickname; + authenticate.m_Password = pClient->m_Password; + ( ( CNetHost* )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; + + assert( pClient ); + assert( pSession ); + + if ( pEvent->GetType() == 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_CAT_NET, "CNetClient::OnAuthenticate(): Authentication result: %ls", pMessage->m_Message.c_str() ); + + pSession->SetID( pMessage->m_SessionID ); + + LOG(CLogger::Error, LOG_CAT_NET, "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; + + assert( pClient ); + assert( pSession ); + + switch ( pEvent->GetType() ) + { + + //CHAIN(BaseHandler); + //CHAIN(ChatHandler); + +// case NMT_GAME_START: + +// pClient->StartGame(); +// break; + + 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_CAT_NET, "Invalid slot assignment %s", 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_COMMAND_FIRST && pMessage->GetType() < NMT_COMMAND_LAST ) + { + pClient->QueueIncomingMessage( pMessage ); + + return true; + } + + if ( pMessage->GetType() == NMT_END_COMMAND_BATCH ) + { + CEndCommandBatchMessage* pMessage = ( CEndCommandBatchMessage* )pEvent->GetParamRef(); + if ( !pMessage ) return false; + + pClient->SetTurnLength( 1, pMessage->m_TurnLength ); + + pClient->m_TurnPending = true; + + // FIXME When the command batch has ended, we should start accepting + // commands for the next turn. This will be accomplished by calling + // NewTurn. *BUT* we shouldn't prematurely proceed game simulation + // since this will produce jerky playback (everything expects a sim + // turn to have a certain duration). + + // We should make sure that any commands received after this message + // are queued in the next batch (#2 instead of #1). If we're already + // putting everything new in batch 2 - we should fast-forward a bit to + // catch up with the server. + } + } + + 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: +//----------------------------------------------------------------------------- +bool CNetClient::OnStartGame( void* pContext, CFsmEvent* pEvent ) +{ + // Validate parameters + if ( !pEvent || !pContext ) return false; + + CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost; + + if ( pClient->m_OnStartGame.Defined() ) + { + CStartGameEvent event; + pClient->m_OnStartGame.DispatchEvent( pClient->GetScript(), &event ); + } + + 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_CAT_NET, "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; + + // Send an end-of-batch message for turn 0 to signal that we're ready. + CEndCommandBatchMessage endBatch; + endBatch.m_TurnLength = 1000 / g_frequencyFilter->StableFrequency(); + + CNetSession* pSession = GetSession( 0 ); + CNetHost::SendMessage( pSession, &endBatch ); + + return 0; +} + +//----------------------------------------------------------------------------- +// Name: GetLocalPlayer() +// Desc: +//----------------------------------------------------------------------------- +CPlayer* CNetClient::GetLocalPlayer() +{ + return m_pLocalPlayerSlot->GetPlayer(); +} + +//----------------------------------------------------------------------------- +// Name: NewTurn() +// Desc: +//----------------------------------------------------------------------------- +void CNetClient::NewTurn() +{ + CScopeLock lock(m_Mutex); + + RotateBatches(); + ClearBatch(2); + m_TurnPending = false; + + //debug_printf("In NewTurn - sending ack\n"); + CEndCommandBatchMessage* pMsg = new CEndCommandBatchMessage; + pMsg->m_TurnLength=1000/g_frequencyFilter->StableFrequency(); // JW: it'd probably be nicer to get the FPS as a parameter + + CNetSession* pSession = GetSession( 0 ); + CNetHost::SendMessage( pSession, pMsg ); +} + +//----------------------------------------------------------------------------- +// Name: QueueLocalCommand() +// Desc: +//----------------------------------------------------------------------------- +void CNetClient::QueueLocalCommand( CNetMessage* pMessage ) +{ + if ( !pMessage ) return; + + // Don't save these locally, since they'll be bounced by the server anyway + CNetSession* pSession = GetSession( 0 ); + CNetHost::SendMessage( pSession, pMessage ); +} + +//----------------------------------------------------------------------------- +// Name: QueueIncomingMessage() +// Desc: +//----------------------------------------------------------------------------- +void CNetClient::QueueIncomingMessage( CNetMessage* pMessage ) +{ + CScopeLock lock( m_Mutex ); + + QueueMessage( 2, pMessage ); +} diff --git a/source/network/NetClient.h b/source/network/NetClient.h index d8d9060cb7..fd5f56aa80 100644 --- a/source/network/NetClient.h +++ b/source/network/NetClient.h @@ -1,161 +1,161 @@ -/** - *----------------------------------------------------------------------------- - * FILE : NetClient.h - * PROJECT : 0 A.D. - * DESCRIPTION : Network client class interface file - *----------------------------------------------------------------------------- - */ - -#ifndef NETCLIENT_H -#define NETCLIENT_H - -// INCLUDES -#include "NetSession.h" -#include "ps/CStr.h" -#include "simulation/TurnManager.h" -#include "simulation/ScriptObject.h" -#include "scripting/ScriptableObject.h" -#include "ps/scripting/JSMap.h" - -#include - -// DECLARATIONS -enum -{ - NCS_CONNECT = 200, - NCS_HANDSHAKE = 300, - NCS_AUTHENTICATE = 400, - NCS_PREGAME = 500, - NCS_INGAME = 600 -}; - -class CPlayerSlot; -class CPlayer; -class CGame; -class CGameAttributes; -class CServerPlayer; - -//typedef std::map< uint, CStr > PlayerMap; -typedef std::map< uint, CServerPlayer* > PlayerMap; - -/* - CLASS : CServerPlayer - DESCRIPTION : - NOTES : -*/ -class CServerPlayer : public CJSObject< 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; } -protected: - -private: - - CServerPlayer( const CServerPlayer& ); - CServerPlayer& operator=( const CServerPlayer& ); - - uint m_SessionID; // Player session ID - CStr m_Nickname; // Player nickname -}; - -/* - CLASS : CNetClient - DESCRIPTION : - NOTES : -*/ - -class CNetClient: public CNetHost, - public CJSObject, - protected CTurnManager -{ -public: - - CNetClient( CGame* pGame, CGameAttributes* pGameAttributes ); - ~CNetClient( void ); - - bool CreateSession ( void ); - void OnPlayer ( uint ID, const CStr& name ); - void OnPlayerLeave ( uint ID ); - - /** - * Returns true indicating the host acts as a client - * - * @return Always true - */ - virtual bool IsClient( void ) const { return true; } - - // Get a pointer to our player - CPlayer* GetLocalPlayer(); - - CJSMap< PlayerMap > m_JsPlayers; - - CStr m_Nickname; - CStr m_Password; - //int m_SessionID; - - CPlayerSlot *m_pLocalPlayerSlot; - CGame *m_pGame; - CGameAttributes *m_pGameAttributes; - - // Are we currently in a locally-yet-unsimulated turn? - // This is set to true when we receive a command batch and cleared in NewTurn(). - // The server also ensures that it does not send a new turn until we ack one. - bool m_TurnPending; - - // Mutex for accessing batches - CMutex m_Mutex; - - // JS event scripts - CScriptObject m_OnStartGame; - CScriptObject m_OnChat; - CScriptObject m_OnConnectComplete; - CScriptObject m_OnDisconnect; - CScriptObject m_OnPlayerJoin; - CScriptObject m_OnPlayerLeave; - - static void ScriptingInit( void ); - int StartGame( void ); - -protected: - - virtual bool SetupSession ( CNetSession* pSession ); - virtual bool HandleConnect ( CNetSession* pSession ); - virtual bool HandleDisconnect ( CNetSession *pSession ); - - virtual void NewTurn ( void ); - virtual bool NewTurnReady ( void ) { return m_TurnPending; } - virtual void QueueLocalCommand ( CNetMessage* pMessage ); - void QueueIncomingMessage ( CNetMessage* pMessage ); - -private: - - // Not implemented - CNetClient( const CNetClient& ); - CNetClient& operator=( const CNetClient& ); - - 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 ); - - - bool SetupConnection( JSContext *cx, uintN argc, jsval *argv ); - - //CNetSession* m_Session; // Server session - PlayerMap m_Players; // List of online players -}; - -extern CNetClient *g_NetClient; - -#endif // NETCLIENT_H - +/** + *----------------------------------------------------------------------------- + * FILE : NetClient.h + * PROJECT : 0 A.D. + * DESCRIPTION : Network client class interface file + *----------------------------------------------------------------------------- + */ + +#ifndef NETCLIENT_H +#define NETCLIENT_H + +// INCLUDES +#include "NetSession.h" +#include "ps/CStr.h" +#include "simulation/TurnManager.h" +#include "simulation/ScriptObject.h" +#include "scripting/ScriptableObject.h" +#include "ps/scripting/JSMap.h" + +#include + +// DECLARATIONS +enum +{ + NCS_CONNECT = 200, + NCS_HANDSHAKE = 300, + NCS_AUTHENTICATE = 400, + NCS_PREGAME = 500, + NCS_INGAME = 600 +}; + +class CPlayerSlot; +class CPlayer; +class CGame; +class CGameAttributes; +class CServerPlayer; + +//typedef std::map< uint, CStr > PlayerMap; +typedef std::map< uint, CServerPlayer* > PlayerMap; + +/* + CLASS : CServerPlayer + DESCRIPTION : + NOTES : +*/ +class CServerPlayer : public CJSObject< 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; } +protected: + +private: + + CServerPlayer( const CServerPlayer& ); + CServerPlayer& operator=( const CServerPlayer& ); + + uint m_SessionID; // Player session ID + CStr m_Nickname; // Player nickname +}; + +/* + CLASS : CNetClient + DESCRIPTION : + NOTES : +*/ + +class CNetClient: public CNetHost, + public CJSObject, + protected CTurnManager +{ +public: + + CNetClient( CGame* pGame, CGameAttributes* pGameAttributes ); + ~CNetClient( void ); + + bool CreateSession ( void ); + void OnPlayer ( uint ID, const CStr& name ); + void OnPlayerLeave ( uint ID ); + + /** + * Returns true indicating the host acts as a client + * + * @return Always true + */ + virtual bool IsClient( void ) const { return true; } + + // Get a pointer to our player + CPlayer* GetLocalPlayer(); + + CJSMap< PlayerMap > m_JsPlayers; + + CStr m_Nickname; + CStr m_Password; + //int m_SessionID; + + CPlayerSlot *m_pLocalPlayerSlot; + CGame *m_pGame; + CGameAttributes *m_pGameAttributes; + + // Are we currently in a locally-yet-unsimulated turn? + // This is set to true when we receive a command batch and cleared in NewTurn(). + // The server also ensures that it does not send a new turn until we ack one. + bool m_TurnPending; + + // Mutex for accessing batches + CMutex m_Mutex; + + // JS event scripts + CScriptObject m_OnStartGame; + CScriptObject m_OnChat; + CScriptObject m_OnConnectComplete; + CScriptObject m_OnDisconnect; + CScriptObject m_OnPlayerJoin; + CScriptObject m_OnPlayerLeave; + + static void ScriptingInit( void ); + int StartGame( void ); + +protected: + + virtual bool SetupSession ( CNetSession* pSession ); + virtual bool HandleConnect ( CNetSession* pSession ); + virtual bool HandleDisconnect ( CNetSession *pSession ); + + virtual void NewTurn ( void ); + virtual bool NewTurnReady ( void ) { return m_TurnPending; } + virtual void QueueLocalCommand ( CNetMessage* pMessage ); + void QueueIncomingMessage ( CNetMessage* pMessage ); + +private: + + // Not implemented + CNetClient( const CNetClient& ); + CNetClient& operator=( const CNetClient& ); + + 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 ); + + + bool SetupConnection( JSContext *cx, uintN argc, jsval *argv ); + + //CNetSession* m_Session; // Server session + PlayerMap m_Players; // List of online players +}; + +extern CNetClient *g_NetClient; + +#endif // NETCLIENT_H + diff --git a/source/network/NetJsEvents.h b/source/network/NetJsEvents.h index d242d5637c..06732fe2d1 100644 --- a/source/network/NetJsEvents.h +++ b/source/network/NetJsEvents.h @@ -1,147 +1,147 @@ -/* - *----------------------------------------------------------------------------- - * 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 - +/* + *----------------------------------------------------------------------------- + * 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 + diff --git a/source/network/NetMessages.h b/source/network/NetMessages.h index 807073a953..e9a9ce6a57 100644 --- a/source/network/NetMessages.h +++ b/source/network/NetMessages.h @@ -1,263 +1,263 @@ -/** - *----------------------------------------------------------------------------- - * FILE : NetMessages.h - * PROJECT : 0 A.D. - * DESCRIPTION : 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 "simulation/EntityHandles.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_DEFAULT_PORT 0x5073 // 'P', 's' - -// 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_INVALID = 0, // Invalid message - NMT_SERVER_HANDSHAKE, // Handshake stage - NMT_CLIENT_HANDSHAKE, - NMT_SERVER_HANDSHAKE_RESPONSE, - 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_GAME_START, - NMT_END_COMMAND_BATCH, // In-game stage - NMT_GOTO, - NMT_COMMAND_FIRST = NMT_GOTO, - NMT_PATROL, - NMT_ADD_WAYPOINT, - NMT_CONTACT_ACTION, - NMT_PRODUCE, - NMT_PLACE_OBJECT, - NMT_REMOVE_OBJECT, - NMT_RUN, - NMT_SET_RALLY_POINT, - NMT_SET_STANCE, - NMT_NOTIFY_REQUEST, - NMT_FORMATION_GOTO, - NMT_FORMATION_CONTACT_ACTION, - NMT_COMMAND_LAST, - NMT_LAST // Last message in the list -}; - -// Authentication result codes -enum AuthenticateResultCode -{ - ARC_OK, - ARC_PASSWORD_INVALID, - ARC_NICK_TAKEN, - 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 - -#define ALLNETMSGS_DONT_CREATE_NMTS -#define START_NMT_CLASS_(_nm, _message) START_NMT_CLASS(C##_nm##Message, _message) -#define DERIVE_NMT_CLASS_(_base, _nm, _message) START_NMT_CLASS_DERIVED(C ## _base ## Message, C ## _nm ## Message, _message) - -START_NMTS() - -START_NMT_CLASS_(SrvHandshake, NMT_SERVER_HANDSHAKE) - NMT_FIELD_INT(m_Magic, u32, 4) - NMT_FIELD_INT(m_ProtocolVersion, u32, 4) - NMT_FIELD_INT(m_SoftwareVersion, u32, 4) -END_NMT_CLASS() - -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) -END_NMT_CLASS() - -START_NMT_CLASS_(SrvHandshakeResponse, NMT_SERVER_HANDSHAKE_RESPONSE) - NMT_FIELD_INT(m_UseProtocolVersion, u32, 4) - NMT_FIELD_INT(m_Flags, u32, 4) - NMT_FIELD(CStrW, m_Message) -END_NMT_CLASS() - -START_NMT_CLASS_(Authenticate, NMT_AUTHENTICATE) - 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(CStrW, m_Message) -END_NMT_CLASS() - -START_NMT_CLASS_(Chat, NMT_CHAT) - NMT_FIELD(CStrW, m_Sender) - NMT_FIELD_INT(m_Recipient, u32, 2) - 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) - NMT_FIELD(CStrW, m_Name) - NMT_FIELD(CStrW, m_Value) - 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() -END_NMT_CLASS() - -START_NMT_CLASS_(GameStart, NMT_GAME_START) -END_NMT_CLASS() - -START_NMT_CLASS_(EndCommandBatch, NMT_END_COMMAND_BATCH) - NMT_FIELD_INT(m_TurnLength, u32, 2) -END_NMT_CLASS() - -START_NMT_CLASS_(Command, NMT_INVALID) - NMT_FIELD(CEntityList, m_Entities) - NMT_FIELD_INT(m_IsQueued, u32, 1) -END_NMT_CLASS() - -DERIVE_NMT_CLASS_(Command, Goto, NMT_GOTO) - NMT_FIELD_INT(m_TargetX, u32, 2) - NMT_FIELD_INT(m_TargetY, u32, 2) -END_NMT_CLASS() - -DERIVE_NMT_CLASS_(Command, Run, NMT_RUN) - NMT_FIELD_INT(m_TargetX, u32, 2) - NMT_FIELD_INT(m_TargetY, u32, 2) -END_NMT_CLASS() - -DERIVE_NMT_CLASS_(Command, Patrol, NMT_PATROL) - NMT_FIELD_INT(m_TargetX, u32, 2) - NMT_FIELD_INT(m_TargetY, u32, 2) -END_NMT_CLASS() - -DERIVE_NMT_CLASS_(Command, AddWaypoint, NMT_ADD_WAYPOINT) - NMT_FIELD_INT(m_TargetX, u32, 2) - NMT_FIELD_INT(m_TargetY, u32, 2) -END_NMT_CLASS() - -DERIVE_NMT_CLASS_(Command, SetRallyPoint, NMT_SET_RALLY_POINT) - NMT_FIELD_INT(m_TargetX, u32, 2) - NMT_FIELD_INT(m_TargetY, u32, 2) -END_NMT_CLASS() - -DERIVE_NMT_CLASS_(Command, SetStance, NMT_SET_STANCE) - NMT_FIELD(CStrW, m_Stance) -END_NMT_CLASS() - -DERIVE_NMT_CLASS_(Command, ContactAction, NMT_CONTACT_ACTION) - NMT_FIELD(HEntity, m_Target) - NMT_FIELD_INT(m_Action, u32, 4) - NMT_FIELD_INT(m_Run, u32, 1) -END_NMT_CLASS() - -DERIVE_NMT_CLASS_(Command, Produce, NMT_PRODUCE) - NMT_FIELD_INT(m_Type, u32, 4) - NMT_FIELD(CStrW, m_Name) -END_NMT_CLASS() - -DERIVE_NMT_CLASS_(Command, PlaceObject, NMT_PLACE_OBJECT) - NMT_FIELD(CStrW, m_Template) - NMT_FIELD_INT(m_X, u32, 4) - NMT_FIELD_INT(m_Y, u32, 4) - NMT_FIELD_INT(m_Z, u32, 4) - NMT_FIELD_INT(m_Angle, u32, 4) // Orientation angle -END_NMT_CLASS() - -DERIVE_NMT_CLASS_(Command, RemoveObject, NMT_REMOVE_OBJECT) -END_NMT_CLASS() - -DERIVE_NMT_CLASS_(Command, NotifyRequest, NMT_NOTIFY_REQUEST) - NMT_FIELD(HEntity, m_Target) - NMT_FIELD_INT(m_Action, u32, 4) -END_NMT_CLASS() - -DERIVE_NMT_CLASS_(Command, FormationGoto, NMT_FORMATION_GOTO) - NMT_FIELD_INT(m_TargetX, u32, 2) - NMT_FIELD_INT(m_TargetY, u32, 2) -END_NMT_CLASS() - -DERIVE_NMT_CLASS_(Command, FormationContactAction, NMT_FORMATION_CONTACT_ACTION) - NMT_FIELD(HEntity, m_Target) - NMT_FIELD_INT(m_Action, u32, 4) -END_NMT_CLASS() - -END_NMTS() - -#else -#ifndef ALLNETMSGS_DONT_CREATE_NMTS - -# ifdef ALLNETMSGS_IMPLEMENT -# define NMT_CREATOR_IMPLEMENT -# endif - -# define NMT_CREATE_HEADER_NAME "NetMessages.h" -# include "NMTCreator.h" - -#endif // #ifndef ALLNETMSGS_DONT_CREATE_NMTS -#endif // #ifdef CREATING_NMT +/** + *----------------------------------------------------------------------------- + * FILE : NetMessages.h + * PROJECT : 0 A.D. + * DESCRIPTION : 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 "simulation/EntityHandles.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_DEFAULT_PORT 0x5073 // 'P', 's' + +// 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_INVALID = 0, // Invalid message + NMT_SERVER_HANDSHAKE, // Handshake stage + NMT_CLIENT_HANDSHAKE, + NMT_SERVER_HANDSHAKE_RESPONSE, + 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_GAME_START, + NMT_END_COMMAND_BATCH, // In-game stage + NMT_GOTO, + NMT_COMMAND_FIRST = NMT_GOTO, + NMT_PATROL, + NMT_ADD_WAYPOINT, + NMT_CONTACT_ACTION, + NMT_PRODUCE, + NMT_PLACE_OBJECT, + NMT_REMOVE_OBJECT, + NMT_RUN, + NMT_SET_RALLY_POINT, + NMT_SET_STANCE, + NMT_NOTIFY_REQUEST, + NMT_FORMATION_GOTO, + NMT_FORMATION_CONTACT_ACTION, + NMT_COMMAND_LAST, + NMT_LAST // Last message in the list +}; + +// Authentication result codes +enum AuthenticateResultCode +{ + ARC_OK, + ARC_PASSWORD_INVALID, + ARC_NICK_TAKEN, + 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 + +#define ALLNETMSGS_DONT_CREATE_NMTS +#define START_NMT_CLASS_(_nm, _message) START_NMT_CLASS(C##_nm##Message, _message) +#define DERIVE_NMT_CLASS_(_base, _nm, _message) START_NMT_CLASS_DERIVED(C ## _base ## Message, C ## _nm ## Message, _message) + +START_NMTS() + +START_NMT_CLASS_(SrvHandshake, NMT_SERVER_HANDSHAKE) + NMT_FIELD_INT(m_Magic, u32, 4) + NMT_FIELD_INT(m_ProtocolVersion, u32, 4) + NMT_FIELD_INT(m_SoftwareVersion, u32, 4) +END_NMT_CLASS() + +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) +END_NMT_CLASS() + +START_NMT_CLASS_(SrvHandshakeResponse, NMT_SERVER_HANDSHAKE_RESPONSE) + NMT_FIELD_INT(m_UseProtocolVersion, u32, 4) + NMT_FIELD_INT(m_Flags, u32, 4) + NMT_FIELD(CStrW, m_Message) +END_NMT_CLASS() + +START_NMT_CLASS_(Authenticate, NMT_AUTHENTICATE) + 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(CStrW, m_Message) +END_NMT_CLASS() + +START_NMT_CLASS_(Chat, NMT_CHAT) + NMT_FIELD(CStrW, m_Sender) + NMT_FIELD_INT(m_Recipient, u32, 2) + 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) + NMT_FIELD(CStrW, m_Name) + NMT_FIELD(CStrW, m_Value) + 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() +END_NMT_CLASS() + +START_NMT_CLASS_(GameStart, NMT_GAME_START) +END_NMT_CLASS() + +START_NMT_CLASS_(EndCommandBatch, NMT_END_COMMAND_BATCH) + NMT_FIELD_INT(m_TurnLength, u32, 2) +END_NMT_CLASS() + +START_NMT_CLASS_(Command, NMT_INVALID) + NMT_FIELD(CEntityList, m_Entities) + NMT_FIELD_INT(m_IsQueued, u32, 1) +END_NMT_CLASS() + +DERIVE_NMT_CLASS_(Command, Goto, NMT_GOTO) + NMT_FIELD_INT(m_TargetX, u32, 2) + NMT_FIELD_INT(m_TargetY, u32, 2) +END_NMT_CLASS() + +DERIVE_NMT_CLASS_(Command, Run, NMT_RUN) + NMT_FIELD_INT(m_TargetX, u32, 2) + NMT_FIELD_INT(m_TargetY, u32, 2) +END_NMT_CLASS() + +DERIVE_NMT_CLASS_(Command, Patrol, NMT_PATROL) + NMT_FIELD_INT(m_TargetX, u32, 2) + NMT_FIELD_INT(m_TargetY, u32, 2) +END_NMT_CLASS() + +DERIVE_NMT_CLASS_(Command, AddWaypoint, NMT_ADD_WAYPOINT) + NMT_FIELD_INT(m_TargetX, u32, 2) + NMT_FIELD_INT(m_TargetY, u32, 2) +END_NMT_CLASS() + +DERIVE_NMT_CLASS_(Command, SetRallyPoint, NMT_SET_RALLY_POINT) + NMT_FIELD_INT(m_TargetX, u32, 2) + NMT_FIELD_INT(m_TargetY, u32, 2) +END_NMT_CLASS() + +DERIVE_NMT_CLASS_(Command, SetStance, NMT_SET_STANCE) + NMT_FIELD(CStrW, m_Stance) +END_NMT_CLASS() + +DERIVE_NMT_CLASS_(Command, ContactAction, NMT_CONTACT_ACTION) + NMT_FIELD(HEntity, m_Target) + NMT_FIELD_INT(m_Action, u32, 4) + NMT_FIELD_INT(m_Run, u32, 1) +END_NMT_CLASS() + +DERIVE_NMT_CLASS_(Command, Produce, NMT_PRODUCE) + NMT_FIELD_INT(m_Type, u32, 4) + NMT_FIELD(CStrW, m_Name) +END_NMT_CLASS() + +DERIVE_NMT_CLASS_(Command, PlaceObject, NMT_PLACE_OBJECT) + NMT_FIELD(CStrW, m_Template) + NMT_FIELD_INT(m_X, u32, 4) + NMT_FIELD_INT(m_Y, u32, 4) + NMT_FIELD_INT(m_Z, u32, 4) + NMT_FIELD_INT(m_Angle, u32, 4) // Orientation angle +END_NMT_CLASS() + +DERIVE_NMT_CLASS_(Command, RemoveObject, NMT_REMOVE_OBJECT) +END_NMT_CLASS() + +DERIVE_NMT_CLASS_(Command, NotifyRequest, NMT_NOTIFY_REQUEST) + NMT_FIELD(HEntity, m_Target) + NMT_FIELD_INT(m_Action, u32, 4) +END_NMT_CLASS() + +DERIVE_NMT_CLASS_(Command, FormationGoto, NMT_FORMATION_GOTO) + NMT_FIELD_INT(m_TargetX, u32, 2) + NMT_FIELD_INT(m_TargetY, u32, 2) +END_NMT_CLASS() + +DERIVE_NMT_CLASS_(Command, FormationContactAction, NMT_FORMATION_CONTACT_ACTION) + NMT_FIELD(HEntity, m_Target) + NMT_FIELD_INT(m_Action, u32, 4) +END_NMT_CLASS() + +END_NMTS() + +#else +#ifndef ALLNETMSGS_DONT_CREATE_NMTS + +# ifdef ALLNETMSGS_IMPLEMENT +# define NMT_CREATOR_IMPLEMENT +# endif + +# define NMT_CREATE_HEADER_NAME "NetMessages.h" +# include "NMTCreator.h" + +#endif // #ifndef ALLNETMSGS_DONT_CREATE_NMTS +#endif // #ifdef CREATING_NMT diff --git a/source/network/NetServer.cpp b/source/network/NetServer.cpp index b0e29eb733..b0c1c48136 100644 --- a/source/network/NetServer.cpp +++ b/source/network/NetServer.cpp @@ -1,1055 +1,1055 @@ -/** - *----------------------------------------------------------------------------- - * FILE : NetServer.cpp - * PROJECT : 0 A.D. - * DESCRIPTION : Network server class implementation - *----------------------------------------------------------------------------- - */ - -// INCLUDES -#include "precompiled.h" -#include "ps/CLogger.h" -#include "ps/CConsole.h" -#include "simulation/Simulation.h" -#include "NetJsEvents.h" -#include "NetSession.h" -#include "NetServer.h" - -// DECLARATIONS - -CNetServer* g_NetServer = NULL; - -//----------------------------------------------------------------------------- -// Name: CNetServer() -// Desc: Constructor -//----------------------------------------------------------------------------- -CNetServer::CNetServer( CGame *pGame, CGameAttributes *pGameAttributes ) -: m_JsSessions( &m_IDSessions ) -{ - //ONCE( ScriptingInit(); ); - - m_Game = pGame; - m_GameAttributes = pGameAttributes; - m_MaxObservers = MAX_OBSERVERS; - //m_LastSessionID = 1; - m_Port = DEFAULT_HOST_PORT; - m_Name = DEFAULT_SERVER_NAME; - m_PlayerName = DEFAULT_PLAYER_NAME; - m_WelcomeMessage = DEFAULT_WELCOME_MESSAGE; - - // Set callbacks - m_GameAttributes->SetUpdateCallback( AttributeUpdate, this ); - m_GameAttributes->SetPlayerUpdateCallback( PlayerAttributeUpdate, this ); - m_GameAttributes->SetPlayerSlotAssignmentCallback( PlayerSlotAssignment, this ); - - // Set turn manager - CSimulation* pSimulation = m_Game->GetSimulation(); - if ( pSimulation ) pSimulation->SetTurnManager( this ); - - // Set an incredibly long turn length for debugging - // (e.g. less command batch spam that way) - for ( uint i = 0; i < 3; i++ ) - { - CTurnManager::SetTurnLength( i, DEFAULT_TURN_LENGTH ); - } - - g_ScriptingHost.SetGlobal( "g_NetServer", OBJECT_TO_JSVAL( GetScript() ) ); -} - -//----------------------------------------------------------------------------- -// Name: ~CNetServer() -// Desc: Destructor -//----------------------------------------------------------------------------- -CNetServer::~CNetServer() -{ - m_IDSessions.clear(); - - g_ScriptingHost.SetGlobal( "g_NetServer", JSVAL_NULL ); -} - -//----------------------------------------------------------------------------- -// Name: ScriptingInit() -// Desc: -//----------------------------------------------------------------------------- -void CNetServer::ScriptingInit( void ) -{ - CJSMap< IDSessionMap >::ScriptingInit( "NetServer_SessionMap" ); - - AddMethod< bool, &CNetServer::Start >( "open", 0 ); - - AddProperty( L"sessions", &CNetServer::m_JsSessions ); - AddProperty( L"serverPlayerName", &CNetServer::m_PlayerName ); - AddProperty( L"serverName", &CNetServer::m_Name ); - AddProperty( L"welcomeMessage", &CNetServer::m_WelcomeMessage ); - AddProperty( L"port", &CNetServer::m_Port ); - AddProperty( L"onChat", &CNetServer::m_OnChat ); - AddProperty( L"onClientConnect", &CNetServer::m_OnClientConnect ); - AddProperty( L"onClientDisconnect", &CNetServer::m_OnClientDisconnect ); - - CJSObject< CNetServer >::ScriptingInit( "NetServer" ); - - //CGameAttributes::ScriptingInit(); -} - -//----------------------------------------------------------------------------- -// Name: Start() -// Desc: Initialize the server -//----------------------------------------------------------------------------- -bool CNetServer::Start( JSContext* UNUSED(pContext), uintN UNUSED(argc), jsval* UNUSED(argv) ) -{ - // Setup initial state - m_State = SERVER_STATE_PREGAME; - - // Create new session - if ( !Create( m_Port, MAX_CLIENTS ) ) - { - LOG( CLogger::Error, LOG_CAT_NET, "CNetServer::Run(): Initialize failed" ); - - return false; - } - - return true; -} - -//----------------------------------------------------------------------------- -// Name: SetupSession() -// Desc: Setup new session -//----------------------------------------------------------------------------- -bool CNetServer::SetupSession( CNetSession* pSession ) -{ - // Validate parameters - if ( !pSession ) return false; - - FsmActionCtx* pContext = new FsmActionCtx; - if ( !pContext ) return false; - - pContext->pHost = this; - pContext->pSession = pSession; - - // Setup transitions for session - pSession->AddTransition( NSS_HANDSHAKE, ( uint )NMT_ERROR, NSS_HANDSHAKE, (void*)&OnError, pContext ); - pSession->AddTransition( NSS_HANDSHAKE, ( uint )NMT_CLIENT_HANDSHAKE, NSS_AUTHENTICATE, (void*)&OnHandshake, pContext ); - pSession->AddTransition( NSS_AUTHENTICATE, ( uint )NMT_ERROR, NSS_AUTHENTICATE, (void*)&OnError, pContext ); - pSession->AddTransition( NSS_AUTHENTICATE, ( uint )NMT_AUTHENTICATE, NSS_PREGAME, (void*)&OnAuthenticate, pContext ); - pSession->AddTransition( NSS_AUTHENTICATE, ( uint )NMT_APP_PREGAME, NSS_PREGAME, (void*)&OnPreGame, pContext ); - pSession->AddTransition( NSS_AUTHENTICATE, ( uint )NMT_APP_OBSERVER, NSS_INGAME, (void*)&OnChat, pContext ); - pSession->AddTransition( NSS_PREGAME, ( uint )NMT_END_COMMAND_BATCH, NSS_INGAME, (void*)&OnInGame, pContext ); - pSession->AddTransition( NSS_PREGAME, ( uint )NMT_CHAT, NSS_INGAME, (void*)&OnInGame, pContext ); - pSession->AddTransition( NSS_INGAME, ( uint )NMT_ERROR, NSS_INGAME, (void*)&OnError, pContext ); - pSession->AddTransition( NSS_INGAME, ( uint )NMT_CHAT, NSS_INGAME, (void*)&OnChat, pContext ); - pSession->AddTransition( NSS_INGAME, ( uint )NMT_GOTO, NSS_INGAME, (void*)&OnInGame, pContext ); - pSession->AddTransition( NSS_INGAME, ( uint )NMT_PATROL, NSS_INGAME, (void*)&OnInGame, pContext ); - pSession->AddTransition( NSS_INGAME, ( uint )NMT_ADD_WAYPOINT, NSS_INGAME, (void*)&OnInGame, pContext ); - pSession->AddTransition( NSS_INGAME, ( uint )NMT_CONTACT_ACTION, NSS_INGAME, (void*)&OnInGame, pContext ); - pSession->AddTransition( NSS_INGAME, ( uint )NMT_PRODUCE, NSS_INGAME, (void*)&OnInGame, pContext ); - pSession->AddTransition( NSS_INGAME, ( uint )NMT_PLACE_OBJECT, NSS_INGAME, (void*)&OnInGame, pContext ); - pSession->AddTransition( NSS_INGAME, ( uint )NMT_RUN, NSS_INGAME, (void*)&OnInGame, pContext ); - pSession->AddTransition( NSS_INGAME, ( uint )NMT_SET_RALLY_POINT, NSS_INGAME, (void*)&OnInGame, pContext ); - pSession->AddTransition( NSS_INGAME, ( uint )NMT_SET_STANCE, NSS_INGAME, (void*)&OnInGame, pContext ); - pSession->AddTransition( NSS_INGAME, ( uint )NMT_NOTIFY_REQUEST, NSS_INGAME, (void*)&OnInGame, pContext ); - pSession->AddTransition( NSS_INGAME, ( uint )NMT_FORMATION_GOTO, NSS_INGAME, (void*)&OnInGame, pContext ); - pSession->AddTransition( NSS_INGAME, ( uint )NMT_FORMATION_CONTACT_ACTION, NSS_INGAME, (void*)&OnInGame, pContext ); - pSession->AddTransition( NSS_INGAME, ( uint )NMT_END_COMMAND_BATCH, NSS_INGAME, (void*)&OnInGame, pContext ); - - // Set first state - pSession->SetFirstState( NSS_HANDSHAKE ); - - return true; -} - -//----------------------------------------------------------------------------- -// Name: HandleConnect() -// Desc: Called when a new player joins the current game -//----------------------------------------------------------------------------- -bool CNetServer::HandleConnect( CNetSession* pSession ) -{ - // Validate parameters - if ( !pSession ) return false; - - // Player joined the game, start authentication - CSrvHandshakeMessage handshake; - handshake.m_Magic = PS_PROTOCOL_MAGIC; - handshake.m_ProtocolVersion = PS_PROTOCOL_VERSION; - handshake.m_SoftwareVersion = PS_PROTOCOL_VERSION; - CNetHost::SendMessage( pSession, &handshake ); - - // Store new session into the map - m_IDSessions[ pSession->GetID() ] = pSession; - - //OnPlayerJoin( pSession ); - - return true; -} - -//----------------------------------------------------------------------------- -// Name: HandleDisconnect() -// Desc: Called when a client has disconnected -//----------------------------------------------------------------------------- -bool CNetServer::HandleDisconnect( CNetSession* pSession ) -{ - // Validate parameters - if ( !pSession ) return false; - - OnPlayerLeave( pSession ); - - // Free session slot so it can be recycled - m_IDSessions[ pSession->GetID() ] = NULL; - - return true; -} - -//----------------------------------------------------------------------------- -// Name: SetupPlayer() -// Desc: Sends the necessary messages for the player setup -//----------------------------------------------------------------------------- -void CNetServer::SetupPlayer( CNetSession* pSession ) -{ - CGameSetupMessage gameSetup; - CPlayerConfigMessage playerConfig; - CAssignPlayerSlotMessage assignSlot; - CPlayerJoinMessage playerJoin; - - // Validate parameters - if ( !pSession ) return; - - assert( m_GameAttributes ); - - // Send a new config message to the connected client - BuildGameSetupMessage( &gameSetup ); - CNetHost::SendMessage( pSession, &gameSetup ); - - // Add information for already connected clients and the server - playerJoin.m_Clients.resize( GetSessionCount() ); - playerJoin.m_Clients[ 0 ].m_SessionID = SERVER_SESSIONID; - playerJoin.m_Clients[ 0 ].m_Name = GetPlayerName(); - - for ( uint i = 0; i < GetSessionCount(); i++ ) - { - CNetSession* pCurrSession = GetSession( i ); - - // Skip the player being setup - if ( !pCurrSession || pCurrSession == pSession ) continue; - - playerJoin.m_Clients[ i + 1 ].m_SessionID = pCurrSession->GetID(); - playerJoin.m_Clients[ i + 1 ].m_Name = pCurrSession->GetName(); - } - - CNetHost::SendMessage( pSession, &playerJoin ); - - // TODO: Handle observers - - // Send a message informing about the newly connected player - playerJoin.m_Clients.resize( 1 ); - playerJoin.m_Clients[ 0 ].m_SessionID = pSession->GetID(); - playerJoin.m_Clients[ 0 ].m_Name = pSession->GetName(); - - for ( uint i = 0; i < GetSessionCount(); i++ ) - { - CNetSession* pCurrSession = GetSession( i ); - - // Skip the player being setup - if ( !pCurrSession || pCurrSession == pSession ) continue; - - CNetHost::SendMessage( pCurrSession, &playerJoin ); - } - //Broadcast( &playerJoin ); - - // Sync player slot assignments and player attributes - for ( uint i = 0; i < m_GameAttributes->GetSlotCount(); i++ ) - { - CPlayerSlot* pCurrSlot = m_GameAttributes->GetSlot( i ); - if ( !pCurrSlot ) continue; - - assignSlot.m_SlotID = (u32)pCurrSlot->GetSlotID(); - assignSlot.m_SessionID = (u32)pCurrSlot->GetSessionID(); - switch ( pCurrSlot->GetAssignment() ) - { - case SLOT_CLOSED: - assignSlot.m_Assignment = ASSIGN_CLOSED; - break; - - case SLOT_OPEN: - assignSlot.m_Assignment = ASSIGN_OPEN; - break; - - case SLOT_SESSION: - assignSlot.m_Assignment = ASSIGN_SESSION; - break; - - case SLOT_AI: - break; - } - - CNetHost::SendMessage( pSession, &assignSlot ); - - if ( pCurrSlot->GetAssignment() == SLOT_SESSION ) - { - // Setup player - BuildPlayerConfigMessage( &playerConfig, pCurrSlot->GetPlayer() ); - CNetHost::SendMessage( pSession, &playerConfig ); - } - } -} - -//----------------------------------------------------------------------------- -// Name: OnClientDisconnect() -// Desc: Called when a player joins the game -//----------------------------------------------------------------------------- -void CNetServer::OnPlayerJoin( CNetSession* pSession ) -{ - if ( m_OnClientConnect.Defined() ) - { - // Dispatch a client connect event - CClientConnectEvent event( pSession ); - m_OnClientConnect.DispatchEvent( GetScript(), &event ); - } -} - -//----------------------------------------------------------------------------- -// Name: OnClientDisconnect() -// Desc: Called when a player quits the game -//----------------------------------------------------------------------------- -void CNetServer::OnPlayerLeave( CNetSession* pSession ) -{ - // Validate parameters - if ( !pSession ) return; - - CPlayer* pPlayer = NULL; - CPlayerSlot* pPlayerSlot = NULL; - CPlayerLeaveMessage playerLeave; - - // Validate parameters - if ( !pSession ) return; - - pPlayer = pSession->GetPlayer(); - pPlayerSlot = pSession->GetPlayerSlot(); - - switch ( m_State ) - { - case SERVER_STATE_PREGAME: - - // Delete player's slot and sync client disconnection - if ( pPlayerSlot ) pPlayerSlot->AssignClosed(); - break; - - case SERVER_STATE_INGAME: - - // Revert player entities to Gaia control and - // wait for client reconnection - // TODO Reassign entities to Gaia control - // TODO Set everything up for re-connect and resume - if ( pPlayerSlot ) - { - SetClientPipe( pPlayerSlot->GetSlotID(), NULL ); - pPlayerSlot->AssignClosed(); - } - break; - - case SERVER_STATE_POSTGAME: - - // Synchronize disconnection - break; - } - - // Inform other clients about client disconnection - playerLeave.m_SessionID = pSession->GetID(); - - Broadcast( &playerLeave ); - - // Script object defined? - if ( m_OnClientDisconnect.Defined() ) - { - // Dispatch a client disconnect event - CClientDisconnectEvent event( pSession->GetID(), pSession->GetName() ); - m_OnClientDisconnect.DispatchEvent( GetScript(), &event ); - } -} - -//----------------------------------------------------------------------------- -// Name: OnError() -// Desc: -//----------------------------------------------------------------------------- -bool CNetServer::OnError( void* pContext, CFsmEvent* pEvent ) -{ - // Validate parameters - if ( !pContext || !pEvent ) return false; - - // Error event? - if ( pEvent->GetType() != NMT_ERROR ) return true; - - CNetServer* pServer = ( CNetServer* )( ( FsmActionCtx* )pContext )->pHost; - UNUSED2(pServer); - CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession; - UNUSED2(pSession); - - CErrorMessage* pMessage = ( CErrorMessage* )pEvent->GetParamRef(); - if ( pMessage ) - { - if ( pMessage->m_State == SS_UNCONNECTED ) - { - //if ( pSession->GetID() != -1) - // pServer->RemoveSession( pSession ); - - //delete pSession; - } - else - { - // Weird stuff... - LOG( CLogger::Warning, LOG_CAT_NET, "NMT_ERROR: %s", pMessage->ToString().c_str() ); - } - } - - return true; -} - -//----------------------------------------------------------------------------- -// Name: OnHandshake() -// Desc: -//----------------------------------------------------------------------------- -bool CNetServer::OnHandshake( void* pContext, CFsmEvent* pEvent ) -{ - // Validate parameters - if ( !pEvent || !pContext ) return false; - - // Client handshake event? - if ( pEvent->GetType() != NMT_CLIENT_HANDSHAKE ) return true; - - CNetServer* pServer = ( CNetServer* )( ( FsmActionCtx* )pContext )->pHost; - CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession; - - assert( pServer ); - assert( pSession ); - - CCliHandshakeMessage* pMessage = ( CCliHandshakeMessage* )pEvent->GetParamRef(); - if ( pMessage ) - { - if ( pMessage->m_ProtocolVersion != PS_PROTOCOL_VERSION ) - { - CCloseRequestMessage closeRequest; - ( ( CNetHost* )pServer )->SendMessage( pSession, &closeRequest ); - - CErrorMessage error( PS_OK, SS_UNCONNECTED ); - pSession->Update( ( uint )NMT_ERROR, &error ); - } - else - { - CSrvHandshakeResponseMessage handshakeResponse; - handshakeResponse.m_UseProtocolVersion = PS_PROTOCOL_VERSION; - handshakeResponse.m_Message = pServer->m_WelcomeMessage; - handshakeResponse.m_Flags = 0; - ( ( CNetHost* )pServer )->SendMessage( pSession, &handshakeResponse ); - } - } - - return true; -} - -//----------------------------------------------------------------------------- -// Name: OnAuthenticate() -// Desc: -//----------------------------------------------------------------------------- -bool CNetServer::OnAuthenticate( void* pContext, CFsmEvent* pEvent ) -{ - // Validate parameters - if ( !pContext || !pEvent ) return false; - - // Authenticate event? - if ( pEvent->GetType() != NMT_AUTHENTICATE ) return true; - - CNetServer* pServer = ( CNetServer* )( ( FsmActionCtx* )pContext )->pHost; - CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession; - - CAuthenticateMessage* pMessage = ( CAuthenticateMessage* )pEvent->GetParamRef(); - if ( pMessage ) - { - if ( pMessage->m_Password == pServer->m_PlayerPassword ) - { - LOG( CLogger::Normal, LOG_CAT_NET, "Player authentication successful"); - - pSession->SetName( pMessage->m_Name ); - pSession->SetID( pServer->GetFreeSessionID() ); - - // Store new session - //pServer->m_IDSessions[ pSession->GetID() ] = pSession; - - CAuthenticateResultMessage authenticateResult; - authenticateResult.m_Code = ARC_OK; - authenticateResult.m_SessionID = pSession->GetID(); - authenticateResult.m_Message = L"Logged in"; - ( ( CNetHost* )pServer )->SendMessage( pSession, &authenticateResult ); - - //pServer->AddSession( pSession ); - - //if ( pServer->GetServerState() == NSS_PreGame ) - //{ - pServer->SetupPlayer( pSession ); - //} - //else - //{ - // Chatter / observer - // SetupObserver( pSession ); - //} - - pServer->OnPlayerJoin( pSession ); - } - else - { - LOG( CLogger::Warning, LOG_CAT_NET, "Player authentication failed" ); - - CAuthenticateResultMessage authenticateResult; - authenticateResult.m_Code = ARC_PASSWORD_INVALID; - authenticateResult.m_SessionID = 0; - authenticateResult.m_Message = L"Invalid Password"; - ( ( CNetHost* )pServer )->SendMessage( pSession, &authenticateResult ); - } - } - - return true; -} - -//----------------------------------------------------------------------------- -// Name: OnPreGame() -// Desc: -//----------------------------------------------------------------------------- -bool CNetServer::OnPreGame( void* pContext, CFsmEvent* pEvent ) -{ - // Validate parameters - if ( !pContext || !pEvent ) return false; - - return CNetServer::OnChat( pContext, pEvent ); -} - -//----------------------------------------------------------------------------- -// Name: OnInGame() -// Desc: -//----------------------------------------------------------------------------- -bool CNetServer::OnInGame( void* pContext, CFsmEvent* pEvent ) -{ - // Validate parameters - if ( !pContext || !pEvent ) return false; - - CNetServer* pServer = ( CNetServer* )( ( FsmActionCtx* )pContext )->pHost; - CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession; - - CNetMessage* pMessage = ( CNetMessage* )pEvent->GetParamRef(); - if ( pMessage ) - { - if ( pMessage->GetType() >= NMT_COMMAND_FIRST && pMessage->GetType() < NMT_COMMAND_LAST ) - { - //pSession->m_pPlayer->ValidateCommand(pMsg); - pServer->QueueIncomingCommand( pMessage ); - - return true; - } - - if ( pMessage->GetType() == NMT_END_COMMAND_BATCH ) - { - // TODO Update client timing information and recalculate turn length - pSession->SetReadyForTurn( true ); - } - } - - return true; -} - -//----------------------------------------------------------------------------- -// Name: OnChat() -// Desc: -//----------------------------------------------------------------------------- -bool CNetServer::OnChat( void* pContext, CFsmEvent* pEvent ) -{ - // Validate parameters - if ( !pContext || !pEvent ) return false; - - // Chatting? - if ( pEvent->GetType() != NMT_CHAT ) return true; - - CNetServer* pServer = ( CNetServer* )( ( FsmActionCtx* )pContext )->pHost; - CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession; - - assert( pSession ); - assert( pServer ); - - CChatMessage* pMessage = ( CChatMessage* )pEvent->GetParamRef(); - if ( pMessage ) - { - pMessage->m_Sender = pSession->GetName(); - - g_Console->ReceivedChatMessage( pSession->GetName(), pMessage->m_Message.c_str() ); - - pServer->OnPlayerChat( pMessage->m_Sender, pMessage->m_Message ); - - ( ( CNetHost*)pServer )->Broadcast( pMessage ); - } - - return true; -} - -//----------------------------------------------------------------------------- -// Name: GetSessionByID() -// Desc: Retrieve the session by its ID -//----------------------------------------------------------------------------- -CNetSession* CNetServer::GetSessionByID( uint sessionID ) -{ - // Lookup session - IDSessionMap::const_iterator it = m_IDSessions.find( sessionID ); - if ( it == m_IDSessions.end() ) return NULL; - - return it->second; -} - -//----------------------------------------------------------------------------- -// Name: AddSession() -// Desc: Adds a new session to the list of managed sessions -//----------------------------------------------------------------------------- -/*void CNetServer::AddSession( CNetSession* pSession ) -{ - // Validate parameter - if ( !pSession ) return; - - // Setup new session - //SetupNewSession(); - - // Broadcase a new message informing about the newly connected client - CPlayerJoinMessage playerJoin; - playerJoin.m_Clients.resize( 1 ); - playerJoin.m_Clients[ 0 ].m_SessionID = pSession->GetID(); - playerJoin.m_Clients[ 0 ].m_Name = pSession->GetName(); - - Broadcast( playerJoin ); - - // Store new session - m_IDSessions[ pSession->GetID() ] = pSession; -}*/ - -//----------------------------------------------------------------------------- -// Name: RemoveSession() -// Desc: Removes the specified session from the list of sessions -//----------------------------------------------------------------------------- -/*CNetSession* CNetServer::RemoveSession( CNetSession* pSession ) -{ - CPlayer* pPlayer = NULL; - CPlayerSlot* pPlayerSlot = NULL; - CPlayerLeaveMessage playerLeave; - uint sessionID; - - // Validate parameters - if ( !pSession ) return; - - pPlayer = pSession->GetPlayer(); - pPlayerSlot = pSession->GetPlayerSlot(); - sessionID = pSession->GetID(); - - switch ( m_State ) - { - case SERVER_STATE_PREGAME: - - // Delete player's slot and sync client disconnection - if ( pPlayerSlot ) pPlayerSlot->AssignClosed(); - - break; - - case SERVER_STATE_INGAME: - - // Revert player entities to Gaia control and - // wait for client reconnection - // TODO Reassign entities to Gaia control - // TODO Set everything up for re-connect and resume - if ( pPlayerSlot ) - { - SetClientPipe( pPlayerSlot->GetSlotID(), NULL ); - pPlayerSlot->AssignClosed(); - } - - break; - - case SERVER_STATE_POSTGAME: - - // Synchronize disconnection - - break; - } - - // Inform other clients about client disconnection - playerLeave.m_SessionID = sessionID; - - Broadcast( playerLeave ); - - // Free session slot from the list for later reuse - m_IDSessions[ sessionID ] = NULL; - - // TODO Handle observers -}*/ - -//----------------------------------------------------------------------------- -// Name: RemoveAllSessions() -// Desc: Removes all sessions from the list -//----------------------------------------------------------------------------- -/*void CNetServer::RemoveAllSessions( void ) -{ - SessionList::iterator it = m_Sessions.begin(); - for ( ; it != m_Sessions.end(); it++ ) - { - CNetSession* pCurrSession = it->second; - if ( !pCurrSession ) continue; - - RemoveSession( pCurrSession ); - } - - m_Sessions.clear(); -}*/ - -//----------------------------------------------------------------------------- -// Name: SetPlayerPassword() -// Desc: Sets player new password -//----------------------------------------------------------------------------- -void CNetServer::SetPlayerPassword( const CStr& password ) -{ - m_PlayerPassword = password; -} - -//----------------------------------------------------------------------------- -// Name: StartGame() -// Desc: -//----------------------------------------------------------------------------- -int CNetServer::StartGame( void ) -{ - uint i; - - assert( m_Game ); - assert( m_GameAttributes ); - - if ( m_Game->StartGame( m_GameAttributes ) != PSRETURN_OK ) return -1; - - CTurnManager::Initialize( m_GameAttributes->GetSlotCount() ); - - for ( i = 0; i < m_GameAttributes->GetSlotCount(); i++ ) - { - CPlayerSlot* pCurrSlot = m_GameAttributes->GetSlot( i ); - if ( !pCurrSlot ) continue; - - if ( pCurrSlot->GetAssignment() == SLOT_SESSION ) - CTurnManager::SetClientPipe( i, pCurrSlot->GetSession() ); - } - - m_State = SERVER_STATE_INGAME; - - for ( i = 0; i < GetSessionCount(); i++ ) - { - CNetSession* pCurrSession = GetSession( i ); - if ( !pCurrSession ) continue; - - pCurrSession->StartGame(); - } - - CGameStartMessage gameStart; - Broadcast( &gameStart ); - - // This is the signal for everyone to start their simulations. - //SendBatch( 1 ); - - return 0; -} - -//----------------------------------------------------------------------------- -// Name: BuildGameSetupMessage() -// Desc: Loads the game properties into the specified message -//----------------------------------------------------------------------------- -void CNetServer::BuildGameSetupMessage( CGameSetupMessage* pMessage ) -{ - // Validate parameters - if ( !pMessage ) return; - - assert( m_GameAttributes ); - - // Iterate through game properties and load them into message - m_GameAttributes->IterateSynchedProperties( GameSetupMessageCallback, pMessage ); -} - -//----------------------------------------------------------------------------- -// Name: GameSetupMessageCallback() -// Desc: Callback called for each game attribute -//----------------------------------------------------------------------------- -void CNetServer::GameSetupMessageCallback( - const CStrW& name, - ISynchedJSProperty* pProperty, - void *pData ) -{ - // Validate parameters - if ( !pProperty || !pData ) return; - - CGameSetupMessage* pMessage = ( CGameSetupMessage* )pData; - - // Add new property to list - size_t valueCount = pMessage->m_Values.size(); - pMessage->m_Values.resize( valueCount + 1 ); - - // Store property into list - pMessage->m_Values[ valueCount ].m_Name = name; - pMessage->m_Values[ valueCount ].m_Value = pProperty->ToString(); -} - -//----------------------------------------------------------------------------- -// Name: BuildPlayerConfigMessage() -// Desc: Loads the player properties into the specified message -//----------------------------------------------------------------------------- -void CNetServer::BuildPlayerConfigMessage( - CPlayerConfigMessage* pMessage, - CPlayer* pPlayer ) -{ - // Validare parameters - if ( !pMessage || !pPlayer ) return; - - pMessage->m_PlayerID = (u32)pPlayer->GetPlayerID(); - - // Iterate through player properties and load them into message - pPlayer->IterateSynchedProperties( PlayerConfigMessageCallback, pMessage ); -} - -//----------------------------------------------------------------------------- -// Name: PlayerConfigMessageCallback() -// Desc: Callback called for each property of the player -//----------------------------------------------------------------------------- -void CNetServer::PlayerConfigMessageCallback( - const CStrW& name, - ISynchedJSProperty* pProperty, - void* pData ) -{ - // Validate parameters - if ( !pProperty || !pData ) return; - - CPlayerConfigMessage* pMessage = ( CPlayerConfigMessage* )pData; - - // Add new property to list - size_t valueCount = pMessage->m_Values.size(); - pMessage->m_Values.resize( valueCount + 1 ); - - // Store property into the list - pMessage->m_Values[ valueCount ].m_Name = name; - pMessage->m_Values[ valueCount ].m_Value = pProperty->ToString(); -} - -//----------------------------------------------------------------------------- -// Name: GetFreeSessionID() -// Desc: -//----------------------------------------------------------------------------- -uint CNetServer::GetFreeSessionID( void ) const -{ - // No need to be conservative with session IDs; just use a global counter. - static uint lastSessionID = CLIENT_MIN_SESSIONID; - return lastSessionID++; - - /* - // Loop through the list of sessions and return the first - // ID for which the associated session is NULL. If no such - // free slot is found, return a new session ID which is higher - // than the last session ID from the list. - IDSessionMap::const_iterator it = m_IDSessions.begin(); - for ( ; it != m_IDSessions.end(); it++ ) - { - CNetSession* pCurrSession = it->second; - if ( !pCurrSession ) return it->first; - - sessionID++; - } - - return sessionID; - */ -} - -//----------------------------------------------------------------------------- -// Name: AttributeUpdate() -// Desc: -//----------------------------------------------------------------------------- -void CNetServer::AttributeUpdate( - const CStrW& name, - const CStrW& newValue, - void* pData ) -{ - // Validate parameters - if ( !pData ) return; - - CNetServer* pServer = ( CNetServer* )pData; - - g_Console->InsertMessage( L"AttributeUpdate: %ls = \"%ls\"", name.c_str(), newValue.c_str() ); - - CGameSetupMessage gameSetup; - gameSetup.m_Values.resize( 1 ); - gameSetup.m_Values[ 0 ].m_Name = name; - gameSetup.m_Values[ 0 ].m_Value = newValue; - pServer->Broadcast( &gameSetup ); -} - -//----------------------------------------------------------------------------- -// Name: PlayerAttributeUpdate() -// Desc: -//----------------------------------------------------------------------------- -void CNetServer::PlayerAttributeUpdate( - const CStrW& name, - const CStrW& newValue, - CPlayer* pPlayer, - void* pData ) -{ - // Validate parameters - if ( !pData ) return; - - CNetServer* pServer = ( CNetServer* )pData; - - g_Console->InsertMessage( L"PlayerAttributeUpdate(%d): %ls = \"%ls\"", pPlayer->GetPlayerID(), name.c_str(), newValue.c_str() ); - - CPlayerConfigMessage* pNewMessage = new CPlayerConfigMessage; - if ( !pNewMessage ) return; - - pNewMessage->m_PlayerID = (u32)pPlayer->GetPlayerID(); - pNewMessage->m_Values.resize( 1 ); - pNewMessage->m_Values[ 0 ].m_Name = name; - pNewMessage->m_Values[ 0 ].m_Value = newValue; - - pServer->Broadcast( pNewMessage ); -} - -//----------------------------------------------------------------------------- -// Name: PlayerSlotAssignment() -// Desc: -//----------------------------------------------------------------------------- -void CNetServer::PlayerSlotAssignment( - void* pData, - CPlayerSlot* pPlayerSlot ) -{ - // Validate parameters - if ( !pData || !pPlayerSlot ) return; - - CNetServer* pServer = ( CNetServer* )pData; - - if ( pPlayerSlot->GetAssignment() == SLOT_SESSION ) - pPlayerSlot->GetSession()->SetPlayerSlot( pPlayerSlot ); - - CAssignPlayerSlotMessage assignPlayerSlot; - - pServer->BuildPlayerSlotAssignmentMessage( &assignPlayerSlot, pPlayerSlot ); - - g_Console->InsertMessage( L"Player Slot Assignment: %hs\n", assignPlayerSlot.ToString().c_str() ); - - pServer->Broadcast( &assignPlayerSlot ); -} - -//----------------------------------------------------------------------------- -// Name: AllowObserver() -// Desc: -//----------------------------------------------------------------------------- -bool CNetServer::AllowObserver( CNetSession* UNUSED( pSession ) ) -{ - return m_Observers.size() < m_MaxObservers; -} - -//----------------------------------------------------------------------------- -// Name: NewTurnReady() -// Desc: -//----------------------------------------------------------------------------- -bool CNetServer::NewTurnReady() -{ - // Check whether all sessions are ready for the next turn - for ( uint i = 0; i < GetSessionCount(); i++ ) - { - CNetSession* pCurrSession = GetSession( i ); - if ( !pCurrSession ) continue; - - if ( !pCurrSession->IsReadyForTurn() ) - return false; - } - - return true; -} - -//----------------------------------------------------------------------------- -// Name: NewTurn() -// Desc: -//----------------------------------------------------------------------------- -void CNetServer::NewTurn() -{ - CScopeLock lock(m_Mutex); - - // Reset session ready for next turn flag - for ( uint i = 0; i < GetSessionCount(); i++ ) - { - CNetSession* pCurrSession = GetSession( i ); - if ( !pCurrSession ) continue; - - pCurrSession->SetReadyForTurn( false ); - } - - RecordBatch( 2 ); - RotateBatches(); - ClearBatch( 2 ); - IterateBatch( 1, CSimulation::GetMessageMask, m_Game->GetSimulation() ); - SendBatch( 1 ); - //IterateBatch( 1, SendToObservers, this ); -} - -//----------------------------------------------------------------------------- -// Name: QueueLocalCommand() -// Desc: -//----------------------------------------------------------------------------- -void CNetServer::QueueLocalCommand( CNetMessage *pMessage ) -{ - QueueIncomingCommand( pMessage ); -} - -//----------------------------------------------------------------------------- -// Name: QueueIncomingCommand() -// Desc: -//----------------------------------------------------------------------------- -void CNetServer::QueueIncomingCommand( CNetMessage* pMessage ) -{ - // Validate parameters - if ( !pMessage ) return; - - //LOG( NORMAL, LOG_CAT_NET, "CNetServer::QueueIncomingCommand(): %s.", pMessage->ToString().c_str() ); - - QueueMessage( 2, pMessage ); -} - -//----------------------------------------------------------------------------- -// Name: OnChat() -// Desc: -//----------------------------------------------------------------------------- -void CNetServer::OnPlayerChat( const CStrW& from, const CStrW& message ) -{ - if ( m_OnChat.Defined() ) - { - CChatEvent event( from, message ); - m_OnChat.DispatchEvent( GetScript(), &event ); - } -} - -//----------------------------------------------------------------------------- -// Name: BuildPlayerSlotAssignementMessage() -// Desc: -//----------------------------------------------------------------------------- -void CNetServer::BuildPlayerSlotAssignmentMessage( - CAssignPlayerSlotMessage* pMessage, - CPlayerSlot* pPlayerSlot ) -{ - // Validate parameters - if ( !pMessage || !pPlayerSlot ) return; - - pMessage->m_SlotID = (u32)pPlayerSlot->GetSlotID(); - pMessage->m_SessionID = pPlayerSlot->GetSessionID(); - - switch ( pPlayerSlot->GetAssignment() ) - { - case SLOT_CLOSED: - pMessage->m_Assignment = ASSIGN_CLOSED; - break; - - case SLOT_OPEN: - pMessage->m_Assignment = ASSIGN_OPEN; - break; - - case SLOT_SESSION: - pMessage->m_Assignment = ASSIGN_SESSION; - break; - } -} - - +/** + *----------------------------------------------------------------------------- + * FILE : NetServer.cpp + * PROJECT : 0 A.D. + * DESCRIPTION : Network server class implementation + *----------------------------------------------------------------------------- + */ + +// INCLUDES +#include "precompiled.h" +#include "ps/CLogger.h" +#include "ps/CConsole.h" +#include "simulation/Simulation.h" +#include "NetJsEvents.h" +#include "NetSession.h" +#include "NetServer.h" + +// DECLARATIONS + +CNetServer* g_NetServer = NULL; + +//----------------------------------------------------------------------------- +// Name: CNetServer() +// Desc: Constructor +//----------------------------------------------------------------------------- +CNetServer::CNetServer( CGame *pGame, CGameAttributes *pGameAttributes ) +: m_JsSessions( &m_IDSessions ) +{ + //ONCE( ScriptingInit(); ); + + m_Game = pGame; + m_GameAttributes = pGameAttributes; + m_MaxObservers = MAX_OBSERVERS; + //m_LastSessionID = 1; + m_Port = DEFAULT_HOST_PORT; + m_Name = DEFAULT_SERVER_NAME; + m_PlayerName = DEFAULT_PLAYER_NAME; + m_WelcomeMessage = DEFAULT_WELCOME_MESSAGE; + + // Set callbacks + m_GameAttributes->SetUpdateCallback( AttributeUpdate, this ); + m_GameAttributes->SetPlayerUpdateCallback( PlayerAttributeUpdate, this ); + m_GameAttributes->SetPlayerSlotAssignmentCallback( PlayerSlotAssignment, this ); + + // Set turn manager + CSimulation* pSimulation = m_Game->GetSimulation(); + if ( pSimulation ) pSimulation->SetTurnManager( this ); + + // Set an incredibly long turn length for debugging + // (e.g. less command batch spam that way) + for ( uint i = 0; i < 3; i++ ) + { + CTurnManager::SetTurnLength( i, DEFAULT_TURN_LENGTH ); + } + + g_ScriptingHost.SetGlobal( "g_NetServer", OBJECT_TO_JSVAL( GetScript() ) ); +} + +//----------------------------------------------------------------------------- +// Name: ~CNetServer() +// Desc: Destructor +//----------------------------------------------------------------------------- +CNetServer::~CNetServer() +{ + m_IDSessions.clear(); + + g_ScriptingHost.SetGlobal( "g_NetServer", JSVAL_NULL ); +} + +//----------------------------------------------------------------------------- +// Name: ScriptingInit() +// Desc: +//----------------------------------------------------------------------------- +void CNetServer::ScriptingInit( void ) +{ + CJSMap< IDSessionMap >::ScriptingInit( "NetServer_SessionMap" ); + + AddMethod< bool, &CNetServer::Start >( "open", 0 ); + + AddProperty( L"sessions", &CNetServer::m_JsSessions ); + AddProperty( L"serverPlayerName", &CNetServer::m_PlayerName ); + AddProperty( L"serverName", &CNetServer::m_Name ); + AddProperty( L"welcomeMessage", &CNetServer::m_WelcomeMessage ); + AddProperty( L"port", &CNetServer::m_Port ); + AddProperty( L"onChat", &CNetServer::m_OnChat ); + AddProperty( L"onClientConnect", &CNetServer::m_OnClientConnect ); + AddProperty( L"onClientDisconnect", &CNetServer::m_OnClientDisconnect ); + + CJSObject< CNetServer >::ScriptingInit( "NetServer" ); + + //CGameAttributes::ScriptingInit(); +} + +//----------------------------------------------------------------------------- +// Name: Start() +// Desc: Initialize the server +//----------------------------------------------------------------------------- +bool CNetServer::Start( JSContext* UNUSED(pContext), uintN UNUSED(argc), jsval* UNUSED(argv) ) +{ + // Setup initial state + m_State = SERVER_STATE_PREGAME; + + // Create new session + if ( !Create( m_Port, MAX_CLIENTS ) ) + { + LOG( CLogger::Error, LOG_CAT_NET, "CNetServer::Run(): Initialize failed" ); + + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: SetupSession() +// Desc: Setup new session +//----------------------------------------------------------------------------- +bool CNetServer::SetupSession( CNetSession* pSession ) +{ + // Validate parameters + if ( !pSession ) return false; + + FsmActionCtx* pContext = new FsmActionCtx; + if ( !pContext ) return false; + + pContext->pHost = this; + pContext->pSession = pSession; + + // Setup transitions for session + pSession->AddTransition( NSS_HANDSHAKE, ( uint )NMT_ERROR, NSS_HANDSHAKE, (void*)&OnError, pContext ); + pSession->AddTransition( NSS_HANDSHAKE, ( uint )NMT_CLIENT_HANDSHAKE, NSS_AUTHENTICATE, (void*)&OnHandshake, pContext ); + pSession->AddTransition( NSS_AUTHENTICATE, ( uint )NMT_ERROR, NSS_AUTHENTICATE, (void*)&OnError, pContext ); + pSession->AddTransition( NSS_AUTHENTICATE, ( uint )NMT_AUTHENTICATE, NSS_PREGAME, (void*)&OnAuthenticate, pContext ); + pSession->AddTransition( NSS_AUTHENTICATE, ( uint )NMT_APP_PREGAME, NSS_PREGAME, (void*)&OnPreGame, pContext ); + pSession->AddTransition( NSS_AUTHENTICATE, ( uint )NMT_APP_OBSERVER, NSS_INGAME, (void*)&OnChat, pContext ); + pSession->AddTransition( NSS_PREGAME, ( uint )NMT_END_COMMAND_BATCH, NSS_INGAME, (void*)&OnInGame, pContext ); + pSession->AddTransition( NSS_PREGAME, ( uint )NMT_CHAT, NSS_INGAME, (void*)&OnInGame, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_ERROR, NSS_INGAME, (void*)&OnError, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_CHAT, NSS_INGAME, (void*)&OnChat, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_GOTO, NSS_INGAME, (void*)&OnInGame, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_PATROL, NSS_INGAME, (void*)&OnInGame, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_ADD_WAYPOINT, NSS_INGAME, (void*)&OnInGame, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_CONTACT_ACTION, NSS_INGAME, (void*)&OnInGame, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_PRODUCE, NSS_INGAME, (void*)&OnInGame, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_PLACE_OBJECT, NSS_INGAME, (void*)&OnInGame, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_RUN, NSS_INGAME, (void*)&OnInGame, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_SET_RALLY_POINT, NSS_INGAME, (void*)&OnInGame, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_SET_STANCE, NSS_INGAME, (void*)&OnInGame, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_NOTIFY_REQUEST, NSS_INGAME, (void*)&OnInGame, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_FORMATION_GOTO, NSS_INGAME, (void*)&OnInGame, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_FORMATION_CONTACT_ACTION, NSS_INGAME, (void*)&OnInGame, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_END_COMMAND_BATCH, NSS_INGAME, (void*)&OnInGame, pContext ); + + // Set first state + pSession->SetFirstState( NSS_HANDSHAKE ); + + return true; +} + +//----------------------------------------------------------------------------- +// Name: HandleConnect() +// Desc: Called when a new player joins the current game +//----------------------------------------------------------------------------- +bool CNetServer::HandleConnect( CNetSession* pSession ) +{ + // Validate parameters + if ( !pSession ) return false; + + // Player joined the game, start authentication + CSrvHandshakeMessage handshake; + handshake.m_Magic = PS_PROTOCOL_MAGIC; + handshake.m_ProtocolVersion = PS_PROTOCOL_VERSION; + handshake.m_SoftwareVersion = PS_PROTOCOL_VERSION; + CNetHost::SendMessage( pSession, &handshake ); + + // Store new session into the map + m_IDSessions[ pSession->GetID() ] = pSession; + + //OnPlayerJoin( pSession ); + + return true; +} + +//----------------------------------------------------------------------------- +// Name: HandleDisconnect() +// Desc: Called when a client has disconnected +//----------------------------------------------------------------------------- +bool CNetServer::HandleDisconnect( CNetSession* pSession ) +{ + // Validate parameters + if ( !pSession ) return false; + + OnPlayerLeave( pSession ); + + // Free session slot so it can be recycled + m_IDSessions[ pSession->GetID() ] = NULL; + + return true; +} + +//----------------------------------------------------------------------------- +// Name: SetupPlayer() +// Desc: Sends the necessary messages for the player setup +//----------------------------------------------------------------------------- +void CNetServer::SetupPlayer( CNetSession* pSession ) +{ + CGameSetupMessage gameSetup; + CPlayerConfigMessage playerConfig; + CAssignPlayerSlotMessage assignSlot; + CPlayerJoinMessage playerJoin; + + // Validate parameters + if ( !pSession ) return; + + assert( m_GameAttributes ); + + // Send a new config message to the connected client + BuildGameSetupMessage( &gameSetup ); + CNetHost::SendMessage( pSession, &gameSetup ); + + // Add information for already connected clients and the server + playerJoin.m_Clients.resize( GetSessionCount() ); + playerJoin.m_Clients[ 0 ].m_SessionID = SERVER_SESSIONID; + playerJoin.m_Clients[ 0 ].m_Name = GetPlayerName(); + + for ( uint i = 0; i < GetSessionCount(); i++ ) + { + CNetSession* pCurrSession = GetSession( i ); + + // Skip the player being setup + if ( !pCurrSession || pCurrSession == pSession ) continue; + + playerJoin.m_Clients[ i + 1 ].m_SessionID = pCurrSession->GetID(); + playerJoin.m_Clients[ i + 1 ].m_Name = pCurrSession->GetName(); + } + + CNetHost::SendMessage( pSession, &playerJoin ); + + // TODO: Handle observers + + // Send a message informing about the newly connected player + playerJoin.m_Clients.resize( 1 ); + playerJoin.m_Clients[ 0 ].m_SessionID = pSession->GetID(); + playerJoin.m_Clients[ 0 ].m_Name = pSession->GetName(); + + for ( uint i = 0; i < GetSessionCount(); i++ ) + { + CNetSession* pCurrSession = GetSession( i ); + + // Skip the player being setup + if ( !pCurrSession || pCurrSession == pSession ) continue; + + CNetHost::SendMessage( pCurrSession, &playerJoin ); + } + //Broadcast( &playerJoin ); + + // Sync player slot assignments and player attributes + for ( uint i = 0; i < m_GameAttributes->GetSlotCount(); i++ ) + { + CPlayerSlot* pCurrSlot = m_GameAttributes->GetSlot( i ); + if ( !pCurrSlot ) continue; + + assignSlot.m_SlotID = (u32)pCurrSlot->GetSlotID(); + assignSlot.m_SessionID = (u32)pCurrSlot->GetSessionID(); + switch ( pCurrSlot->GetAssignment() ) + { + case SLOT_CLOSED: + assignSlot.m_Assignment = ASSIGN_CLOSED; + break; + + case SLOT_OPEN: + assignSlot.m_Assignment = ASSIGN_OPEN; + break; + + case SLOT_SESSION: + assignSlot.m_Assignment = ASSIGN_SESSION; + break; + + case SLOT_AI: + break; + } + + CNetHost::SendMessage( pSession, &assignSlot ); + + if ( pCurrSlot->GetAssignment() == SLOT_SESSION ) + { + // Setup player + BuildPlayerConfigMessage( &playerConfig, pCurrSlot->GetPlayer() ); + CNetHost::SendMessage( pSession, &playerConfig ); + } + } +} + +//----------------------------------------------------------------------------- +// Name: OnClientDisconnect() +// Desc: Called when a player joins the game +//----------------------------------------------------------------------------- +void CNetServer::OnPlayerJoin( CNetSession* pSession ) +{ + if ( m_OnClientConnect.Defined() ) + { + // Dispatch a client connect event + CClientConnectEvent event( pSession ); + m_OnClientConnect.DispatchEvent( GetScript(), &event ); + } +} + +//----------------------------------------------------------------------------- +// Name: OnClientDisconnect() +// Desc: Called when a player quits the game +//----------------------------------------------------------------------------- +void CNetServer::OnPlayerLeave( CNetSession* pSession ) +{ + // Validate parameters + if ( !pSession ) return; + + CPlayer* pPlayer = NULL; + CPlayerSlot* pPlayerSlot = NULL; + CPlayerLeaveMessage playerLeave; + + // Validate parameters + if ( !pSession ) return; + + pPlayer = pSession->GetPlayer(); + pPlayerSlot = pSession->GetPlayerSlot(); + + switch ( m_State ) + { + case SERVER_STATE_PREGAME: + + // Delete player's slot and sync client disconnection + if ( pPlayerSlot ) pPlayerSlot->AssignClosed(); + break; + + case SERVER_STATE_INGAME: + + // Revert player entities to Gaia control and + // wait for client reconnection + // TODO Reassign entities to Gaia control + // TODO Set everything up for re-connect and resume + if ( pPlayerSlot ) + { + SetClientPipe( pPlayerSlot->GetSlotID(), NULL ); + pPlayerSlot->AssignClosed(); + } + break; + + case SERVER_STATE_POSTGAME: + + // Synchronize disconnection + break; + } + + // Inform other clients about client disconnection + playerLeave.m_SessionID = pSession->GetID(); + + Broadcast( &playerLeave ); + + // Script object defined? + if ( m_OnClientDisconnect.Defined() ) + { + // Dispatch a client disconnect event + CClientDisconnectEvent event( pSession->GetID(), pSession->GetName() ); + m_OnClientDisconnect.DispatchEvent( GetScript(), &event ); + } +} + +//----------------------------------------------------------------------------- +// Name: OnError() +// Desc: +//----------------------------------------------------------------------------- +bool CNetServer::OnError( void* pContext, CFsmEvent* pEvent ) +{ + // Validate parameters + if ( !pContext || !pEvent ) return false; + + // Error event? + if ( pEvent->GetType() != NMT_ERROR ) return true; + + CNetServer* pServer = ( CNetServer* )( ( FsmActionCtx* )pContext )->pHost; + UNUSED2(pServer); + CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession; + UNUSED2(pSession); + + CErrorMessage* pMessage = ( CErrorMessage* )pEvent->GetParamRef(); + if ( pMessage ) + { + if ( pMessage->m_State == SS_UNCONNECTED ) + { + //if ( pSession->GetID() != -1) + // pServer->RemoveSession( pSession ); + + //delete pSession; + } + else + { + // Weird stuff... + LOG( CLogger::Warning, LOG_CAT_NET, "NMT_ERROR: %s", pMessage->ToString().c_str() ); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: OnHandshake() +// Desc: +//----------------------------------------------------------------------------- +bool CNetServer::OnHandshake( void* pContext, CFsmEvent* pEvent ) +{ + // Validate parameters + if ( !pEvent || !pContext ) return false; + + // Client handshake event? + if ( pEvent->GetType() != NMT_CLIENT_HANDSHAKE ) return true; + + CNetServer* pServer = ( CNetServer* )( ( FsmActionCtx* )pContext )->pHost; + CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession; + + assert( pServer ); + assert( pSession ); + + CCliHandshakeMessage* pMessage = ( CCliHandshakeMessage* )pEvent->GetParamRef(); + if ( pMessage ) + { + if ( pMessage->m_ProtocolVersion != PS_PROTOCOL_VERSION ) + { + CCloseRequestMessage closeRequest; + ( ( CNetHost* )pServer )->SendMessage( pSession, &closeRequest ); + + CErrorMessage error( PS_OK, SS_UNCONNECTED ); + pSession->Update( ( uint )NMT_ERROR, &error ); + } + else + { + CSrvHandshakeResponseMessage handshakeResponse; + handshakeResponse.m_UseProtocolVersion = PS_PROTOCOL_VERSION; + handshakeResponse.m_Message = pServer->m_WelcomeMessage; + handshakeResponse.m_Flags = 0; + ( ( CNetHost* )pServer )->SendMessage( pSession, &handshakeResponse ); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: OnAuthenticate() +// Desc: +//----------------------------------------------------------------------------- +bool CNetServer::OnAuthenticate( void* pContext, CFsmEvent* pEvent ) +{ + // Validate parameters + if ( !pContext || !pEvent ) return false; + + // Authenticate event? + if ( pEvent->GetType() != NMT_AUTHENTICATE ) return true; + + CNetServer* pServer = ( CNetServer* )( ( FsmActionCtx* )pContext )->pHost; + CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession; + + CAuthenticateMessage* pMessage = ( CAuthenticateMessage* )pEvent->GetParamRef(); + if ( pMessage ) + { + if ( pMessage->m_Password == pServer->m_PlayerPassword ) + { + LOG( CLogger::Normal, LOG_CAT_NET, "Player authentication successful"); + + pSession->SetName( pMessage->m_Name ); + pSession->SetID( pServer->GetFreeSessionID() ); + + // Store new session + //pServer->m_IDSessions[ pSession->GetID() ] = pSession; + + CAuthenticateResultMessage authenticateResult; + authenticateResult.m_Code = ARC_OK; + authenticateResult.m_SessionID = pSession->GetID(); + authenticateResult.m_Message = L"Logged in"; + ( ( CNetHost* )pServer )->SendMessage( pSession, &authenticateResult ); + + //pServer->AddSession( pSession ); + + //if ( pServer->GetServerState() == NSS_PreGame ) + //{ + pServer->SetupPlayer( pSession ); + //} + //else + //{ + // Chatter / observer + // SetupObserver( pSession ); + //} + + pServer->OnPlayerJoin( pSession ); + } + else + { + LOG( CLogger::Warning, LOG_CAT_NET, "Player authentication failed" ); + + CAuthenticateResultMessage authenticateResult; + authenticateResult.m_Code = ARC_PASSWORD_INVALID; + authenticateResult.m_SessionID = 0; + authenticateResult.m_Message = L"Invalid Password"; + ( ( CNetHost* )pServer )->SendMessage( pSession, &authenticateResult ); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: OnPreGame() +// Desc: +//----------------------------------------------------------------------------- +bool CNetServer::OnPreGame( void* pContext, CFsmEvent* pEvent ) +{ + // Validate parameters + if ( !pContext || !pEvent ) return false; + + return CNetServer::OnChat( pContext, pEvent ); +} + +//----------------------------------------------------------------------------- +// Name: OnInGame() +// Desc: +//----------------------------------------------------------------------------- +bool CNetServer::OnInGame( void* pContext, CFsmEvent* pEvent ) +{ + // Validate parameters + if ( !pContext || !pEvent ) return false; + + CNetServer* pServer = ( CNetServer* )( ( FsmActionCtx* )pContext )->pHost; + CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession; + + CNetMessage* pMessage = ( CNetMessage* )pEvent->GetParamRef(); + if ( pMessage ) + { + if ( pMessage->GetType() >= NMT_COMMAND_FIRST && pMessage->GetType() < NMT_COMMAND_LAST ) + { + //pSession->m_pPlayer->ValidateCommand(pMsg); + pServer->QueueIncomingCommand( pMessage ); + + return true; + } + + if ( pMessage->GetType() == NMT_END_COMMAND_BATCH ) + { + // TODO Update client timing information and recalculate turn length + pSession->SetReadyForTurn( true ); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: OnChat() +// Desc: +//----------------------------------------------------------------------------- +bool CNetServer::OnChat( void* pContext, CFsmEvent* pEvent ) +{ + // Validate parameters + if ( !pContext || !pEvent ) return false; + + // Chatting? + if ( pEvent->GetType() != NMT_CHAT ) return true; + + CNetServer* pServer = ( CNetServer* )( ( FsmActionCtx* )pContext )->pHost; + CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession; + + assert( pSession ); + assert( pServer ); + + CChatMessage* pMessage = ( CChatMessage* )pEvent->GetParamRef(); + if ( pMessage ) + { + pMessage->m_Sender = pSession->GetName(); + + g_Console->ReceivedChatMessage( pSession->GetName(), pMessage->m_Message.c_str() ); + + pServer->OnPlayerChat( pMessage->m_Sender, pMessage->m_Message ); + + ( ( CNetHost*)pServer )->Broadcast( pMessage ); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: GetSessionByID() +// Desc: Retrieve the session by its ID +//----------------------------------------------------------------------------- +CNetSession* CNetServer::GetSessionByID( uint sessionID ) +{ + // Lookup session + IDSessionMap::const_iterator it = m_IDSessions.find( sessionID ); + if ( it == m_IDSessions.end() ) return NULL; + + return it->second; +} + +//----------------------------------------------------------------------------- +// Name: AddSession() +// Desc: Adds a new session to the list of managed sessions +//----------------------------------------------------------------------------- +/*void CNetServer::AddSession( CNetSession* pSession ) +{ + // Validate parameter + if ( !pSession ) return; + + // Setup new session + //SetupNewSession(); + + // Broadcase a new message informing about the newly connected client + CPlayerJoinMessage playerJoin; + playerJoin.m_Clients.resize( 1 ); + playerJoin.m_Clients[ 0 ].m_SessionID = pSession->GetID(); + playerJoin.m_Clients[ 0 ].m_Name = pSession->GetName(); + + Broadcast( playerJoin ); + + // Store new session + m_IDSessions[ pSession->GetID() ] = pSession; +}*/ + +//----------------------------------------------------------------------------- +// Name: RemoveSession() +// Desc: Removes the specified session from the list of sessions +//----------------------------------------------------------------------------- +/*CNetSession* CNetServer::RemoveSession( CNetSession* pSession ) +{ + CPlayer* pPlayer = NULL; + CPlayerSlot* pPlayerSlot = NULL; + CPlayerLeaveMessage playerLeave; + uint sessionID; + + // Validate parameters + if ( !pSession ) return; + + pPlayer = pSession->GetPlayer(); + pPlayerSlot = pSession->GetPlayerSlot(); + sessionID = pSession->GetID(); + + switch ( m_State ) + { + case SERVER_STATE_PREGAME: + + // Delete player's slot and sync client disconnection + if ( pPlayerSlot ) pPlayerSlot->AssignClosed(); + + break; + + case SERVER_STATE_INGAME: + + // Revert player entities to Gaia control and + // wait for client reconnection + // TODO Reassign entities to Gaia control + // TODO Set everything up for re-connect and resume + if ( pPlayerSlot ) + { + SetClientPipe( pPlayerSlot->GetSlotID(), NULL ); + pPlayerSlot->AssignClosed(); + } + + break; + + case SERVER_STATE_POSTGAME: + + // Synchronize disconnection + + break; + } + + // Inform other clients about client disconnection + playerLeave.m_SessionID = sessionID; + + Broadcast( playerLeave ); + + // Free session slot from the list for later reuse + m_IDSessions[ sessionID ] = NULL; + + // TODO Handle observers +}*/ + +//----------------------------------------------------------------------------- +// Name: RemoveAllSessions() +// Desc: Removes all sessions from the list +//----------------------------------------------------------------------------- +/*void CNetServer::RemoveAllSessions( void ) +{ + SessionList::iterator it = m_Sessions.begin(); + for ( ; it != m_Sessions.end(); it++ ) + { + CNetSession* pCurrSession = it->second; + if ( !pCurrSession ) continue; + + RemoveSession( pCurrSession ); + } + + m_Sessions.clear(); +}*/ + +//----------------------------------------------------------------------------- +// Name: SetPlayerPassword() +// Desc: Sets player new password +//----------------------------------------------------------------------------- +void CNetServer::SetPlayerPassword( const CStr& password ) +{ + m_PlayerPassword = password; +} + +//----------------------------------------------------------------------------- +// Name: StartGame() +// Desc: +//----------------------------------------------------------------------------- +int CNetServer::StartGame( void ) +{ + uint i; + + assert( m_Game ); + assert( m_GameAttributes ); + + if ( m_Game->StartGame( m_GameAttributes ) != PSRETURN_OK ) return -1; + + CTurnManager::Initialize( m_GameAttributes->GetSlotCount() ); + + for ( i = 0; i < m_GameAttributes->GetSlotCount(); i++ ) + { + CPlayerSlot* pCurrSlot = m_GameAttributes->GetSlot( i ); + if ( !pCurrSlot ) continue; + + if ( pCurrSlot->GetAssignment() == SLOT_SESSION ) + CTurnManager::SetClientPipe( i, pCurrSlot->GetSession() ); + } + + m_State = SERVER_STATE_INGAME; + + for ( i = 0; i < GetSessionCount(); i++ ) + { + CNetSession* pCurrSession = GetSession( i ); + if ( !pCurrSession ) continue; + + pCurrSession->StartGame(); + } + + CGameStartMessage gameStart; + Broadcast( &gameStart ); + + // This is the signal for everyone to start their simulations. + //SendBatch( 1 ); + + return 0; +} + +//----------------------------------------------------------------------------- +// Name: BuildGameSetupMessage() +// Desc: Loads the game properties into the specified message +//----------------------------------------------------------------------------- +void CNetServer::BuildGameSetupMessage( CGameSetupMessage* pMessage ) +{ + // Validate parameters + if ( !pMessage ) return; + + assert( m_GameAttributes ); + + // Iterate through game properties and load them into message + m_GameAttributes->IterateSynchedProperties( GameSetupMessageCallback, pMessage ); +} + +//----------------------------------------------------------------------------- +// Name: GameSetupMessageCallback() +// Desc: Callback called for each game attribute +//----------------------------------------------------------------------------- +void CNetServer::GameSetupMessageCallback( + const CStrW& name, + ISynchedJSProperty* pProperty, + void *pData ) +{ + // Validate parameters + if ( !pProperty || !pData ) return; + + CGameSetupMessage* pMessage = ( CGameSetupMessage* )pData; + + // Add new property to list + size_t valueCount = pMessage->m_Values.size(); + pMessage->m_Values.resize( valueCount + 1 ); + + // Store property into list + pMessage->m_Values[ valueCount ].m_Name = name; + pMessage->m_Values[ valueCount ].m_Value = pProperty->ToString(); +} + +//----------------------------------------------------------------------------- +// Name: BuildPlayerConfigMessage() +// Desc: Loads the player properties into the specified message +//----------------------------------------------------------------------------- +void CNetServer::BuildPlayerConfigMessage( + CPlayerConfigMessage* pMessage, + CPlayer* pPlayer ) +{ + // Validare parameters + if ( !pMessage || !pPlayer ) return; + + pMessage->m_PlayerID = (u32)pPlayer->GetPlayerID(); + + // Iterate through player properties and load them into message + pPlayer->IterateSynchedProperties( PlayerConfigMessageCallback, pMessage ); +} + +//----------------------------------------------------------------------------- +// Name: PlayerConfigMessageCallback() +// Desc: Callback called for each property of the player +//----------------------------------------------------------------------------- +void CNetServer::PlayerConfigMessageCallback( + const CStrW& name, + ISynchedJSProperty* pProperty, + void* pData ) +{ + // Validate parameters + if ( !pProperty || !pData ) return; + + CPlayerConfigMessage* pMessage = ( CPlayerConfigMessage* )pData; + + // Add new property to list + size_t valueCount = pMessage->m_Values.size(); + pMessage->m_Values.resize( valueCount + 1 ); + + // Store property into the list + pMessage->m_Values[ valueCount ].m_Name = name; + pMessage->m_Values[ valueCount ].m_Value = pProperty->ToString(); +} + +//----------------------------------------------------------------------------- +// Name: GetFreeSessionID() +// Desc: +//----------------------------------------------------------------------------- +uint CNetServer::GetFreeSessionID( void ) const +{ + // No need to be conservative with session IDs; just use a global counter. + static uint lastSessionID = CLIENT_MIN_SESSIONID; + return lastSessionID++; + + /* + // Loop through the list of sessions and return the first + // ID for which the associated session is NULL. If no such + // free slot is found, return a new session ID which is higher + // than the last session ID from the list. + IDSessionMap::const_iterator it = m_IDSessions.begin(); + for ( ; it != m_IDSessions.end(); it++ ) + { + CNetSession* pCurrSession = it->second; + if ( !pCurrSession ) return it->first; + + sessionID++; + } + + return sessionID; + */ +} + +//----------------------------------------------------------------------------- +// Name: AttributeUpdate() +// Desc: +//----------------------------------------------------------------------------- +void CNetServer::AttributeUpdate( + const CStrW& name, + const CStrW& newValue, + void* pData ) +{ + // Validate parameters + if ( !pData ) return; + + CNetServer* pServer = ( CNetServer* )pData; + + g_Console->InsertMessage( L"AttributeUpdate: %ls = \"%ls\"", name.c_str(), newValue.c_str() ); + + CGameSetupMessage gameSetup; + gameSetup.m_Values.resize( 1 ); + gameSetup.m_Values[ 0 ].m_Name = name; + gameSetup.m_Values[ 0 ].m_Value = newValue; + pServer->Broadcast( &gameSetup ); +} + +//----------------------------------------------------------------------------- +// Name: PlayerAttributeUpdate() +// Desc: +//----------------------------------------------------------------------------- +void CNetServer::PlayerAttributeUpdate( + const CStrW& name, + const CStrW& newValue, + CPlayer* pPlayer, + void* pData ) +{ + // Validate parameters + if ( !pData ) return; + + CNetServer* pServer = ( CNetServer* )pData; + + g_Console->InsertMessage( L"PlayerAttributeUpdate(%d): %ls = \"%ls\"", pPlayer->GetPlayerID(), name.c_str(), newValue.c_str() ); + + CPlayerConfigMessage* pNewMessage = new CPlayerConfigMessage; + if ( !pNewMessage ) return; + + pNewMessage->m_PlayerID = (u32)pPlayer->GetPlayerID(); + pNewMessage->m_Values.resize( 1 ); + pNewMessage->m_Values[ 0 ].m_Name = name; + pNewMessage->m_Values[ 0 ].m_Value = newValue; + + pServer->Broadcast( pNewMessage ); +} + +//----------------------------------------------------------------------------- +// Name: PlayerSlotAssignment() +// Desc: +//----------------------------------------------------------------------------- +void CNetServer::PlayerSlotAssignment( + void* pData, + CPlayerSlot* pPlayerSlot ) +{ + // Validate parameters + if ( !pData || !pPlayerSlot ) return; + + CNetServer* pServer = ( CNetServer* )pData; + + if ( pPlayerSlot->GetAssignment() == SLOT_SESSION ) + pPlayerSlot->GetSession()->SetPlayerSlot( pPlayerSlot ); + + CAssignPlayerSlotMessage assignPlayerSlot; + + pServer->BuildPlayerSlotAssignmentMessage( &assignPlayerSlot, pPlayerSlot ); + + g_Console->InsertMessage( L"Player Slot Assignment: %hs\n", assignPlayerSlot.ToString().c_str() ); + + pServer->Broadcast( &assignPlayerSlot ); +} + +//----------------------------------------------------------------------------- +// Name: AllowObserver() +// Desc: +//----------------------------------------------------------------------------- +bool CNetServer::AllowObserver( CNetSession* UNUSED( pSession ) ) +{ + return m_Observers.size() < m_MaxObservers; +} + +//----------------------------------------------------------------------------- +// Name: NewTurnReady() +// Desc: +//----------------------------------------------------------------------------- +bool CNetServer::NewTurnReady() +{ + // Check whether all sessions are ready for the next turn + for ( uint i = 0; i < GetSessionCount(); i++ ) + { + CNetSession* pCurrSession = GetSession( i ); + if ( !pCurrSession ) continue; + + if ( !pCurrSession->IsReadyForTurn() ) + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: NewTurn() +// Desc: +//----------------------------------------------------------------------------- +void CNetServer::NewTurn() +{ + CScopeLock lock(m_Mutex); + + // Reset session ready for next turn flag + for ( uint i = 0; i < GetSessionCount(); i++ ) + { + CNetSession* pCurrSession = GetSession( i ); + if ( !pCurrSession ) continue; + + pCurrSession->SetReadyForTurn( false ); + } + + RecordBatch( 2 ); + RotateBatches(); + ClearBatch( 2 ); + IterateBatch( 1, CSimulation::GetMessageMask, m_Game->GetSimulation() ); + SendBatch( 1 ); + //IterateBatch( 1, SendToObservers, this ); +} + +//----------------------------------------------------------------------------- +// Name: QueueLocalCommand() +// Desc: +//----------------------------------------------------------------------------- +void CNetServer::QueueLocalCommand( CNetMessage *pMessage ) +{ + QueueIncomingCommand( pMessage ); +} + +//----------------------------------------------------------------------------- +// Name: QueueIncomingCommand() +// Desc: +//----------------------------------------------------------------------------- +void CNetServer::QueueIncomingCommand( CNetMessage* pMessage ) +{ + // Validate parameters + if ( !pMessage ) return; + + //LOG( NORMAL, LOG_CAT_NET, "CNetServer::QueueIncomingCommand(): %s.", pMessage->ToString().c_str() ); + + QueueMessage( 2, pMessage ); +} + +//----------------------------------------------------------------------------- +// Name: OnChat() +// Desc: +//----------------------------------------------------------------------------- +void CNetServer::OnPlayerChat( const CStrW& from, const CStrW& message ) +{ + if ( m_OnChat.Defined() ) + { + CChatEvent event( from, message ); + m_OnChat.DispatchEvent( GetScript(), &event ); + } +} + +//----------------------------------------------------------------------------- +// Name: BuildPlayerSlotAssignementMessage() +// Desc: +//----------------------------------------------------------------------------- +void CNetServer::BuildPlayerSlotAssignmentMessage( + CAssignPlayerSlotMessage* pMessage, + CPlayerSlot* pPlayerSlot ) +{ + // Validate parameters + if ( !pMessage || !pPlayerSlot ) return; + + pMessage->m_SlotID = (u32)pPlayerSlot->GetSlotID(); + pMessage->m_SessionID = pPlayerSlot->GetSessionID(); + + switch ( pPlayerSlot->GetAssignment() ) + { + case SLOT_CLOSED: + pMessage->m_Assignment = ASSIGN_CLOSED; + break; + + case SLOT_OPEN: + pMessage->m_Assignment = ASSIGN_OPEN; + break; + + case SLOT_SESSION: + pMessage->m_Assignment = ASSIGN_SESSION; + break; + } +} + + diff --git a/source/network/NetServer.h b/source/network/NetServer.h index 5667ebea4d..517ce8b6c3 100644 --- a/source/network/NetServer.h +++ b/source/network/NetServer.h @@ -1,345 +1,345 @@ -/** - *----------------------------------------------------------------------------- - * FILE : NetServer.h - * PROJECT : 0 A.D. - * DESCRIPTION : Network server class interface file - *----------------------------------------------------------------------------- - */ - -#ifndef NETSERVER_H -#define NETSERVER_H - -// INCLUDES -#include "Network.h" -#include "NetSession.h" -#include "simulation/TurnManager.h" -#include "scripting/ScriptableObject.h" -#include "ps/GameAttributes.h" -#include "ps/scripting/JSMap.h" -#include "ps/Player.h" -#include "ps/Game.h" -#include "simulation/ScriptObject.h" - -#include -#include - -// DECLARATIONS -#define SERVER_SESSIONID 1 -#define CLIENT_MIN_SESSIONID 100 -#define MAX_CLIENTS 8 -#define MAX_OBSERVERS 5 -#define DEFAULT_SERVER_SESSION_ID 1 -#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 - -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, - - // 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, - - // The one with all the killing ;-) - SERVER_STATE_INGAME, - - // The game is over and someone has won. Players might linger to chat or - // download the replay log. - SERVER_STATE_POSTGAME -}; - -enum -{ - NSS_HANDSHAKE = 1300, - NSS_AUTHENTICATE = 1400, - NSS_PREGAME = 1500, - NSS_INGAME = 1600 -}; - -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, - public CTurnManager -{ -public: - - CNetServer( CGame* pGame, CGameAttributes* pGameAttributes ); - virtual ~CNetServer( void ); - - bool Start ( JSContext *pContext, uintN argc, jsval *argv ); -// void Shutdown ( void ); - - /** - * Returns true indicating the host acts as a server - * - * @return Always true - */ - virtual bool IsServer( void ) const { return true; } - - /** - * Adds a new session to the list of sessions - * - * @param pSession New session to add - */ - void AddSession( CNetSession* pSession ); - - /** - * 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 - */ - CNetSession* RemoveSession( CNetSession* pSession ); - - /** - * Removes all the sessions managed by the network server - * - */ - //void RemoveAllSessions( void ); - - /** - * Returns the number of session the server manages - * - * @return The number of sessions - */ - //uint GetSessionCount( void ) const; - - /** - * 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 - */ - CNetSession* GetSessionByID( uint sessionID ); - -protected: - - virtual bool SetupSession ( CNetSession* pSession ); - virtual bool HandleConnect ( CNetSession* pSession ); - virtual bool HandleDisconnect ( CNetSession *pSession ); - -private: - - // Not implemented - CNetServer( const CNetServer& ); - CNetServer& operator=( const CNetServer& ); - - //void ClientConnect ( ENetPeer* pPeer ); - //void ClientDisconnect ( ENetPeer* pPeer ); - //void ClientReceive ( ENetPeer* pPeer, ENetPacket* pPacket ); - - /** - * Returns the session associated with the specified ENet peer - * - * @param pPeer ENet peer - * @return The session object if found or NULL - */ - //CNetSession* GetSessionByPeer( const ENetPeer* pPeer ); - - /** - * Setup client game by sending the apropiate network messages. It also - * inform the client about the other connected clients as well as player - * slot assignment and attributes. - * - */ - //void SetupNewSession( CNetSession* pSession ); - - /** - * 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 - */ - void BuildPlayerConfigMessage( - CPlayerConfigMessage* pMessage, - CPlayer* pPlayer ); - - /** - * 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 ); - - /** - * 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 ); - - /** - * 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 - */ - // IterateCB GameSetupMessageCallbak; - static void GameSetupMessageCallback( - const CStrW& name, - ISynchedJSProperty *pProperty, - void *pData ); - - /** - * Retrieves a free session ID from the recycled sessions list - * - * @return Free session ID - */ - uint GetFreeSessionID( void ) const; - - IDSessionMap m_IDSessions; // List of connected ID and session pairs - //CScriptObject m_ScriptConnect; // Script client connect dispatch - //CScriptObject m_ScriptDisconnect; // Script client disconnect dispatch - //CScriptObject m_ScriptChat; // Script client chat dispatch - CPlayer* m_Player; // Server player - - -public: - - /** - * - * - * @param addr Address where to bind - * @return PS_OK if bind successfully, error code otherwise - */ - //PS_RESULT Bind( const CSocketAddress& addr ); - 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 ); - - // Add the session. This will be called after the session passes the - // handshake and authentication stages. AssignSessionID should've been called - // on the session prior to calling this method. - //void AddSession( CNetServerSession* pSession ); - - // Remove the session from the server - //void RemoveSession( CNetServerSession* pSession ); - - // Queue a command coming in from the wire. The command has been validated - // by the caller. - void QueueIncomingCommand( CNetMessage* pMessage ); - - // Call the JS callback for incoming events - void OnPlayerChat ( const CStrW& from, const CStrW& message ); - void OnPlayerJoin ( CNetSession* pSession ); - 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 ); - - // OVERRIDES FROM CServerSocket - //virtual void OnAccept( const CSocketAddress& address ); - - // OVERRIDES FROM CTurnManager - virtual void NewTurn( void ); - virtual bool NewTurnReady( void ); - virtual void QueueLocalCommand( CNetMessage* pMessage ); - - // Will only be called from the Network Thread, by the OnAccept handler - //virtual CNetServerSession* CreateSession( CSocketInternal* pSocketInternal); - - // 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 ); - -private: - - CGameAttributes* m_GameAttributes; // Stores game attributes - //int m_LastSessionID; // Stores the last session ID - //SessionMap m_Sessions; // Managed sessions - CJSMap< IDSessionMap > m_JsSessions; - CMutex m_Mutex; // Synchronization object for batches - - /* - 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 - CGame* m_Game; // Pointer to actual game - NetServerState m_State; // Holds server state - CStrW m_Name; // Server name - CStrW m_WelcomeMessage; // Nice welcome message - //CPlayer* m_Player; // Pointer to 'server' player - 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 CGameAttributes::UpdateCallback AttributeUpdate; -// static CPlayer::UpdateCallback PlayerAttributeUpdate; -// static PlayerSlotAssignmentCB PlayerSlotAssignmentCallback; - - 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 ); -}; - -extern CNetServer *g_NetServer; - -#endif // NETSERVER_H - - +/** + *----------------------------------------------------------------------------- + * FILE : NetServer.h + * PROJECT : 0 A.D. + * DESCRIPTION : Network server class interface file + *----------------------------------------------------------------------------- + */ + +#ifndef NETSERVER_H +#define NETSERVER_H + +// INCLUDES +#include "Network.h" +#include "NetSession.h" +#include "simulation/TurnManager.h" +#include "scripting/ScriptableObject.h" +#include "ps/GameAttributes.h" +#include "ps/scripting/JSMap.h" +#include "ps/Player.h" +#include "ps/Game.h" +#include "simulation/ScriptObject.h" + +#include +#include + +// DECLARATIONS +#define SERVER_SESSIONID 1 +#define CLIENT_MIN_SESSIONID 100 +#define MAX_CLIENTS 8 +#define MAX_OBSERVERS 5 +#define DEFAULT_SERVER_SESSION_ID 1 +#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 + +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, + + // 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, + + // The one with all the killing ;-) + SERVER_STATE_INGAME, + + // The game is over and someone has won. Players might linger to chat or + // download the replay log. + SERVER_STATE_POSTGAME +}; + +enum +{ + NSS_HANDSHAKE = 1300, + NSS_AUTHENTICATE = 1400, + NSS_PREGAME = 1500, + NSS_INGAME = 1600 +}; + +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, + public CTurnManager +{ +public: + + CNetServer( CGame* pGame, CGameAttributes* pGameAttributes ); + virtual ~CNetServer( void ); + + bool Start ( JSContext *pContext, uintN argc, jsval *argv ); +// void Shutdown ( void ); + + /** + * Returns true indicating the host acts as a server + * + * @return Always true + */ + virtual bool IsServer( void ) const { return true; } + + /** + * Adds a new session to the list of sessions + * + * @param pSession New session to add + */ + void AddSession( CNetSession* pSession ); + + /** + * 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 + */ + CNetSession* RemoveSession( CNetSession* pSession ); + + /** + * Removes all the sessions managed by the network server + * + */ + //void RemoveAllSessions( void ); + + /** + * Returns the number of session the server manages + * + * @return The number of sessions + */ + //uint GetSessionCount( void ) const; + + /** + * 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 + */ + CNetSession* GetSessionByID( uint sessionID ); + +protected: + + virtual bool SetupSession ( CNetSession* pSession ); + virtual bool HandleConnect ( CNetSession* pSession ); + virtual bool HandleDisconnect ( CNetSession *pSession ); + +private: + + // Not implemented + CNetServer( const CNetServer& ); + CNetServer& operator=( const CNetServer& ); + + //void ClientConnect ( ENetPeer* pPeer ); + //void ClientDisconnect ( ENetPeer* pPeer ); + //void ClientReceive ( ENetPeer* pPeer, ENetPacket* pPacket ); + + /** + * Returns the session associated with the specified ENet peer + * + * @param pPeer ENet peer + * @return The session object if found or NULL + */ + //CNetSession* GetSessionByPeer( const ENetPeer* pPeer ); + + /** + * Setup client game by sending the apropiate network messages. It also + * inform the client about the other connected clients as well as player + * slot assignment and attributes. + * + */ + //void SetupNewSession( CNetSession* pSession ); + + /** + * 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 + */ + void BuildPlayerConfigMessage( + CPlayerConfigMessage* pMessage, + CPlayer* pPlayer ); + + /** + * 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 ); + + /** + * 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 ); + + /** + * 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 + */ + // IterateCB GameSetupMessageCallbak; + static void GameSetupMessageCallback( + const CStrW& name, + ISynchedJSProperty *pProperty, + void *pData ); + + /** + * Retrieves a free session ID from the recycled sessions list + * + * @return Free session ID + */ + uint GetFreeSessionID( void ) const; + + IDSessionMap m_IDSessions; // List of connected ID and session pairs + //CScriptObject m_ScriptConnect; // Script client connect dispatch + //CScriptObject m_ScriptDisconnect; // Script client disconnect dispatch + //CScriptObject m_ScriptChat; // Script client chat dispatch + CPlayer* m_Player; // Server player + + +public: + + /** + * + * + * @param addr Address where to bind + * @return PS_OK if bind successfully, error code otherwise + */ + //PS_RESULT Bind( const CSocketAddress& addr ); + 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 ); + + // Add the session. This will be called after the session passes the + // handshake and authentication stages. AssignSessionID should've been called + // on the session prior to calling this method. + //void AddSession( CNetServerSession* pSession ); + + // Remove the session from the server + //void RemoveSession( CNetServerSession* pSession ); + + // Queue a command coming in from the wire. The command has been validated + // by the caller. + void QueueIncomingCommand( CNetMessage* pMessage ); + + // Call the JS callback for incoming events + void OnPlayerChat ( const CStrW& from, const CStrW& message ); + void OnPlayerJoin ( CNetSession* pSession ); + 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 ); + + // OVERRIDES FROM CServerSocket + //virtual void OnAccept( const CSocketAddress& address ); + + // OVERRIDES FROM CTurnManager + virtual void NewTurn( void ); + virtual bool NewTurnReady( void ); + virtual void QueueLocalCommand( CNetMessage* pMessage ); + + // Will only be called from the Network Thread, by the OnAccept handler + //virtual CNetServerSession* CreateSession( CSocketInternal* pSocketInternal); + + // 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 ); + +private: + + CGameAttributes* m_GameAttributes; // Stores game attributes + //int m_LastSessionID; // Stores the last session ID + //SessionMap m_Sessions; // Managed sessions + CJSMap< IDSessionMap > m_JsSessions; + CMutex m_Mutex; // Synchronization object for batches + + /* + 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 + CGame* m_Game; // Pointer to actual game + NetServerState m_State; // Holds server state + CStrW m_Name; // Server name + CStrW m_WelcomeMessage; // Nice welcome message + //CPlayer* m_Player; // Pointer to 'server' player + 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 CGameAttributes::UpdateCallback AttributeUpdate; +// static CPlayer::UpdateCallback PlayerAttributeUpdate; +// static PlayerSlotAssignmentCB PlayerSlotAssignmentCallback; + + 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 ); +}; + +extern CNetServer *g_NetServer; + +#endif // NETSERVER_H + + diff --git a/source/network/NetSession.cpp b/source/network/NetSession.cpp index a131b6e522..7e672ae4ed 100644 --- a/source/network/NetSession.cpp +++ b/source/network/NetSession.cpp @@ -1,905 +1,905 @@ -/** - *----------------------------------------------------------------------------- - * FILE : NetSession.cpp - * PROJECT : 0 A.D. - * DESCRIPTION : Network session class implementation - *----------------------------------------------------------------------------- - */ - -// INCLUDES -#include "precompiled.h" -//#include "SessionManager.h" -#include "NetSession.h" -//#include "NetServer.h" -#include "NetLog.h" - -// DECLARATIONS - -//----------------------------------------------------------------------------- -// Name: CNetHost() -// Desc: Constructor -//----------------------------------------------------------------------------- -CNetHost::CNetHost( void ) -{ - m_Host = NULL; - m_Buffer = NULL; - m_BufferSize = 0; - -// m_WorkerID = 0; -// m_StopWorker = NULL; -} - -//----------------------------------------------------------------------------- -// Name: ~CNetHost() -// Desc: Destructor -//----------------------------------------------------------------------------- -CNetHost::~CNetHost( void ) -{ - // Release host - if ( m_Host ) enet_host_destroy( m_Host ); - if ( m_Buffer ) delete [] m_Buffer; - - // Release running semaphore -// if ( m_StopWorker ) sem_close( m_StopWorker ); - - m_Host = NULL; - m_Buffer = NULL; - m_BufferSize = 0; -// m_WorkerID = 0; -// m_StopWorker = NULL; -} - -//----------------------------------------------------------------------------- -// Name: Create() -// Desc: Creates a client host -//----------------------------------------------------------------------------- -bool CNetHost::Create( void ) -{ - // Create ENet host - m_Host = enet_host_create( NULL, 1, 0, 0 ); - if ( !m_Host ) return false; - - return true; -} - -//----------------------------------------------------------------------------- -// Name: Create() -// Desc: Creates a server host -//----------------------------------------------------------------------------- -bool CNetHost::Create( uint port, uint maxPeers ) -{ - 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( void ) -{ - // Destroy server - if ( m_Host ) enet_host_destroy( m_Host ); - - // Stop worker thread -// sem_post( m_StopWorker ); -// if ( m_WorkerID ) pthread_join( m_WorkerID, NULL ); - - // 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(); - - m_Host = NULL; -// m_WorkerID = 0; -} - -//----------------------------------------------------------------------------- -// 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, uint port ) -{ - ENetEvent event; - ENetAddress addr; - PeerSession item; - - assert( m_Host ); - - // Bind to specified host - 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 3 seconds for the connection to succeed - if ( enet_host_service( m_Host, &event, 5000 ) > 0 && - event.type == ENET_EVENT_TYPE_CONNECT ) - { - // Connection succeeded - CNetSession* pNewSession = new CNetSession( this, event.peer ); - if ( !pNewSession ) return false; - - if ( !SetupSession( pNewSession ) ) return false; - - NET_LOG3( "Successfully connected to server %s:%d succeeded", host.c_str(), port ); - - // Successfully handled? - if ( !HandleConnect( pNewSession ) ) - { - delete pNewSession; - - return false; - } - - // Store the only server session - item.pPeer = event.peer; - item.pSession = pNewSession; - m_PeerSessions.push_back( item ); - - return true; - } - - NET_LOG3( "Connection to server %s:%d failed", host.c_str(), port ); - - // 3 seconds are up or a host was disconnected - enet_peer_reset( pPeer ); - - return false; -} - -//----------------------------------------------------------------------------- -// Name: Disconnect() -// Desc: Disconnects the specified session from the host -//----------------------------------------------------------------------------- -bool CNetHost::Disconnect( CNetSession* pSession ) -{ - ENetEvent event; - - // Validate parameters - if ( !pSession ) return false; - - assert( m_Host ); - assert( pSession->m_Peer ); - - // Disconnect peer - enet_peer_disconnect( pSession->m_Peer, 0 ); - - // Allow up to 3 seconds for the disconnect to succeed - while ( enet_host_service( m_Host, &event, 5000 ) > 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: Run() -// Desc: -//----------------------------------------------------------------------------- -/*bool CNetHost::Run( void ) -{ - assert( m_Host ); - - // Host created? - if ( !m_Host ) return false; - - // Already running? - if ( m_WorkerID != 0 ) return true; - - // Create run semaphore - m_StopWorker = sem_open( "//WFG_HostWorkerRun", O_CREAT | O_EXCL, 0700, 0 ); - if ( !m_StopWorker ) return false; - - // Create worker thread - if ( pthread_create( &m_WorkerID, 0, &WorkerFunc, this ) < 0 ) return false; - - return true; -} - -//----------------------------------------------------------------------------- -// Name: WorkerFunc() -// Desc: Worker thread function -//----------------------------------------------------------------------------- -void* CNetHost::WorkerFunc( void* pData ) -{ - ENetEvent event; - CNetSession* pSession = NULL; - PeerSession item; - PeerSessionList::iterator it; - - // Validate parameters - if ( !pData ) return NULL; - - CNetHost* pHost = ( CNetHost* )pData; - - // Poll host for events - while ( true ) - { - // Decide whether to stop or not - if ( !sem_timedwait( pHost->m_StopWorker, NULL ) ) break; - - int retval = enet_host_service( pHost->m_Host, &event, 100 ); - - // Any event? - if ( !retval ) continue; - - // Any error? - if ( !retval ) break; - - // Handle occured event - switch( event.type ) - { - case ENET_EVENT_TYPE_CONNECT: - - // A new client has connected, handle it - pSession = new CNetSession( pHost, event.peer ); - if ( !pSession ) return NULL; - - // Successfully handled? - if ( !pHost->HandleConnect( pSession ) ) return NULL; - - // Add new item to internal list - item.pPeer = event.peer; - item.pSession = pSession; - pHost->m_PeerSessions.push_back( item ); - - break; - - case ENET_EVENT_TYPE_DISCONNECT: - - // Client has disconnected, handle it - it = pHost->m_PeerSessions.begin();; - for ( ; it != pHost->m_PeerSessions.end(); it++ ) - { - // Is this our session? - if ( it->pPeer == event.peer ) - { - // Successfully handled? - if ( !pHost->HandleDisconnect( it->pSession ) ) return NULL; - - pHost->m_PeerSessions.erase( it ); - } - } - - break; - - case ENET_EVENT_TYPE_RECEIVE: - - // A new data packet was received from client, handle message - it = pHost->m_PeerSessions.begin(); - for ( ; it != pHost->m_PeerSessions.end(); it++ ) - { - // Is this our session? - if ( it->pPeer == event.peer ) - { - // Create message from raw data - CNetMessage* pNewMessage = CNetMessageFactory::CreateMessage( event.packet->data, event.packet->dataLength ); - if ( !pNewMessage ) return NULL; - - // Successfully handled? - if ( !pHost->HandleMessageReceive( pNewMessage, it->pSession ) ) return NULL; - } - } - - break; - } - } - - return NULL; -}*/ - -//----------------------------------------------------------------------------- -// Name: ProcessEvents() -// Desc: Wait for events and shuttles packets between the host and its peers -//----------------------------------------------------------------------------- -bool CNetHost::Poll( void ) -{ - ENetEvent event; - CNetSession* pSession = NULL; - PeerSession item; - PeerSessionList::iterator it; - - assert( m_Host ); - - // Poll host for events - while ( enet_host_service( m_Host, &event, 0 ) > 0 ) - { - // Handle occured event - switch( event.type ) - { - case ENET_EVENT_TYPE_CONNECT: - - // A new client has connected, handle it - pSession = new CNetSession( this, event.peer ); - if ( !pSession ) return false; - - // Setup new session - if ( !SetupSession( pSession ) ) return false; - - NET_LOG3( "A new client connected from %x:%u", event.peer->address.host, event.peer->address.port ); - - // Successfully handled? - if ( !HandleConnect( pSession ) ) return false; - - event.peer->data = pSession; - - // Add new item to internal list - item.pPeer = event.peer; - item.pSession = pSession; - m_PeerSessions.push_back( item ); - - break; - - case ENET_EVENT_TYPE_DISCONNECT: - - // Client has disconnected, handle it - it = m_PeerSessions.begin();; - for ( ; it != m_PeerSessions.end(); it++ ) - { - // Is this our session? - if ( it->pPeer == event.peer ) - { - NET_LOG2( "%x disconnected", event.peer->data ); - - // Successfully handled? - if ( !HandleDisconnect( it->pSession ) ) return false; - - m_PeerSessions.erase( it ); - - return true; - } - } - - break; - - case ENET_EVENT_TYPE_RECEIVE: - - // A new data packet was received from client, handle message - it = m_PeerSessions.begin(); - for ( ; it != m_PeerSessions.end(); it++ ) - { - // Is this our session? - if ( it->pPeer == event.peer ) - { - // Create message from raw data - CNetMessage* pNewMessage = CNetMessageFactory::CreateMessage( event.packet->data, event.packet->dataLength ); - if ( !pNewMessage ) return false; - - NET_LOG4( "Message %s of size %u was received from %x", pNewMessage->ToString().c_str(), pNewMessage->GetSerializedLength(), event.peer->data ); - - // Successfully handled? - if ( !HandleMessageReceive( pNewMessage, it->pSession ) ) { - enet_packet_destroy( event.packet ); - return false; - } - - // Done using the packet - enet_packet_destroy( event.packet ); - } - } - - break; - } - } - - return true; -} - -//----------------------------------------------------------------------------- -// Name: Broadcast() -// Desc: Broadcast the specified message to connected clients -// Note: Reference counting for a sending message requires multithreading -// locking mechanisms so a clone of the message is made and sent out -//----------------------------------------------------------------------------- -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: ResizeBuffer() -// Desc: Resizes the internal buffer -//----------------------------------------------------------------------------- -void CNetHost::ResizeBuffer( size_t size ) -{ - // Already enough space? - if ( size <= m_BufferSize ) return; - - // Allocate enough space for the new buffer - u8* pBuffer = new u8[ ALIGN_BLOCK( m_BufferSize + size ) ]; - if ( !pBuffer ) return; - - // Any old data? - if ( m_Buffer ) - { - // Copy old data - memcpy( pBuffer, m_Buffer, m_BufferSize ); - - delete [] m_Buffer; - } - - // Store new buffer - m_Buffer = pBuffer; - - // Store new buffer size - m_BufferSize = ALIGN_BLOCK( m_BufferSize + size ); -} - -//----------------------------------------------------------------------------- -// 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; - - assert( pSession->m_Peer ); - assert( m_Host ); - - size_t size = pMessage->GetSerializedLength(); - - // Adjust buffer for message - ResizeBuffer( size ); - - // Save message to internal buffer - pMessage->Serialize( m_Buffer ); - - // Create a reliable packet - ENetPacket* pPacket = enet_packet_create( m_Buffer, size, ENET_PACKET_FLAG_RELIABLE ); - if ( !pPacket ) return false; - - // 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 - NET_LOG( "Failed to send ENet packet to peer" ); - - return false; - } - else - { - NET_LOG4( "Message %s of size %u was sent to %x", - pMessage->ToString().c_str(), pMessage->GetSerializedLength(), pSession->m_Peer->data ); - } - - enet_host_flush( m_Host ); - - 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; - - 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 ); -} - -//----------------------------------------------------------------------------- -// Name: SetupSession() -// Desc: Setup new session upon creation -//----------------------------------------------------------------------------- -bool CNetHost::SetupSession( CNetSession* UNUSED(pSession) ) -{ - return true; -} - -//----------------------------------------------------------------------------- -// Name: HandleConnect() -// Desc: Allow application to handle client connect -//----------------------------------------------------------------------------- -bool CNetHost::HandleConnect( CNetSession* UNUSED(pSession) ) -{ - return true; -} - -//----------------------------------------------------------------------------- -// Name: HandleDisconnect() -// Desc: Allow application to handle client disconnect -//----------------------------------------------------------------------------- -bool CNetHost::HandleDisconnect( CNetSession* UNUSED(pSession) ) -{ - return true; -} - -//----------------------------------------------------------------------------- -// 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 -//----------------------------------------------------------------------------- -uint CNetHost::GetSessionCount( void ) const -{ - return ( uint )m_PeerSessions.size(); -} - -//----------------------------------------------------------------------------- -// Name: GetSession() -// Desc: Rteurns the session for the index -//----------------------------------------------------------------------------- -CNetSession* CNetHost::GetSession( uint index ) -{ - // Validate parameter - if ( index >= GetSessionCount() ) return NULL; - - return m_PeerSessions[ index ].pSession; -}; - -//----------------------------------------------------------------------------- -// Name: CNetSession() -// Desc: Constructor -//----------------------------------------------------------------------------- -CNetSession::CNetSession( CNetHost* pHost, ENetPeer* pPeer ) -{ - //ONCE( ScriptingInit(); ); - - m_Host = pHost; - m_Peer = pPeer; - m_ID = INVALID_SESSION; - m_Player = NULL; - m_PlayerSlot = NULL; - m_ReadyForTurn = false; - - // Register the network session - //g_SessionManager.Register( this ); -} - -//----------------------------------------------------------------------------- -// Name: ~CNetSession() -// Desc: Destructor -//----------------------------------------------------------------------------- -CNetSession::~CNetSession( void ) -{ - // Release any resources - //if ( m_Host ) enet_host_destroy( m_Host ); - - //m_Host = NULL; - m_Peer = NULL; - - // Unregister the network session - //g_SessionManager.Unregister( this ); -} - -//----------------------------------------------------------------------------- -// Name: SetName() -// Desc: Set a new name for the session -//----------------------------------------------------------------------------- -void CNetSession::SetName( const CStr& name ) -{ - m_Name = name; -} - -//----------------------------------------------------------------------------- -// Name: SetID() -// Desc: Set new ID for this session -//----------------------------------------------------------------------------- -void CNetSession::SetID( uint ID ) -{ - m_ID = ID; -} - -//----------------------------------------------------------------------------- -// Name: SetPlayer() -// Desc: Set the player for this session -//----------------------------------------------------------------------------- -void CNetSession::SetPlayer( CPlayer* pPlayer ) -{ - m_Player = pPlayer; -} - -//----------------------------------------------------------------------------- -// Name: SetPlayerSlot() -// Desc: Set the player slot for this session -//----------------------------------------------------------------------------- -void CNetSession::SetPlayerSlot( CPlayerSlot* pPlayerSlot ) -{ - m_PlayerSlot = pPlayerSlot; -} - -//----------------------------------------------------------------------------- -// Name: StartGame() -// Desc: Called by server after informing all clients about starting the game -//----------------------------------------------------------------------------- -void CNetSession::StartGame( void ) -{ -} - -//----------------------------------------------------------------------------- -// Name: Push() -// Desc: Sends a message through ENet -//----------------------------------------------------------------------------- -void CNetSession::Push( CNetMessage* pMessage ) -{ - // Validate parameters - if ( !pMessage ) return; - - assert( m_Host ); - - m_Host->SendMessage( this, pMessage ); -} - -//----------------------------------------------------------------------------- -// Name: TryPop() -// Desc: Receives a message through ENet -//----------------------------------------------------------------------------- -CNetMessage* CNetSession::TryPop( void ) -{ - assert( m_Host ); - - return m_Host->ReceiveMessage( this ); -} - -//----------------------------------------------------------------------------- -// Name: ScriptingInit() -// Desc: -//----------------------------------------------------------------------------- -void CNetSession::ScriptingInit( void ) -{ - AddProperty( L"id", &CNetSession::m_ID ); - AddProperty( L"name", &CNetSession::m_Name ); - AddMethod( "close", 0 ); - - CJSObject::ScriptingInit( "NetSession" ); -} - -//----------------------------------------------------------------------------- -// Name: JSI_Close() -// Desc: -//----------------------------------------------------------------------------- -bool CNetSession::JSI_Close( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) -{ - return false; -} - -//----------------------------------------------------------------------------- -// Name: HandleMessage() -// Desc: -//----------------------------------------------------------------------------- -//bool CNetSession::HandleMessage( CNetMessage* pMessage ) -//{ -// return true; -//} - -/* -//----------------------------------------------------------------------------- -// Name: CNetServerSession() -// Desc: Constructor -//----------------------------------------------------------------------------- -CNetServerSession::CNetSession( - CNetServer* pServer, - NetMessageHandler* pHandler ) -: CNetSession( pHandler ) -{ - m_Server = pServer; - m_Player = NULL; - m_PlayerSlot = NULL; - m_IsObserver = false; - - //ONCE( ScriptingInit() ); -} - -//----------------------------------------------------------------------------- -// Name: CNetServerSession() -// Desc: Constructor -//----------------------------------------------------------------------------- -CNetServerSession::CNetSession( - CNetServer* pServer, - CSocketInternal* pSocketInternal, - NetMessageHandler* pHandler ) -: CNetSession( pSocketInternal, pHandler ) -{ - m_Server = pServer; - m_Player = NULL; - m_PlayerSlot = NULL; - m_IsObserver = false; - - //ONCE( ScriptingInit() ); -} - -//----------------------------------------------------------------------------- -// Name: ~CNetServerSession() -// Desc: Destructor -//----------------------------------------------------------------------------- -CNetServerSession::~CNetServerSession( void ) -{ -} - -//----------------------------------------------------------------------------- -// Name: StartGame() -// Desc: Called by server after informing all clients about starting the game -//----------------------------------------------------------------------------- -void CNetServerSession::StartGame( void ) -{ -} - -//----------------------------------------------------------------------------- -// Name: SetPlayer() -// Desc: Set the player for this session -//----------------------------------------------------------------------------- -void CNetServerSession::SetPlayer( CPlayer* pPlayer ) -{ - m_Player = pPlayer; -} - -//----------------------------------------------------------------------------- -// Name: SetPlayerSlot() -// Desc: Set the player slot for this session -//----------------------------------------------------------------------------- -void CNetServerSession::SetPlayerSlot( CPlayerSlot* pPlayerSlot ) -{ - m_PlayerSlot = pPlayerSlot; -} - -//----------------------------------------------------------------------------- -// Name: SetID() -// Desc: Set new session ID -//----------------------------------------------------------------------------- -void CNetServerSession::SetID( uint ID ) -{ - m_ID = ID; -} - -//----------------------------------------------------------------------------- -// Name: BaseHandler() -// Desc: -//----------------------------------------------------------------------------- -bool CNetServerSession::BaseHandler( - CNetMessage* pMessage, - CNetSession* pSession ) -{ - assert( pMessage ); - assert( pSession ); - - // Validate parameters - if ( !pMessage || !pSession ) return false; - - CNetServerSession* pSrvSession = dynamic_cast< CNetSession* >( pSession ); - if ( pSrvSession ) return false; - - // Handler NMT_ERROR message only - if ( pMessage->GetType() != NMT_ERROR ) return false; - - CNetErrorMessage* pErrMessage = dynamic_cast< CNetErrorMessage* >( pMessage ); - if ( !pErrMessage ) return false; - - if ( pErrMessage->GetState() == SERVER_STATE_DISCONNECTED ) - { - if ( pSrvSession->m_ID != -1 ) - { - pSrvSession->m_Server->RemoveSession( pSrvSession ); - } - - delete pSrvSession; - } - else - { - // Not disconnected? Weired... - LOG( WARNING, LOG_CAT_NET, "CNetServerSession::BaseHandler() NMT_ERROR: %s", pErrMessage->ToString().c_str() ); - } - - delete pMessage; - - return true; -} - -//----------------------------------------------------------------------------- -// Name: HandshakeHandler() -// Desc: -//----------------------------------------------------------------------------- -bool CNetServerSession::HandshakeHandler( - CNetMessage* pMessage, - CNetSession* pSession ) -{ - assert( pMessage ); - assert( pSession ); - assert( m_Server ); - - // Validate parameters - if ( !pMessage || !pSession ) return false; - - CNetServerSession* pSrvSession = dynamic_cast< CNetServerSession* >( pSession ); - if ( !pSrvSession ) return false; - - LOG( NORMAL, LOG_CAT_NET, "CNetServerSession::HandshakeHandler() %s", pMessage->ToString().c_str() ); - - // Call base handler if other message thant NMT_ClientHandshake - if ( pMessage->GetType() != NMT_ClientHandshake ) BaseHandler( pMessage, pSession ); - - CClientHandshake* pHandshakeMessage = dynamic_cast< CClientHandshake* >( pMessage ); - if ( !pHandshakeMessage ) return false; - - if ( pHandshakeMessage->m_ProtocolVersion != PS_PROTOCOL_VERSION ) - { - pSrvSession->Push( new CCloseRequestMessage() ); - BaseHandler( new CNetErrorMessage( PS_OK, SERVER_STATE_DISCONNECTED ), pSrvSession ); - } - - //??? (else) - CServerHandshakeResponse* pNewMessage = new CServerHandshakeResponse(); - pNewMessage->m_UseProtocolVersion = PS_PROTOCOL_VERSION; - pNewMessage->m_Flags = 0; - pNewMessage->m_Message = pSrvSession->m_Server->m_WelcomeMessage; - - pSrvSession->Push( pNewMessage ); - - pSrvSession->m_MessageHandler = AuthHandler; - - delete pMessage; - - return true; +/** + *----------------------------------------------------------------------------- + * FILE : NetSession.cpp + * PROJECT : 0 A.D. + * DESCRIPTION : Network session class implementation + *----------------------------------------------------------------------------- + */ + +// INCLUDES +#include "precompiled.h" +//#include "SessionManager.h" +#include "NetSession.h" +//#include "NetServer.h" +#include "NetLog.h" + +// DECLARATIONS + +//----------------------------------------------------------------------------- +// Name: CNetHost() +// Desc: Constructor +//----------------------------------------------------------------------------- +CNetHost::CNetHost( void ) +{ + m_Host = NULL; + m_Buffer = NULL; + m_BufferSize = 0; + +// m_WorkerID = 0; +// m_StopWorker = NULL; +} + +//----------------------------------------------------------------------------- +// Name: ~CNetHost() +// Desc: Destructor +//----------------------------------------------------------------------------- +CNetHost::~CNetHost( void ) +{ + // Release host + if ( m_Host ) enet_host_destroy( m_Host ); + if ( m_Buffer ) delete [] m_Buffer; + + // Release running semaphore +// if ( m_StopWorker ) sem_close( m_StopWorker ); + + m_Host = NULL; + m_Buffer = NULL; + m_BufferSize = 0; +// m_WorkerID = 0; +// m_StopWorker = NULL; +} + +//----------------------------------------------------------------------------- +// Name: Create() +// Desc: Creates a client host +//----------------------------------------------------------------------------- +bool CNetHost::Create( void ) +{ + // Create ENet host + m_Host = enet_host_create( NULL, 1, 0, 0 ); + if ( !m_Host ) return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Name: Create() +// Desc: Creates a server host +//----------------------------------------------------------------------------- +bool CNetHost::Create( uint port, uint maxPeers ) +{ + 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( void ) +{ + // Destroy server + if ( m_Host ) enet_host_destroy( m_Host ); + + // Stop worker thread +// sem_post( m_StopWorker ); +// if ( m_WorkerID ) pthread_join( m_WorkerID, NULL ); + + // 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(); + + m_Host = NULL; +// m_WorkerID = 0; +} + +//----------------------------------------------------------------------------- +// 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, uint port ) +{ + ENetEvent event; + ENetAddress addr; + PeerSession item; + + assert( m_Host ); + + // Bind to specified host + 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 3 seconds for the connection to succeed + if ( enet_host_service( m_Host, &event, 5000 ) > 0 && + event.type == ENET_EVENT_TYPE_CONNECT ) + { + // Connection succeeded + CNetSession* pNewSession = new CNetSession( this, event.peer ); + if ( !pNewSession ) return false; + + if ( !SetupSession( pNewSession ) ) return false; + + NET_LOG3( "Successfully connected to server %s:%d succeeded", host.c_str(), port ); + + // Successfully handled? + if ( !HandleConnect( pNewSession ) ) + { + delete pNewSession; + + return false; + } + + // Store the only server session + item.pPeer = event.peer; + item.pSession = pNewSession; + m_PeerSessions.push_back( item ); + + return true; + } + + NET_LOG3( "Connection to server %s:%d failed", host.c_str(), port ); + + // 3 seconds are up or a host was disconnected + enet_peer_reset( pPeer ); + + return false; +} + +//----------------------------------------------------------------------------- +// Name: Disconnect() +// Desc: Disconnects the specified session from the host +//----------------------------------------------------------------------------- +bool CNetHost::Disconnect( CNetSession* pSession ) +{ + ENetEvent event; + + // Validate parameters + if ( !pSession ) return false; + + assert( m_Host ); + assert( pSession->m_Peer ); + + // Disconnect peer + enet_peer_disconnect( pSession->m_Peer, 0 ); + + // Allow up to 3 seconds for the disconnect to succeed + while ( enet_host_service( m_Host, &event, 5000 ) > 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: Run() +// Desc: +//----------------------------------------------------------------------------- +/*bool CNetHost::Run( void ) +{ + assert( m_Host ); + + // Host created? + if ( !m_Host ) return false; + + // Already running? + if ( m_WorkerID != 0 ) return true; + + // Create run semaphore + m_StopWorker = sem_open( "//WFG_HostWorkerRun", O_CREAT | O_EXCL, 0700, 0 ); + if ( !m_StopWorker ) return false; + + // Create worker thread + if ( pthread_create( &m_WorkerID, 0, &WorkerFunc, this ) < 0 ) return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Name: WorkerFunc() +// Desc: Worker thread function +//----------------------------------------------------------------------------- +void* CNetHost::WorkerFunc( void* pData ) +{ + ENetEvent event; + CNetSession* pSession = NULL; + PeerSession item; + PeerSessionList::iterator it; + + // Validate parameters + if ( !pData ) return NULL; + + CNetHost* pHost = ( CNetHost* )pData; + + // Poll host for events + while ( true ) + { + // Decide whether to stop or not + if ( !sem_timedwait( pHost->m_StopWorker, NULL ) ) break; + + int retval = enet_host_service( pHost->m_Host, &event, 100 ); + + // Any event? + if ( !retval ) continue; + + // Any error? + if ( !retval ) break; + + // Handle occured event + switch( event.type ) + { + case ENET_EVENT_TYPE_CONNECT: + + // A new client has connected, handle it + pSession = new CNetSession( pHost, event.peer ); + if ( !pSession ) return NULL; + + // Successfully handled? + if ( !pHost->HandleConnect( pSession ) ) return NULL; + + // Add new item to internal list + item.pPeer = event.peer; + item.pSession = pSession; + pHost->m_PeerSessions.push_back( item ); + + break; + + case ENET_EVENT_TYPE_DISCONNECT: + + // Client has disconnected, handle it + it = pHost->m_PeerSessions.begin();; + for ( ; it != pHost->m_PeerSessions.end(); it++ ) + { + // Is this our session? + if ( it->pPeer == event.peer ) + { + // Successfully handled? + if ( !pHost->HandleDisconnect( it->pSession ) ) return NULL; + + pHost->m_PeerSessions.erase( it ); + } + } + + break; + + case ENET_EVENT_TYPE_RECEIVE: + + // A new data packet was received from client, handle message + it = pHost->m_PeerSessions.begin(); + for ( ; it != pHost->m_PeerSessions.end(); it++ ) + { + // Is this our session? + if ( it->pPeer == event.peer ) + { + // Create message from raw data + CNetMessage* pNewMessage = CNetMessageFactory::CreateMessage( event.packet->data, event.packet->dataLength ); + if ( !pNewMessage ) return NULL; + + // Successfully handled? + if ( !pHost->HandleMessageReceive( pNewMessage, it->pSession ) ) return NULL; + } + } + + break; + } + } + + return NULL; +}*/ + +//----------------------------------------------------------------------------- +// Name: ProcessEvents() +// Desc: Wait for events and shuttles packets between the host and its peers +//----------------------------------------------------------------------------- +bool CNetHost::Poll( void ) +{ + ENetEvent event; + CNetSession* pSession = NULL; + PeerSession item; + PeerSessionList::iterator it; + + assert( m_Host ); + + // Poll host for events + while ( enet_host_service( m_Host, &event, 0 ) > 0 ) + { + // Handle occured event + switch( event.type ) + { + case ENET_EVENT_TYPE_CONNECT: + + // A new client has connected, handle it + pSession = new CNetSession( this, event.peer ); + if ( !pSession ) return false; + + // Setup new session + if ( !SetupSession( pSession ) ) return false; + + NET_LOG3( "A new client connected from %x:%u", event.peer->address.host, event.peer->address.port ); + + // Successfully handled? + if ( !HandleConnect( pSession ) ) return false; + + event.peer->data = pSession; + + // Add new item to internal list + item.pPeer = event.peer; + item.pSession = pSession; + m_PeerSessions.push_back( item ); + + break; + + case ENET_EVENT_TYPE_DISCONNECT: + + // Client has disconnected, handle it + it = m_PeerSessions.begin();; + for ( ; it != m_PeerSessions.end(); it++ ) + { + // Is this our session? + if ( it->pPeer == event.peer ) + { + NET_LOG2( "%x disconnected", event.peer->data ); + + // Successfully handled? + if ( !HandleDisconnect( it->pSession ) ) return false; + + m_PeerSessions.erase( it ); + + return true; + } + } + + break; + + case ENET_EVENT_TYPE_RECEIVE: + + // A new data packet was received from client, handle message + it = m_PeerSessions.begin(); + for ( ; it != m_PeerSessions.end(); it++ ) + { + // Is this our session? + if ( it->pPeer == event.peer ) + { + // Create message from raw data + CNetMessage* pNewMessage = CNetMessageFactory::CreateMessage( event.packet->data, event.packet->dataLength ); + if ( !pNewMessage ) return false; + + NET_LOG4( "Message %s of size %u was received from %x", pNewMessage->ToString().c_str(), pNewMessage->GetSerializedLength(), event.peer->data ); + + // Successfully handled? + if ( !HandleMessageReceive( pNewMessage, it->pSession ) ) { + enet_packet_destroy( event.packet ); + return false; + } + + // Done using the packet + enet_packet_destroy( event.packet ); + } + } + + break; + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: Broadcast() +// Desc: Broadcast the specified message to connected clients +// Note: Reference counting for a sending message requires multithreading +// locking mechanisms so a clone of the message is made and sent out +//----------------------------------------------------------------------------- +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: ResizeBuffer() +// Desc: Resizes the internal buffer +//----------------------------------------------------------------------------- +void CNetHost::ResizeBuffer( size_t size ) +{ + // Already enough space? + if ( size <= m_BufferSize ) return; + + // Allocate enough space for the new buffer + u8* pBuffer = new u8[ ALIGN_BLOCK( m_BufferSize + size ) ]; + if ( !pBuffer ) return; + + // Any old data? + if ( m_Buffer ) + { + // Copy old data + memcpy( pBuffer, m_Buffer, m_BufferSize ); + + delete [] m_Buffer; + } + + // Store new buffer + m_Buffer = pBuffer; + + // Store new buffer size + m_BufferSize = ALIGN_BLOCK( m_BufferSize + size ); +} + +//----------------------------------------------------------------------------- +// 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; + + assert( pSession->m_Peer ); + assert( m_Host ); + + size_t size = pMessage->GetSerializedLength(); + + // Adjust buffer for message + ResizeBuffer( size ); + + // Save message to internal buffer + pMessage->Serialize( m_Buffer ); + + // Create a reliable packet + ENetPacket* pPacket = enet_packet_create( m_Buffer, size, ENET_PACKET_FLAG_RELIABLE ); + if ( !pPacket ) return false; + + // 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 + NET_LOG( "Failed to send ENet packet to peer" ); + + return false; + } + else + { + NET_LOG4( "Message %s of size %u was sent to %x", + pMessage->ToString().c_str(), pMessage->GetSerializedLength(), pSession->m_Peer->data ); + } + + enet_host_flush( m_Host ); + + 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; + + 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 ); +} + +//----------------------------------------------------------------------------- +// Name: SetupSession() +// Desc: Setup new session upon creation +//----------------------------------------------------------------------------- +bool CNetHost::SetupSession( CNetSession* UNUSED(pSession) ) +{ + return true; +} + +//----------------------------------------------------------------------------- +// Name: HandleConnect() +// Desc: Allow application to handle client connect +//----------------------------------------------------------------------------- +bool CNetHost::HandleConnect( CNetSession* UNUSED(pSession) ) +{ + return true; +} + +//----------------------------------------------------------------------------- +// Name: HandleDisconnect() +// Desc: Allow application to handle client disconnect +//----------------------------------------------------------------------------- +bool CNetHost::HandleDisconnect( CNetSession* UNUSED(pSession) ) +{ + return true; +} + +//----------------------------------------------------------------------------- +// 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 +//----------------------------------------------------------------------------- +uint CNetHost::GetSessionCount( void ) const +{ + return ( uint )m_PeerSessions.size(); +} + +//----------------------------------------------------------------------------- +// Name: GetSession() +// Desc: Rteurns the session for the index +//----------------------------------------------------------------------------- +CNetSession* CNetHost::GetSession( uint index ) +{ + // Validate parameter + if ( index >= GetSessionCount() ) return NULL; + + return m_PeerSessions[ index ].pSession; +}; + +//----------------------------------------------------------------------------- +// Name: CNetSession() +// Desc: Constructor +//----------------------------------------------------------------------------- +CNetSession::CNetSession( CNetHost* pHost, ENetPeer* pPeer ) +{ + //ONCE( ScriptingInit(); ); + + m_Host = pHost; + m_Peer = pPeer; + m_ID = INVALID_SESSION; + m_Player = NULL; + m_PlayerSlot = NULL; + m_ReadyForTurn = false; + + // Register the network session + //g_SessionManager.Register( this ); +} + +//----------------------------------------------------------------------------- +// Name: ~CNetSession() +// Desc: Destructor +//----------------------------------------------------------------------------- +CNetSession::~CNetSession( void ) +{ + // Release any resources + //if ( m_Host ) enet_host_destroy( m_Host ); + + //m_Host = NULL; + m_Peer = NULL; + + // Unregister the network session + //g_SessionManager.Unregister( this ); +} + +//----------------------------------------------------------------------------- +// Name: SetName() +// Desc: Set a new name for the session +//----------------------------------------------------------------------------- +void CNetSession::SetName( const CStr& name ) +{ + m_Name = name; +} + +//----------------------------------------------------------------------------- +// Name: SetID() +// Desc: Set new ID for this session +//----------------------------------------------------------------------------- +void CNetSession::SetID( uint ID ) +{ + m_ID = ID; +} + +//----------------------------------------------------------------------------- +// Name: SetPlayer() +// Desc: Set the player for this session +//----------------------------------------------------------------------------- +void CNetSession::SetPlayer( CPlayer* pPlayer ) +{ + m_Player = pPlayer; +} + +//----------------------------------------------------------------------------- +// Name: SetPlayerSlot() +// Desc: Set the player slot for this session +//----------------------------------------------------------------------------- +void CNetSession::SetPlayerSlot( CPlayerSlot* pPlayerSlot ) +{ + m_PlayerSlot = pPlayerSlot; +} + +//----------------------------------------------------------------------------- +// Name: StartGame() +// Desc: Called by server after informing all clients about starting the game +//----------------------------------------------------------------------------- +void CNetSession::StartGame( void ) +{ +} + +//----------------------------------------------------------------------------- +// Name: Push() +// Desc: Sends a message through ENet +//----------------------------------------------------------------------------- +void CNetSession::Push( CNetMessage* pMessage ) +{ + // Validate parameters + if ( !pMessage ) return; + + assert( m_Host ); + + m_Host->SendMessage( this, pMessage ); +} + +//----------------------------------------------------------------------------- +// Name: TryPop() +// Desc: Receives a message through ENet +//----------------------------------------------------------------------------- +CNetMessage* CNetSession::TryPop( void ) +{ + assert( m_Host ); + + return m_Host->ReceiveMessage( this ); +} + +//----------------------------------------------------------------------------- +// Name: ScriptingInit() +// Desc: +//----------------------------------------------------------------------------- +void CNetSession::ScriptingInit( void ) +{ + AddProperty( L"id", &CNetSession::m_ID ); + AddProperty( L"name", &CNetSession::m_Name ); + AddMethod( "close", 0 ); + + CJSObject::ScriptingInit( "NetSession" ); +} + +//----------------------------------------------------------------------------- +// Name: JSI_Close() +// Desc: +//----------------------------------------------------------------------------- +bool CNetSession::JSI_Close( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) +{ + return false; +} + +//----------------------------------------------------------------------------- +// Name: HandleMessage() +// Desc: +//----------------------------------------------------------------------------- +//bool CNetSession::HandleMessage( CNetMessage* pMessage ) +//{ +// return true; +//} + +/* +//----------------------------------------------------------------------------- +// Name: CNetServerSession() +// Desc: Constructor +//----------------------------------------------------------------------------- +CNetServerSession::CNetSession( + CNetServer* pServer, + NetMessageHandler* pHandler ) +: CNetSession( pHandler ) +{ + m_Server = pServer; + m_Player = NULL; + m_PlayerSlot = NULL; + m_IsObserver = false; + + //ONCE( ScriptingInit() ); +} + +//----------------------------------------------------------------------------- +// Name: CNetServerSession() +// Desc: Constructor +//----------------------------------------------------------------------------- +CNetServerSession::CNetSession( + CNetServer* pServer, + CSocketInternal* pSocketInternal, + NetMessageHandler* pHandler ) +: CNetSession( pSocketInternal, pHandler ) +{ + m_Server = pServer; + m_Player = NULL; + m_PlayerSlot = NULL; + m_IsObserver = false; + + //ONCE( ScriptingInit() ); +} + +//----------------------------------------------------------------------------- +// Name: ~CNetServerSession() +// Desc: Destructor +//----------------------------------------------------------------------------- +CNetServerSession::~CNetServerSession( void ) +{ +} + +//----------------------------------------------------------------------------- +// Name: StartGame() +// Desc: Called by server after informing all clients about starting the game +//----------------------------------------------------------------------------- +void CNetServerSession::StartGame( void ) +{ +} + +//----------------------------------------------------------------------------- +// Name: SetPlayer() +// Desc: Set the player for this session +//----------------------------------------------------------------------------- +void CNetServerSession::SetPlayer( CPlayer* pPlayer ) +{ + m_Player = pPlayer; +} + +//----------------------------------------------------------------------------- +// Name: SetPlayerSlot() +// Desc: Set the player slot for this session +//----------------------------------------------------------------------------- +void CNetServerSession::SetPlayerSlot( CPlayerSlot* pPlayerSlot ) +{ + m_PlayerSlot = pPlayerSlot; +} + +//----------------------------------------------------------------------------- +// Name: SetID() +// Desc: Set new session ID +//----------------------------------------------------------------------------- +void CNetServerSession::SetID( uint ID ) +{ + m_ID = ID; +} + +//----------------------------------------------------------------------------- +// Name: BaseHandler() +// Desc: +//----------------------------------------------------------------------------- +bool CNetServerSession::BaseHandler( + CNetMessage* pMessage, + CNetSession* pSession ) +{ + assert( pMessage ); + assert( pSession ); + + // Validate parameters + if ( !pMessage || !pSession ) return false; + + CNetServerSession* pSrvSession = dynamic_cast< CNetSession* >( pSession ); + if ( pSrvSession ) return false; + + // Handler NMT_ERROR message only + if ( pMessage->GetType() != NMT_ERROR ) return false; + + CNetErrorMessage* pErrMessage = dynamic_cast< CNetErrorMessage* >( pMessage ); + if ( !pErrMessage ) return false; + + if ( pErrMessage->GetState() == SERVER_STATE_DISCONNECTED ) + { + if ( pSrvSession->m_ID != -1 ) + { + pSrvSession->m_Server->RemoveSession( pSrvSession ); + } + + delete pSrvSession; + } + else + { + // Not disconnected? Weired... + LOG( WARNING, LOG_CAT_NET, "CNetServerSession::BaseHandler() NMT_ERROR: %s", pErrMessage->ToString().c_str() ); + } + + delete pMessage; + + return true; +} + +//----------------------------------------------------------------------------- +// Name: HandshakeHandler() +// Desc: +//----------------------------------------------------------------------------- +bool CNetServerSession::HandshakeHandler( + CNetMessage* pMessage, + CNetSession* pSession ) +{ + assert( pMessage ); + assert( pSession ); + assert( m_Server ); + + // Validate parameters + if ( !pMessage || !pSession ) return false; + + CNetServerSession* pSrvSession = dynamic_cast< CNetServerSession* >( pSession ); + if ( !pSrvSession ) return false; + + LOG( NORMAL, LOG_CAT_NET, "CNetServerSession::HandshakeHandler() %s", pMessage->ToString().c_str() ); + + // Call base handler if other message thant NMT_ClientHandshake + if ( pMessage->GetType() != NMT_ClientHandshake ) BaseHandler( pMessage, pSession ); + + CClientHandshake* pHandshakeMessage = dynamic_cast< CClientHandshake* >( pMessage ); + if ( !pHandshakeMessage ) return false; + + if ( pHandshakeMessage->m_ProtocolVersion != PS_PROTOCOL_VERSION ) + { + pSrvSession->Push( new CCloseRequestMessage() ); + BaseHandler( new CNetErrorMessage( PS_OK, SERVER_STATE_DISCONNECTED ), pSrvSession ); + } + + //??? (else) + CServerHandshakeResponse* pNewMessage = new CServerHandshakeResponse(); + pNewMessage->m_UseProtocolVersion = PS_PROTOCOL_VERSION; + pNewMessage->m_Flags = 0; + pNewMessage->m_Message = pSrvSession->m_Server->m_WelcomeMessage; + + pSrvSession->Push( pNewMessage ); + + pSrvSession->m_MessageHandler = AuthHandler; + + delete pMessage; + + return true; }*/ \ No newline at end of file diff --git a/source/network/NetSession.h b/source/network/NetSession.h index a81a14bb8f..6d42e35ef9 100644 --- a/source/network/NetSession.h +++ b/source/network/NetSession.h @@ -1,341 +1,341 @@ -/** - *----------------------------------------------------------------------------- - * FILE : NetSession.h - * PROJECT : 0 A.D. - * DESCRIPTION : Network session class interface file - *----------------------------------------------------------------------------- - */ - -#ifndef NETSESSION_H -#define NETSESSION_H - -// INCLUDES -#include "Network.h" -#include "ps/Singleton.h" -#include "ps/GameAttributes.h" -#include "ps/Player.h" -#include "fsm.h" -#include - -#include - -// DECLARATIONS -#define INVALID_SESSION 0 -#define ENET_DEFAULT_CHANNEL 0 - -class CNetSession; -class CNetHost; - -typedef struct -{ - ENetPeer* pPeer; - CNetSession* pSession; - -} PeerSession; - -typedef struct -{ - CNetHost* pHost; - CNetSession* pSession; - -} FsmActionCtx; - -typedef std::vector< PeerSession > PeerSessionList; - -/* - CLASS : CNetHost - DESCRIPTION : CNetHost is a wrapper around ENet host conecept - NOTES : -*/ - -class CNetHost : public Singleton< CNetHost > -{ -public: - - CNetHost( void ); - virtual ~CNetHost( void ); - - bool Create( void ); - bool Create( uint port, uint maxPeers ); - void Shutdown( void ); - - /** - * Indicates whether the host is currently a server - * - * @return Boolean indicating whether the host is a server - */ - virtual bool IsServer( void ) const { return false; } - - /** - * Indicates whether the host is currently a client - * - * @return Boolean indicating whether the host is a client - */ - virtual bool IsClient( void ) const { return false; } - - /** - * Returns the number of sessions for the host - * - * @return The number of sessions - */ - uint GetSessionCount( void ) 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( uint index ); - - /** - * Connects to foreign host - * - * @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, uint 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 - * - * @return true on exit, false dispatch failure - */ - //bool Run( void ); - bool Poll( void ); - - /** - * 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 - */ - virtual bool SendMessage( - const CNetSession* pSession, - const CNetMessage* pMessage ); - - /** - * Receive a message from client if available - * - */ - virtual CNetMessage* ReceiveMessage( const CNetSession* pSession ); - -protected: - - /** - * Attempts to resize the internal buffer to the size indicated by the - * passed parameter. - * - * @param size The new size for the buffer - */ - void ResizeBuffer( size_t size ); - - // Allow application to handle new client connect - virtual bool SetupSession ( CNetSession* pSession ); - virtual bool HandleConnect ( CNetSession* pSession ); - virtual bool HandleDisconnect ( CNetSession* pSession ); - virtual bool HandleMessageReceive ( - CNetMessage* pMessage, - CNetSession* pSession ); - - /** - * Worker thread function - * - * @pData Argument specified on thread creation - * @return NULL - */ - //static void* WorkerFunc( void* pData ); - -private: - - // Not implemented - CNetHost( const CNetHost& ); - CNetHost& operator=( const CNetHost& ); - - u8* m_Buffer; // Serialize out messages buffer - size_t m_BufferSize; // Output buffer size - ENetHost* m_Host; // Represents this host - PeerSessionList m_PeerSessions; // Session list of connected peers - //pthread_t m_WorkerID; // Worker thread - //sem_t* m_StopWorker; // Worker thread stop semaphore -}; - -/* - CLASS : CNetSession - DESCRIPTION : CNetSession is a wrapper class around 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 fom 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. - NOTES : -*/ - -class CNetSession : public CFsm, - public CJSObject< CNetSession >, - public IMessagePipeEnd -{ - friend class CNetHost; - -public: - - virtual ~CNetSession( void ); - - /** - * Retrieves the name of the session - * - * @return Session name - */ - const CStrW& GetName( void ) const { return m_Name; } - - /** - * 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( void ) const { return m_ID; } - - /** - * Set the ID for this session - * - * @param New session ID - */ - void SetID( uint ID ); - - /** - * Allows both client and server to set a callback handler - * - * @param pCallbackHandler Callback handler - */ - //void SetCallbackHandler( ISessionCallback* pCallbackHandler ); - - /** - * Disconnects the client from remote host - * - */ - void Reset( void ); - - void SetPlayer( CPlayer* pPlayer ); - CPlayer* GetPlayer( void ) { return m_Player; } - void SetPlayerSlot( CPlayerSlot* pPlayerSlot ); - CPlayerSlot* GetPlayerSlot( void ) { return m_PlayerSlot; } - void StartGame( void ); - virtual void Push( CNetMessage* pMessage ); - virtual CNetMessage* TryPop( void ); - bool IsReadyForTurn( void ) const { return m_ReadyForTurn; } - void SetReadyForTurn( bool newValue ) { m_ReadyForTurn = newValue; } - bool JSI_Close( JSContext *cx, uintN argc, jsval *argv ); - static void ScriptingInit( void ); - - //bool HandleMessage( CNetMessage* pMessage ); - -protected: - - /** - * Process the message passed as parameter - * - * @param message The message to process - * @return true if the message was handler - * successufully, false otherwise - */ - //bool ProcessMessage( const CNetMessage& message ); - -private: - - // Only the hosts can create sessions - CNetSession( CNetHost* pHost, ENetPeer* pPeer ); - - // Not implemented - CNetSession( void ); - CNetSession( const CNetSession& ); - CNetSession& operator=( const CNetSession& ); - - CNetHost* m_Host; // The associated local host - ENetPeer* m_Peer; // Represents the peer host - uint m_ID; // Session ID - CStrW m_Name; // Session name - CPlayer* m_Player; - CPlayerSlot* m_PlayerSlot; - bool m_ReadyForTurn; // Next turn ready flag -}; - -/* - CLASS : CNetServerSession - DESCRIPTION : - NOTES : -*/ - -/*class CNetServerSession : public CNetSession, - public CJSObject< CNetServerSession > -{ -public: - - bool IsObserver ( void ) const { return m_IsObserver; } - CPlayer* GetPlayer ( void ) const { return m_Player; } - CPlayerSlot* GetPlayerSlot ( void ) const { return m_PlayerSlot; } - void StartGame ( void ); - void SetPlayer ( CPlayer* pPlayer ); - void SetPlayerSlot ( CPlayerSlot* pPlayerSlot ); - -protected: - - CNetServerSession( - CNetServer* pServer, - NetMessageHandler* pHandler = m_HandshakeHandler ); - CNetServerSession( - CNetServer* pServer, - CSocketInternal* pSocketInternal, - NetMessageHandler* pHandler = m_HandshakeHandler ); - virtual ~CNetServerSession( void ); - -private: - - static void ScriptingInit ( void ); - bool JSI_Close ( - JSContext* pContext, - uintN argc, - jsval* argv ); - - CNetServer* m_Server; - CPlayer* m_Player; - CPlayerSlot* m_PlayerSlot; - bool m_IsObserver; - - static bool HandshakeHandler( CNetMessage* pMessage, CNetSession* pSession ); - static bool ObserverHandler ( CNetMessage* pMessage, CNetSession* pSession ); - static bool BaseHandler ( CNetMessage* pMessage, CNetSession* pSession ); - static bool AuthHandler ( CNetMessage* pMessage, CNetSession* pSession ); - static bool PreGameHandler ( CNetMessage* pMessage, CNetSession* pSession ); - static bool InGameHandler ( CNetMessage* pMessage, CNetSession* pSession ); - static bool ChatHandler ( CNetMessage* pMessage, CNetSession* pSession ); -};*/ - -#endif // NETSESSION_H - +/** + *----------------------------------------------------------------------------- + * FILE : NetSession.h + * PROJECT : 0 A.D. + * DESCRIPTION : Network session class interface file + *----------------------------------------------------------------------------- + */ + +#ifndef NETSESSION_H +#define NETSESSION_H + +// INCLUDES +#include "Network.h" +#include "ps/Singleton.h" +#include "ps/GameAttributes.h" +#include "ps/Player.h" +#include "fsm.h" +#include + +#include + +// DECLARATIONS +#define INVALID_SESSION 0 +#define ENET_DEFAULT_CHANNEL 0 + +class CNetSession; +class CNetHost; + +typedef struct +{ + ENetPeer* pPeer; + CNetSession* pSession; + +} PeerSession; + +typedef struct +{ + CNetHost* pHost; + CNetSession* pSession; + +} FsmActionCtx; + +typedef std::vector< PeerSession > PeerSessionList; + +/* + CLASS : CNetHost + DESCRIPTION : CNetHost is a wrapper around ENet host conecept + NOTES : +*/ + +class CNetHost : public Singleton< CNetHost > +{ +public: + + CNetHost( void ); + virtual ~CNetHost( void ); + + bool Create( void ); + bool Create( uint port, uint maxPeers ); + void Shutdown( void ); + + /** + * Indicates whether the host is currently a server + * + * @return Boolean indicating whether the host is a server + */ + virtual bool IsServer( void ) const { return false; } + + /** + * Indicates whether the host is currently a client + * + * @return Boolean indicating whether the host is a client + */ + virtual bool IsClient( void ) const { return false; } + + /** + * Returns the number of sessions for the host + * + * @return The number of sessions + */ + uint GetSessionCount( void ) 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( uint index ); + + /** + * Connects to foreign host + * + * @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, uint 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 + * + * @return true on exit, false dispatch failure + */ + //bool Run( void ); + bool Poll( void ); + + /** + * 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 + */ + virtual bool SendMessage( + const CNetSession* pSession, + const CNetMessage* pMessage ); + + /** + * Receive a message from client if available + * + */ + virtual CNetMessage* ReceiveMessage( const CNetSession* pSession ); + +protected: + + /** + * Attempts to resize the internal buffer to the size indicated by the + * passed parameter. + * + * @param size The new size for the buffer + */ + void ResizeBuffer( size_t size ); + + // Allow application to handle new client connect + virtual bool SetupSession ( CNetSession* pSession ); + virtual bool HandleConnect ( CNetSession* pSession ); + virtual bool HandleDisconnect ( CNetSession* pSession ); + virtual bool HandleMessageReceive ( + CNetMessage* pMessage, + CNetSession* pSession ); + + /** + * Worker thread function + * + * @pData Argument specified on thread creation + * @return NULL + */ + //static void* WorkerFunc( void* pData ); + +private: + + // Not implemented + CNetHost( const CNetHost& ); + CNetHost& operator=( const CNetHost& ); + + u8* m_Buffer; // Serialize out messages buffer + size_t m_BufferSize; // Output buffer size + ENetHost* m_Host; // Represents this host + PeerSessionList m_PeerSessions; // Session list of connected peers + //pthread_t m_WorkerID; // Worker thread + //sem_t* m_StopWorker; // Worker thread stop semaphore +}; + +/* + CLASS : CNetSession + DESCRIPTION : CNetSession is a wrapper class around 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 fom 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. + NOTES : +*/ + +class CNetSession : public CFsm, + public CJSObject< CNetSession >, + public IMessagePipeEnd +{ + friend class CNetHost; + +public: + + virtual ~CNetSession( void ); + + /** + * Retrieves the name of the session + * + * @return Session name + */ + const CStrW& GetName( void ) const { return m_Name; } + + /** + * 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( void ) const { return m_ID; } + + /** + * Set the ID for this session + * + * @param New session ID + */ + void SetID( uint ID ); + + /** + * Allows both client and server to set a callback handler + * + * @param pCallbackHandler Callback handler + */ + //void SetCallbackHandler( ISessionCallback* pCallbackHandler ); + + /** + * Disconnects the client from remote host + * + */ + void Reset( void ); + + void SetPlayer( CPlayer* pPlayer ); + CPlayer* GetPlayer( void ) { return m_Player; } + void SetPlayerSlot( CPlayerSlot* pPlayerSlot ); + CPlayerSlot* GetPlayerSlot( void ) { return m_PlayerSlot; } + void StartGame( void ); + virtual void Push( CNetMessage* pMessage ); + virtual CNetMessage* TryPop( void ); + bool IsReadyForTurn( void ) const { return m_ReadyForTurn; } + void SetReadyForTurn( bool newValue ) { m_ReadyForTurn = newValue; } + bool JSI_Close( JSContext *cx, uintN argc, jsval *argv ); + static void ScriptingInit( void ); + + //bool HandleMessage( CNetMessage* pMessage ); + +protected: + + /** + * Process the message passed as parameter + * + * @param message The message to process + * @return true if the message was handler + * successufully, false otherwise + */ + //bool ProcessMessage( const CNetMessage& message ); + +private: + + // Only the hosts can create sessions + CNetSession( CNetHost* pHost, ENetPeer* pPeer ); + + // Not implemented + CNetSession( void ); + CNetSession( const CNetSession& ); + CNetSession& operator=( const CNetSession& ); + + CNetHost* m_Host; // The associated local host + ENetPeer* m_Peer; // Represents the peer host + uint m_ID; // Session ID + CStrW m_Name; // Session name + CPlayer* m_Player; + CPlayerSlot* m_PlayerSlot; + bool m_ReadyForTurn; // Next turn ready flag +}; + +/* + CLASS : CNetServerSession + DESCRIPTION : + NOTES : +*/ + +/*class CNetServerSession : public CNetSession, + public CJSObject< CNetServerSession > +{ +public: + + bool IsObserver ( void ) const { return m_IsObserver; } + CPlayer* GetPlayer ( void ) const { return m_Player; } + CPlayerSlot* GetPlayerSlot ( void ) const { return m_PlayerSlot; } + void StartGame ( void ); + void SetPlayer ( CPlayer* pPlayer ); + void SetPlayerSlot ( CPlayerSlot* pPlayerSlot ); + +protected: + + CNetServerSession( + CNetServer* pServer, + NetMessageHandler* pHandler = m_HandshakeHandler ); + CNetServerSession( + CNetServer* pServer, + CSocketInternal* pSocketInternal, + NetMessageHandler* pHandler = m_HandshakeHandler ); + virtual ~CNetServerSession( void ); + +private: + + static void ScriptingInit ( void ); + bool JSI_Close ( + JSContext* pContext, + uintN argc, + jsval* argv ); + + CNetServer* m_Server; + CPlayer* m_Player; + CPlayerSlot* m_PlayerSlot; + bool m_IsObserver; + + static bool HandshakeHandler( CNetMessage* pMessage, CNetSession* pSession ); + static bool ObserverHandler ( CNetMessage* pMessage, CNetSession* pSession ); + static bool BaseHandler ( CNetMessage* pMessage, CNetSession* pSession ); + static bool AuthHandler ( CNetMessage* pMessage, CNetSession* pSession ); + static bool PreGameHandler ( CNetMessage* pMessage, CNetSession* pSession ); + static bool InGameHandler ( CNetMessage* pMessage, CNetSession* pSession ); + static bool ChatHandler ( CNetMessage* pMessage, CNetSession* pSession ); +};*/ + +#endif // NETSESSION_H + diff --git a/source/network/fsm.cpp b/source/network/fsm.cpp index 4c3db8be25..277489070c 100644 --- a/source/network/fsm.cpp +++ b/source/network/fsm.cpp @@ -1,445 +1,445 @@ -/** - *----------------------------------------------------------------------------- - * FILE : fsm.cpp - * PROJECT : 0 A.D. - * DESCRIPTION : Finite state machine class implementation - *----------------------------------------------------------------------------- - */ - -// INCLUDES -#include "precompiled.h" -#include "fsm.h" - -// DECLARATIONS - -//----------------------------------------------------------------------------- -// Name: CFsmEvent() -// Desc: Constructor -//----------------------------------------------------------------------------- -CFsmEvent::CFsmEvent( unsigned int type ) -{ - m_Type = type; - m_Param = NULL; -} - -//----------------------------------------------------------------------------- -// Name; ~CFsmEvent() -// Desc: Destructor -//----------------------------------------------------------------------------- -CFsmEvent::~CFsmEvent( void ) -{ - m_Param = NULL; -} - -//----------------------------------------------------------------------------- -// Name: SetParamRef() -// Desc: Sets the parameter for the event -//----------------------------------------------------------------------------- -void CFsmEvent::SetParamRef( void* pParam ) -{ - m_Param = pParam; -} - -//----------------------------------------------------------------------------- -// Name: CFsmTransition() -// Desc: Constructor -//----------------------------------------------------------------------------- -CFsmTransition::CFsmTransition( unsigned int state ) -{ - m_CurrState = state; -} - -//----------------------------------------------------------------------------- -// Name: ~CFsmTransition() -// Desc: Destructor -//----------------------------------------------------------------------------- -CFsmTransition::~CFsmTransition( void ) -{ - m_Actions.clear(); - m_Conditions.clear(); -} - -//----------------------------------------------------------------------------- -// Name: RegisterAction() -// Desc: Adds action that will be executed when the transition will occur -//----------------------------------------------------------------------------- -void CFsmTransition::RegisterAction( void* pAction, void* pContext ) -{ - CallbackFunction callback; - - // Add action at the end of actions list - callback.pFunction = pAction; - callback.pContext = pContext; - - m_Actions.push_back( callback ); -} - -//----------------------------------------------------------------------------- -// Name: AddCondition() -// Desc: Adds condition which will be evaluated when the transition will occurs -//----------------------------------------------------------------------------- -void CFsmTransition::RegisterCondition( void* pCondition, void* pContext ) -{ - CallbackFunction callback; - - // Add condition at the end of conditions list - callback.pFunction = pCondition; - callback.pContext = pContext; - - m_Conditions.push_back( callback ); -} - -//----------------------------------------------------------------------------- -// Name: SetEvent() -// Desc: Set event for which transition will occur -//----------------------------------------------------------------------------- -void CFsmTransition::SetEvent( CFsmEvent* pEvent ) -{ - m_Event = pEvent; -} - -//----------------------------------------------------------------------------- -// Name: SetNextState() -// Desc: Set next state the transition will switch the system to -//----------------------------------------------------------------------------- -void CFsmTransition::SetNextState( unsigned int nextState ) -{ - m_NextState = nextState; -} - -//----------------------------------------------------------------------------- -// Name: ApplyConditions() -// Desc: Evaluate conditions for the transition -// Note: If there are no conditions, assume true -//----------------------------------------------------------------------------- -bool CFsmTransition::ApplyConditions( void ) const -{ - bool eval = true; - - CallbackList::const_iterator it = m_Conditions.begin(); - for( ; it != m_Conditions.end(); it++ ) - { - if ( it->pFunction ) - { - // Evaluate condition - CONDITION Condition = ( CONDITION )it->pFunction; - eval &= Condition( it->pContext ); - } - } - - return eval; -} - -//----------------------------------------------------------------------------- -// Name: RunActions() -// Desc: Execur actions for the transition -// Note: If there are no actions, assume true -//----------------------------------------------------------------------------- -bool CFsmTransition::RunActions( void ) const -{ - bool result = true; - - CallbackList::const_iterator it = m_Actions.begin(); - for( ; it != m_Actions.end(); it++ ) - { - if ( it->pFunction ) - { - // Run action - ACTION Action = ( ACTION )it->pFunction; - result &= Action( it->pContext, m_Event ); - } - } - - return result; -} - -//----------------------------------------------------------------------------- -// Name: CFsm() -// Desc: Constructor -//----------------------------------------------------------------------------- -CFsm::CFsm( void ) -{ - m_Done = false; - m_FirstState = ( unsigned int )FSM_INVALID_STATE; - m_CurrState = ( unsigned int )FSM_INVALID_STATE; -} - -//----------------------------------------------------------------------------- -// Name: ~CFsm() -// Desc: Destructor -//----------------------------------------------------------------------------- -CFsm::~CFsm( void ) -{ - Shutdown(); -} - -//----------------------------------------------------------------------------- -// Name: Setup() -// Desc: Setup events, actions and state transitions -//----------------------------------------------------------------------------- -void CFsm::Setup( void ) -{ - // Does nothing by default -} - -//----------------------------------------------------------------------------- -// Name: Reset() -// Desc: Shuts down the state machine and releases any resources -//----------------------------------------------------------------------------- -void CFsm::Shutdown( void ) -{ - // Release transitions - TransitionList::iterator itTransition = m_Transitions.begin(); - for ( ; itTransition < m_Transitions.end(); itTransition++ ) - { - CFsmTransition* pCurrTransition = *itTransition; - if ( !pCurrTransition ) continue; - - delete pCurrTransition; - } - - // Release events - EventMap::iterator itEvent = m_Events.begin(); - for( ; itEvent != m_Events.end(); itEvent++ ) - { - CFsmEvent* pCurrEvent = itEvent->second; - if ( !pCurrEvent ) continue; - - delete pCurrEvent; - } - - m_States.clear(); - m_Events.clear(); - m_Transitions.clear(); - - m_Done = false; - m_FirstState = ( unsigned int )FSM_INVALID_STATE; - m_CurrState = ( unsigned int )FSM_INVALID_STATE; -} - -//----------------------------------------------------------------------------- -// Name: AddState() -// Desc: Adds the specified state to the internal list of states -// Note: If a state with the specified ID exists, the state is not added -//----------------------------------------------------------------------------- -void CFsm::AddState( unsigned int state ) -{ - m_States.insert( state ); -} - -//----------------------------------------------------------------------------- -// Name: AddEvent() -// Desc: Adds the specified event to the internal list of events -// Note: If an eveny with the specified ID exists, the event is not added -//----------------------------------------------------------------------------- -CFsmEvent* CFsm::AddEvent( unsigned int eventType ) -{ - CFsmEvent* pEvent = NULL; - - // Lookup event by type - EventMap::iterator it = m_Events.find( eventType ); - if ( it != m_Events.end() ) - { - pEvent = it->second; - } - else - { - pEvent = new CFsmEvent( eventType ); - if ( !pEvent ) return NULL; - - // Store new event into internal map - m_Events[ eventType ] = pEvent; - } - - return pEvent; -} - -//----------------------------------------------------------------------------- -// Name: AddTransition() -// Desc: Adds a new transistion to the state machine -//----------------------------------------------------------------------------- -CFsmTransition* CFsm::AddTransition( - unsigned int state, - unsigned int eventType, - unsigned int nextState ) -{ - // Make sure we store the current state - AddState( state ); - - // Make sure we store the next state - AddState( nextState ); - - // Make sure we store the event - CFsmEvent* pEvent = AddEvent( eventType ); - if ( !pEvent ) return NULL; - - // Create new transition - CFsmTransition* pNewTransition = new CFsmTransition( state ); - if ( !pNewTransition ) return NULL; - - // Setup new transition - pNewTransition->SetEvent( pEvent ); - pNewTransition->SetNextState( nextState ); - - // Store new transition - m_Transitions.push_back( pNewTransition ); - - return pNewTransition; -} - -//----------------------------------------------------------------------------- -// Name: AddTransition() -// Desc: Adds a new transistion to the state machine -//----------------------------------------------------------------------------- -CFsmTransition* CFsm::AddTransition( - unsigned int state, - unsigned int eventType, - unsigned int nextState, - void* pAction, - void* pContext ) -{ - CFsmTransition* pTransition = AddTransition( state, eventType, nextState ); - if ( !pTransition ) return NULL; - - // If action specified, register it - if ( pAction ) - pTransition->RegisterAction( pAction, pContext ); - - return pTransition; -} - -//----------------------------------------------------------------------------- -// Name: GetTransition() -// Desc: Lookup transition given the state, event and next state to transition -//----------------------------------------------------------------------------- -CFsmTransition* CFsm::GetTransition( - unsigned int state, - unsigned int eventType ) const -{ - // Valid state? - if ( !IsValidState( state ) ) return NULL; - - // Valid event? - if ( !IsValidEvent( eventType ) ) return NULL; - - // Loop through the list of transitions - TransitionList::const_iterator it = m_Transitions.begin(); - for ( ; it != m_Transitions.end(); it++ ) - { - CFsmTransition* pCurrTransition = *it; - if ( !pCurrTransition ) continue; - - CFsmEvent* pCurrEvent = pCurrTransition->GetEvent(); - if ( !pCurrEvent ) continue; - - // Is it our transition? - if ( pCurrTransition->GetCurrState() == state && - pCurrEvent->GetType() == eventType ) - { - return pCurrTransition; - } - } - - // No transition found - return NULL; -} - -//----------------------------------------------------------------------------- -// Name: SetFirstState() -// Desc: Set initial state for FSM -//----------------------------------------------------------------------------- -void CFsm::SetFirstState( unsigned int firstState ) -{ - m_FirstState = firstState; -} - -//----------------------------------------------------------------------------- -// Name: SetCurrState() -// Desc: Set current state and update last state to current state -//----------------------------------------------------------------------------- -void CFsm::SetCurrState( unsigned int state ) -{ - m_CurrState = state; -} - -//----------------------------------------------------------------------------- -// Name: IsFirstTime() -// Desc: Verifies if the state machine has been already updated -//----------------------------------------------------------------------------- -bool CFsm::IsFirstTime( void ) const -{ - return ( m_CurrState == FSM_INVALID_STATE ); -} - -//----------------------------------------------------------------------------- -// Name: Update() -// Desc: Updates state machine and retrieves next state -//----------------------------------------------------------------------------- -bool CFsm::Update( unsigned int eventType, void* pEventParam ) -{ - // Valid event? - if ( !IsValidEvent( eventType ) ) - return false; - - // First time update? - if ( IsFirstTime() ) - m_CurrState = m_FirstState; - - // Lookup transition - CFsmTransition* pTransition = GetTransition( m_CurrState, eventType ); - if ( !pTransition ) return false; - - // Setup event parameter - EventMap::iterator it = m_Events.find( eventType ); - if ( it != m_Events.end() ) - { - CFsmEvent* pEvent = it->second; - if ( pEvent ) pEvent->SetParamRef( pEventParam ); - } - - // Valid transition? - if ( !pTransition->ApplyConditions() ) return false; - - // Run transition actions - if ( !pTransition->RunActions() ) return false; - - // Switch state - SetCurrState( pTransition->GetNextState() ); - - return true; -} - -//----------------------------------------------------------------------------- -// Name: IsDone() -// Desc: Tests whether the state machine has finished its work -// Note: This is state machine specific -//----------------------------------------------------------------------------- -bool CFsm::IsDone( void ) const -{ - // By default the internal flag m_Done is tested - return m_Done; -} - -//----------------------------------------------------------------------------- -// Name: IsValidState() -// Desc: Verifies whether the specified state is managed by FSM -//----------------------------------------------------------------------------- -bool CFsm::IsValidState( unsigned int state ) const -{ - StateSet::const_iterator it = m_States.find( state ); - if ( it == m_States.end() ) return false; - - return true; -} - -//----------------------------------------------------------------------------- -// Name: IsValidEvent() -// Desc: Verifies whether the specified event is managed by FSM -//----------------------------------------------------------------------------- -bool CFsm::IsValidEvent( unsigned int eventType ) const -{ - EventMap::const_iterator it = m_Events.find( eventType ); - if ( it == m_Events.end() ) return false; - - return true; -} +/** + *----------------------------------------------------------------------------- + * FILE : fsm.cpp + * PROJECT : 0 A.D. + * DESCRIPTION : Finite state machine class implementation + *----------------------------------------------------------------------------- + */ + +// INCLUDES +#include "precompiled.h" +#include "fsm.h" + +// DECLARATIONS + +//----------------------------------------------------------------------------- +// Name: CFsmEvent() +// Desc: Constructor +//----------------------------------------------------------------------------- +CFsmEvent::CFsmEvent( unsigned int type ) +{ + m_Type = type; + m_Param = NULL; +} + +//----------------------------------------------------------------------------- +// Name; ~CFsmEvent() +// Desc: Destructor +//----------------------------------------------------------------------------- +CFsmEvent::~CFsmEvent( void ) +{ + m_Param = NULL; +} + +//----------------------------------------------------------------------------- +// Name: SetParamRef() +// Desc: Sets the parameter for the event +//----------------------------------------------------------------------------- +void CFsmEvent::SetParamRef( void* pParam ) +{ + m_Param = pParam; +} + +//----------------------------------------------------------------------------- +// Name: CFsmTransition() +// Desc: Constructor +//----------------------------------------------------------------------------- +CFsmTransition::CFsmTransition( unsigned int state ) +{ + m_CurrState = state; +} + +//----------------------------------------------------------------------------- +// Name: ~CFsmTransition() +// Desc: Destructor +//----------------------------------------------------------------------------- +CFsmTransition::~CFsmTransition( void ) +{ + m_Actions.clear(); + m_Conditions.clear(); +} + +//----------------------------------------------------------------------------- +// Name: RegisterAction() +// Desc: Adds action that will be executed when the transition will occur +//----------------------------------------------------------------------------- +void CFsmTransition::RegisterAction( void* pAction, void* pContext ) +{ + CallbackFunction callback; + + // Add action at the end of actions list + callback.pFunction = pAction; + callback.pContext = pContext; + + m_Actions.push_back( callback ); +} + +//----------------------------------------------------------------------------- +// Name: AddCondition() +// Desc: Adds condition which will be evaluated when the transition will occurs +//----------------------------------------------------------------------------- +void CFsmTransition::RegisterCondition( void* pCondition, void* pContext ) +{ + CallbackFunction callback; + + // Add condition at the end of conditions list + callback.pFunction = pCondition; + callback.pContext = pContext; + + m_Conditions.push_back( callback ); +} + +//----------------------------------------------------------------------------- +// Name: SetEvent() +// Desc: Set event for which transition will occur +//----------------------------------------------------------------------------- +void CFsmTransition::SetEvent( CFsmEvent* pEvent ) +{ + m_Event = pEvent; +} + +//----------------------------------------------------------------------------- +// Name: SetNextState() +// Desc: Set next state the transition will switch the system to +//----------------------------------------------------------------------------- +void CFsmTransition::SetNextState( unsigned int nextState ) +{ + m_NextState = nextState; +} + +//----------------------------------------------------------------------------- +// Name: ApplyConditions() +// Desc: Evaluate conditions for the transition +// Note: If there are no conditions, assume true +//----------------------------------------------------------------------------- +bool CFsmTransition::ApplyConditions( void ) const +{ + bool eval = true; + + CallbackList::const_iterator it = m_Conditions.begin(); + for( ; it != m_Conditions.end(); it++ ) + { + if ( it->pFunction ) + { + // Evaluate condition + CONDITION Condition = ( CONDITION )it->pFunction; + eval &= Condition( it->pContext ); + } + } + + return eval; +} + +//----------------------------------------------------------------------------- +// Name: RunActions() +// Desc: Execur actions for the transition +// Note: If there are no actions, assume true +//----------------------------------------------------------------------------- +bool CFsmTransition::RunActions( void ) const +{ + bool result = true; + + CallbackList::const_iterator it = m_Actions.begin(); + for( ; it != m_Actions.end(); it++ ) + { + if ( it->pFunction ) + { + // Run action + ACTION Action = ( ACTION )it->pFunction; + result &= Action( it->pContext, m_Event ); + } + } + + return result; +} + +//----------------------------------------------------------------------------- +// Name: CFsm() +// Desc: Constructor +//----------------------------------------------------------------------------- +CFsm::CFsm( void ) +{ + m_Done = false; + m_FirstState = ( unsigned int )FSM_INVALID_STATE; + m_CurrState = ( unsigned int )FSM_INVALID_STATE; +} + +//----------------------------------------------------------------------------- +// Name: ~CFsm() +// Desc: Destructor +//----------------------------------------------------------------------------- +CFsm::~CFsm( void ) +{ + Shutdown(); +} + +//----------------------------------------------------------------------------- +// Name: Setup() +// Desc: Setup events, actions and state transitions +//----------------------------------------------------------------------------- +void CFsm::Setup( void ) +{ + // Does nothing by default +} + +//----------------------------------------------------------------------------- +// Name: Reset() +// Desc: Shuts down the state machine and releases any resources +//----------------------------------------------------------------------------- +void CFsm::Shutdown( void ) +{ + // Release transitions + TransitionList::iterator itTransition = m_Transitions.begin(); + for ( ; itTransition < m_Transitions.end(); itTransition++ ) + { + CFsmTransition* pCurrTransition = *itTransition; + if ( !pCurrTransition ) continue; + + delete pCurrTransition; + } + + // Release events + EventMap::iterator itEvent = m_Events.begin(); + for( ; itEvent != m_Events.end(); itEvent++ ) + { + CFsmEvent* pCurrEvent = itEvent->second; + if ( !pCurrEvent ) continue; + + delete pCurrEvent; + } + + m_States.clear(); + m_Events.clear(); + m_Transitions.clear(); + + m_Done = false; + m_FirstState = ( unsigned int )FSM_INVALID_STATE; + m_CurrState = ( unsigned int )FSM_INVALID_STATE; +} + +//----------------------------------------------------------------------------- +// Name: AddState() +// Desc: Adds the specified state to the internal list of states +// Note: If a state with the specified ID exists, the state is not added +//----------------------------------------------------------------------------- +void CFsm::AddState( unsigned int state ) +{ + m_States.insert( state ); +} + +//----------------------------------------------------------------------------- +// Name: AddEvent() +// Desc: Adds the specified event to the internal list of events +// Note: If an eveny with the specified ID exists, the event is not added +//----------------------------------------------------------------------------- +CFsmEvent* CFsm::AddEvent( unsigned int eventType ) +{ + CFsmEvent* pEvent = NULL; + + // Lookup event by type + EventMap::iterator it = m_Events.find( eventType ); + if ( it != m_Events.end() ) + { + pEvent = it->second; + } + else + { + pEvent = new CFsmEvent( eventType ); + if ( !pEvent ) return NULL; + + // Store new event into internal map + m_Events[ eventType ] = pEvent; + } + + return pEvent; +} + +//----------------------------------------------------------------------------- +// Name: AddTransition() +// Desc: Adds a new transistion to the state machine +//----------------------------------------------------------------------------- +CFsmTransition* CFsm::AddTransition( + unsigned int state, + unsigned int eventType, + unsigned int nextState ) +{ + // Make sure we store the current state + AddState( state ); + + // Make sure we store the next state + AddState( nextState ); + + // Make sure we store the event + CFsmEvent* pEvent = AddEvent( eventType ); + if ( !pEvent ) return NULL; + + // Create new transition + CFsmTransition* pNewTransition = new CFsmTransition( state ); + if ( !pNewTransition ) return NULL; + + // Setup new transition + pNewTransition->SetEvent( pEvent ); + pNewTransition->SetNextState( nextState ); + + // Store new transition + m_Transitions.push_back( pNewTransition ); + + return pNewTransition; +} + +//----------------------------------------------------------------------------- +// Name: AddTransition() +// Desc: Adds a new transistion to the state machine +//----------------------------------------------------------------------------- +CFsmTransition* CFsm::AddTransition( + unsigned int state, + unsigned int eventType, + unsigned int nextState, + void* pAction, + void* pContext ) +{ + CFsmTransition* pTransition = AddTransition( state, eventType, nextState ); + if ( !pTransition ) return NULL; + + // If action specified, register it + if ( pAction ) + pTransition->RegisterAction( pAction, pContext ); + + return pTransition; +} + +//----------------------------------------------------------------------------- +// Name: GetTransition() +// Desc: Lookup transition given the state, event and next state to transition +//----------------------------------------------------------------------------- +CFsmTransition* CFsm::GetTransition( + unsigned int state, + unsigned int eventType ) const +{ + // Valid state? + if ( !IsValidState( state ) ) return NULL; + + // Valid event? + if ( !IsValidEvent( eventType ) ) return NULL; + + // Loop through the list of transitions + TransitionList::const_iterator it = m_Transitions.begin(); + for ( ; it != m_Transitions.end(); it++ ) + { + CFsmTransition* pCurrTransition = *it; + if ( !pCurrTransition ) continue; + + CFsmEvent* pCurrEvent = pCurrTransition->GetEvent(); + if ( !pCurrEvent ) continue; + + // Is it our transition? + if ( pCurrTransition->GetCurrState() == state && + pCurrEvent->GetType() == eventType ) + { + return pCurrTransition; + } + } + + // No transition found + return NULL; +} + +//----------------------------------------------------------------------------- +// Name: SetFirstState() +// Desc: Set initial state for FSM +//----------------------------------------------------------------------------- +void CFsm::SetFirstState( unsigned int firstState ) +{ + m_FirstState = firstState; +} + +//----------------------------------------------------------------------------- +// Name: SetCurrState() +// Desc: Set current state and update last state to current state +//----------------------------------------------------------------------------- +void CFsm::SetCurrState( unsigned int state ) +{ + m_CurrState = state; +} + +//----------------------------------------------------------------------------- +// Name: IsFirstTime() +// Desc: Verifies if the state machine has been already updated +//----------------------------------------------------------------------------- +bool CFsm::IsFirstTime( void ) const +{ + return ( m_CurrState == FSM_INVALID_STATE ); +} + +//----------------------------------------------------------------------------- +// Name: Update() +// Desc: Updates state machine and retrieves next state +//----------------------------------------------------------------------------- +bool CFsm::Update( unsigned int eventType, void* pEventParam ) +{ + // Valid event? + if ( !IsValidEvent( eventType ) ) + return false; + + // First time update? + if ( IsFirstTime() ) + m_CurrState = m_FirstState; + + // Lookup transition + CFsmTransition* pTransition = GetTransition( m_CurrState, eventType ); + if ( !pTransition ) return false; + + // Setup event parameter + EventMap::iterator it = m_Events.find( eventType ); + if ( it != m_Events.end() ) + { + CFsmEvent* pEvent = it->second; + if ( pEvent ) pEvent->SetParamRef( pEventParam ); + } + + // Valid transition? + if ( !pTransition->ApplyConditions() ) return false; + + // Run transition actions + if ( !pTransition->RunActions() ) return false; + + // Switch state + SetCurrState( pTransition->GetNextState() ); + + return true; +} + +//----------------------------------------------------------------------------- +// Name: IsDone() +// Desc: Tests whether the state machine has finished its work +// Note: This is state machine specific +//----------------------------------------------------------------------------- +bool CFsm::IsDone( void ) const +{ + // By default the internal flag m_Done is tested + return m_Done; +} + +//----------------------------------------------------------------------------- +// Name: IsValidState() +// Desc: Verifies whether the specified state is managed by FSM +//----------------------------------------------------------------------------- +bool CFsm::IsValidState( unsigned int state ) const +{ + StateSet::const_iterator it = m_States.find( state ); + if ( it == m_States.end() ) return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Name: IsValidEvent() +// Desc: Verifies whether the specified event is managed by FSM +//----------------------------------------------------------------------------- +bool CFsm::IsValidEvent( unsigned int eventType ) const +{ + EventMap::const_iterator it = m_Events.find( eventType ); + if ( it == m_Events.end() ) return false; + + return true; +} diff --git a/source/network/fsm.h b/source/network/fsm.h index f52da49a88..6fb0c4d03b 100644 --- a/source/network/fsm.h +++ b/source/network/fsm.h @@ -1,193 +1,193 @@ -/** - *----------------------------------------------------------------------------- - * FILE : fsm.h - * PROJECT : 0 A.D. - * DESCRIPTION : Finite state machine class definitions - *----------------------------------------------------------------------------- - */ - -#ifndef FSM_H -#define FSM_H - -// INCLUDES -#include -#include - -// DECLARATIONS -#define FSM_INVALID_STATE ( unsigned int )( ~0 ) - -class CFsmEvent; -class CFsmTransition; -class CFsm; - -typedef bool ( *CONDITION ) ( void* pContext ); -typedef bool ( *ACTION ) ( void* pContext, const CFsmEvent* pEvent ); - -typedef struct -{ - void* pFunction; - void* pContext; - -} CallbackFunction; - -typedef std::set< unsigned int > StateSet; -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 occured. - NOTES : The CFsmEvent objects are under the control of CFsm so - they are created and deleted via CFsm. -*/ - -class CFsmEvent -{ -public: - - CFsmEvent( unsigned int type ); - ~CFsmEvent( void ); - - unsigned int GetType ( void ) const { return m_Type; } - void* GetParamRef ( void ) { return m_Param; } - void SetParamRef ( void* pParam ); - -protected: - -private: - - // Not implemented - CFsmEvent( const CFsmEvent& ); - CFsmEvent& operator=( const CFsmEvent& ); - - unsigned int m_Type; // Event type - void* m_Param; // Event paramater -}; - - -/* - CLASS : CFsmTransition - DESCRIPTION : The CFsmTransition class is an association of event, condition, - action and next state. - NOTES : -*/ - -class CFsmTransition -{ -public: - - CFsmTransition( unsigned int state ); - ~CFsmTransition( void ); - - void RegisterAction ( - void* pAction, - void* pContext ); - void RegisterCondition ( - void* pCondition, - void* pContext ); - void SetEvent ( CFsmEvent* pEvent ); - CFsmEvent* GetEvent ( void ) const { return m_Event; } - void SetNextState ( unsigned int nextState ); - unsigned int GetNextState ( void ) const { return m_NextState; } - unsigned int GetCurrState ( void ) const { return m_CurrState; } - const CallbackList& GetActions ( void ) const { return m_Actions; } - const CallbackList& GetConditions ( void ) const { return m_Conditions; } - bool ApplyConditions ( void ) const; - bool RunActions ( void ) const; - -protected: - -private: - - // Not implemented - CFsmTransition( const CFsmTransition& ); - CFsmTransition& operator=( const CFsmTransition& ); - - unsigned int m_CurrState; // Current state - unsigned int m_NextState; // Next state - CFsmEvent* m_Event; // Transition event - CallbackList m_Actions; // List of actions for transition - 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 -*/ - -class CFsm -{ -public: - - CFsm( void ); - ~CFsm( void ); - - /** - * 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 ); - - void AddState ( unsigned int state ); - CFsmEvent* AddEvent ( unsigned int eventType ); - CFsmTransition* AddTransition ( - unsigned int state, - unsigned int eventType, - unsigned int nextState ); - CFsmTransition* AddTransition ( - unsigned int state, - unsigned int eventType, - unsigned int nextState, - void* pAction, - void* pContext ); - CFsmTransition* GetTransition ( - unsigned int state, - unsigned int eventType ) const; - CFsmTransition* GetEventTransition ( unsigned int eventType ) const; - void SetFirstState ( unsigned int firstState ); - unsigned int GetCurrState ( void ) const { return m_CurrState; } - const StateSet& GetStates ( void ) const { return m_States; } - const EventMap& GetEvents ( void ) const { return m_Events; } - const TransitionList& GetTransitions ( void ) const { return m_Transitions; } - bool Update ( unsigned int eventType, void* pEventData ); - bool IsValidState ( unsigned int state ) const; - bool IsValidEvent ( unsigned int eventType ) const; - virtual bool IsDone ( void ) const; - -protected: - -private: - - // Not implemented - CFsm( const CFsm& ); - CFsm& operator=( const CFsm& ); - - void SetCurrState ( unsigned int state ); - bool IsFirstTime ( void ) const; - - bool m_Done; // FSM work is done - unsigned int m_FirstState; // Initial state - unsigned int m_CurrState; // Current state - StateSet m_States; // List of states - EventMap m_Events; // List of events - TransitionList m_Transitions; // State transitions -}; - -#endif // FSM_H - +/** + *----------------------------------------------------------------------------- + * FILE : fsm.h + * PROJECT : 0 A.D. + * DESCRIPTION : Finite state machine class definitions + *----------------------------------------------------------------------------- + */ + +#ifndef FSM_H +#define FSM_H + +// INCLUDES +#include +#include + +// DECLARATIONS +#define FSM_INVALID_STATE ( unsigned int )( ~0 ) + +class CFsmEvent; +class CFsmTransition; +class CFsm; + +typedef bool ( *CONDITION ) ( void* pContext ); +typedef bool ( *ACTION ) ( void* pContext, const CFsmEvent* pEvent ); + +typedef struct +{ + void* pFunction; + void* pContext; + +} CallbackFunction; + +typedef std::set< unsigned int > StateSet; +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 occured. + NOTES : The CFsmEvent objects are under the control of CFsm so + they are created and deleted via CFsm. +*/ + +class CFsmEvent +{ +public: + + CFsmEvent( unsigned int type ); + ~CFsmEvent( void ); + + unsigned int GetType ( void ) const { return m_Type; } + void* GetParamRef ( void ) { return m_Param; } + void SetParamRef ( void* pParam ); + +protected: + +private: + + // Not implemented + CFsmEvent( const CFsmEvent& ); + CFsmEvent& operator=( const CFsmEvent& ); + + unsigned int m_Type; // Event type + void* m_Param; // Event paramater +}; + + +/* + CLASS : CFsmTransition + DESCRIPTION : The CFsmTransition class is an association of event, condition, + action and next state. + NOTES : +*/ + +class CFsmTransition +{ +public: + + CFsmTransition( unsigned int state ); + ~CFsmTransition( void ); + + void RegisterAction ( + void* pAction, + void* pContext ); + void RegisterCondition ( + void* pCondition, + void* pContext ); + void SetEvent ( CFsmEvent* pEvent ); + CFsmEvent* GetEvent ( void ) const { return m_Event; } + void SetNextState ( unsigned int nextState ); + unsigned int GetNextState ( void ) const { return m_NextState; } + unsigned int GetCurrState ( void ) const { return m_CurrState; } + const CallbackList& GetActions ( void ) const { return m_Actions; } + const CallbackList& GetConditions ( void ) const { return m_Conditions; } + bool ApplyConditions ( void ) const; + bool RunActions ( void ) const; + +protected: + +private: + + // Not implemented + CFsmTransition( const CFsmTransition& ); + CFsmTransition& operator=( const CFsmTransition& ); + + unsigned int m_CurrState; // Current state + unsigned int m_NextState; // Next state + CFsmEvent* m_Event; // Transition event + CallbackList m_Actions; // List of actions for transition + 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 +*/ + +class CFsm +{ +public: + + CFsm( void ); + ~CFsm( void ); + + /** + * 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 ); + + void AddState ( unsigned int state ); + CFsmEvent* AddEvent ( unsigned int eventType ); + CFsmTransition* AddTransition ( + unsigned int state, + unsigned int eventType, + unsigned int nextState ); + CFsmTransition* AddTransition ( + unsigned int state, + unsigned int eventType, + unsigned int nextState, + void* pAction, + void* pContext ); + CFsmTransition* GetTransition ( + unsigned int state, + unsigned int eventType ) const; + CFsmTransition* GetEventTransition ( unsigned int eventType ) const; + void SetFirstState ( unsigned int firstState ); + unsigned int GetCurrState ( void ) const { return m_CurrState; } + const StateSet& GetStates ( void ) const { return m_States; } + const EventMap& GetEvents ( void ) const { return m_Events; } + const TransitionList& GetTransitions ( void ) const { return m_Transitions; } + bool Update ( unsigned int eventType, void* pEventData ); + bool IsValidState ( unsigned int state ) const; + bool IsValidEvent ( unsigned int eventType ) const; + virtual bool IsDone ( void ) const; + +protected: + +private: + + // Not implemented + CFsm( const CFsm& ); + CFsm& operator=( const CFsm& ); + + void SetCurrState ( unsigned int state ); + bool IsFirstTime ( void ) const; + + bool m_Done; // FSM work is done + unsigned int m_FirstState; // Initial state + unsigned int m_CurrState; // Current state + StateSet m_States; // List of states + EventMap m_Events; // List of events + TransitionList m_Transitions; // State transitions +}; + +#endif // FSM_H + diff --git a/source/pch/atlas/precompiled.h b/source/pch/atlas/precompiled.h index de65d5782d..c79bccb7ae 100644 --- a/source/pch/atlas/precompiled.h +++ b/source/pch/atlas/precompiled.h @@ -1,10 +1,10 @@ -#include "lib/precompiled.h" // common precompiled header - -// Atlas-specific PCH: - -#if HAVE_PCH - -#include "tools/atlas/GameInterface/Messages.h" -#include "ps/CStr.h" - -#endif // HAVE_PCH +#include "lib/precompiled.h" // common precompiled header + +// Atlas-specific PCH: + +#if HAVE_PCH + +#include "tools/atlas/GameInterface/Messages.h" +#include "ps/CStr.h" + +#endif // HAVE_PCH diff --git a/source/pch/engine/precompiled.h b/source/pch/engine/precompiled.h index 3b7df3c568..f698a8c613 100644 --- a/source/pch/engine/precompiled.h +++ b/source/pch/engine/precompiled.h @@ -1,17 +1,17 @@ -#include "lib/precompiled.h" // common precompiled header - -// "engine"-specific PCH: - -#include "ps/Pyrogenesis.h" // MICROLOG and old error system - -#if HAVE_PCH - -// some other external libraries that are used in several places: -// .. CStr is included very frequently, so a reasonable amount of time is -// saved by including it here. (~10% in a full rebuild, as of r2365) -#include "ps/CStr.h" -#include "scripting/SpiderMonkey.h" -#include -#include - -#endif // HAVE_PCH +#include "lib/precompiled.h" // common precompiled header + +// "engine"-specific PCH: + +#include "ps/Pyrogenesis.h" // MICROLOG and old error system + +#if HAVE_PCH + +// some other external libraries that are used in several places: +// .. CStr is included very frequently, so a reasonable amount of time is +// saved by including it here. (~10% in a full rebuild, as of r2365) +#include "ps/CStr.h" +#include "scripting/SpiderMonkey.h" +#include +#include + +#endif // HAVE_PCH diff --git a/source/pch/graphics/precompiled.h b/source/pch/graphics/precompiled.h index 76419330a7..72cf298213 100644 --- a/source/pch/graphics/precompiled.h +++ b/source/pch/graphics/precompiled.h @@ -1,3 +1,3 @@ -#include "lib/precompiled.h" // common precompiled header - -// "graphics"-specific PCH: +#include "lib/precompiled.h" // common precompiled header + +// "graphics"-specific PCH: diff --git a/source/pch/gui/precompiled.h b/source/pch/gui/precompiled.h index a4a45e3511..5d27684d53 100644 --- a/source/pch/gui/precompiled.h +++ b/source/pch/gui/precompiled.h @@ -1,3 +1,3 @@ -#include "lib/precompiled.h" // common precompiled header - -// "gui"-specific PCH: +#include "lib/precompiled.h" // common precompiled header + +// "gui"-specific PCH: diff --git a/source/pch/i18n/precompiled.h b/source/pch/i18n/precompiled.h index b3caac94b8..87ed65fb81 100644 --- a/source/pch/i18n/precompiled.h +++ b/source/pch/i18n/precompiled.h @@ -1,3 +1,3 @@ -#include "lib/precompiled.h" // common precompiled header - -// "i18n"-specific PCH: +#include "lib/precompiled.h" // common precompiled header + +// "i18n"-specific PCH: diff --git a/source/pch/lowlevel/precompiled.h b/source/pch/lowlevel/precompiled.h index d69035cefd..61907f788c 100644 --- a/source/pch/lowlevel/precompiled.h +++ b/source/pch/lowlevel/precompiled.h @@ -1,3 +1,3 @@ -#include "lib/precompiled.h" // common precompiled header - -// "lowlevel"-specific PCH: +#include "lib/precompiled.h" // common precompiled header + +// "lowlevel"-specific PCH: diff --git a/source/pch/network/precompiled.cpp b/source/pch/network/precompiled.cpp index 7ff8df544a..5f656a45da 100644 --- a/source/pch/network/precompiled.cpp +++ b/source/pch/network/precompiled.cpp @@ -1 +1 @@ -#include "precompiled.h" +#include "precompiled.h" diff --git a/source/pch/network/precompiled.h b/source/pch/network/precompiled.h index 560701eab4..8fa2fd34da 100644 --- a/source/pch/network/precompiled.h +++ b/source/pch/network/precompiled.h @@ -1,7 +1,7 @@ -#include "lib/precompiled.h" // common precompiled header - -// network-specific PCH: - -#if HAVE_PCH - -#endif // HAVE_PCH +#include "lib/precompiled.h" // common precompiled header + +// network-specific PCH: + +#if HAVE_PCH + +#endif // HAVE_PCH diff --git a/source/pch/se/precompiled.cpp b/source/pch/se/precompiled.cpp index 7ff8df544a..5f656a45da 100644 --- a/source/pch/se/precompiled.cpp +++ b/source/pch/se/precompiled.cpp @@ -1 +1 @@ -#include "precompiled.h" +#include "precompiled.h" diff --git a/source/pch/se/precompiled.h b/source/pch/se/precompiled.h index 25eb215657..eb84f4e01c 100644 --- a/source/pch/se/precompiled.h +++ b/source/pch/se/precompiled.h @@ -1,7 +1,7 @@ -#include "lib/precompiled.h" // common precompiled header - -// se PCH: - -#if HAVE_PCH - -#endif // HAVE_PCH +#include "lib/precompiled.h" // common precompiled header + +// se PCH: + +#if HAVE_PCH + +#endif // HAVE_PCH diff --git a/source/ps/Filesystem.cpp b/source/ps/Filesystem.cpp index 429f7cccd7..63817bae37 100644 --- a/source/ps/Filesystem.cpp +++ b/source/ps/Filesystem.cpp @@ -1,71 +1,71 @@ -#include "precompiled.h" -#include "Filesystem.h" - -#include "ps/CLogger.h" - -#define LOG_CATEGORY "file" - - -PIVFS g_VFS; - -bool FileExists(const char* pathname) -{ - return g_VFS->GetFileInfo(pathname, 0) == INFO::OK; -} - -bool FileExists(const VfsPath& pathname) -{ - return g_VFS->GetFileInfo(pathname, 0) == INFO::OK; -} - - - -CVFSFile::CVFSFile() -{ -} - -CVFSFile::~CVFSFile() -{ -} - -PSRETURN CVFSFile::Load(const VfsPath& filename) -{ - // Load should never be called more than once, so complain - if (m_Buffer) - { - debug_assert(0); - return PSRETURN_CVFSFile_AlreadyLoaded; - } - - LibError ret = g_VFS->LoadFile(filename, m_Buffer, m_BufferSize); - if (ret != INFO::OK) - { - LOG(CLogger::Error, LOG_CATEGORY, "CVFSFile: file %s couldn't be opened (vfs_load: %d)", filename.string().c_str(), ret); - return PSRETURN_CVFSFile_LoadFailed; - } - - return PSRETURN_OK; -} - -const u8* CVFSFile::GetBuffer() const -{ - // Die in a very obvious way, to avoid subtle problems caused by - // accidentally forgetting to check that the open succeeded - if (!m_Buffer) - { - debug_warn("GetBuffer() called with no file loaded"); - throw PSERROR_CVFSFile_InvalidBufferAccess(); - } - - return m_Buffer.get(); -} - -size_t CVFSFile::GetBufferSize() const -{ - return m_BufferSize; -} - -CStr CVFSFile::GetAsString() const -{ - return std::string((char*)GetBuffer(), GetBufferSize()); -} +#include "precompiled.h" +#include "Filesystem.h" + +#include "ps/CLogger.h" + +#define LOG_CATEGORY "file" + + +PIVFS g_VFS; + +bool FileExists(const char* pathname) +{ + return g_VFS->GetFileInfo(pathname, 0) == INFO::OK; +} + +bool FileExists(const VfsPath& pathname) +{ + return g_VFS->GetFileInfo(pathname, 0) == INFO::OK; +} + + + +CVFSFile::CVFSFile() +{ +} + +CVFSFile::~CVFSFile() +{ +} + +PSRETURN CVFSFile::Load(const VfsPath& filename) +{ + // Load should never be called more than once, so complain + if (m_Buffer) + { + debug_assert(0); + return PSRETURN_CVFSFile_AlreadyLoaded; + } + + LibError ret = g_VFS->LoadFile(filename, m_Buffer, m_BufferSize); + if (ret != INFO::OK) + { + LOG(CLogger::Error, LOG_CATEGORY, "CVFSFile: file %s couldn't be opened (vfs_load: %d)", filename.string().c_str(), ret); + return PSRETURN_CVFSFile_LoadFailed; + } + + return PSRETURN_OK; +} + +const u8* CVFSFile::GetBuffer() const +{ + // Die in a very obvious way, to avoid subtle problems caused by + // accidentally forgetting to check that the open succeeded + if (!m_Buffer) + { + debug_warn("GetBuffer() called with no file loaded"); + throw PSERROR_CVFSFile_InvalidBufferAccess(); + } + + return m_Buffer.get(); +} + +size_t CVFSFile::GetBufferSize() const +{ + return m_BufferSize; +} + +CStr CVFSFile::GetAsString() const +{ + return std::string((char*)GetBuffer(), GetBufferSize()); +} diff --git a/source/ps/Filesystem.h b/source/ps/Filesystem.h index 43a5241365..c1f59bbbe9 100644 --- a/source/ps/Filesystem.h +++ b/source/ps/Filesystem.h @@ -1,45 +1,45 @@ -#ifndef INCLUDED_FILESYSTEM -#define INCLUDED_FILESYSTEM - -#include "lib/path_util.h" -#include "lib/file/file.h" -#include "lib/file/io/io.h" -#include "lib/file/vfs/vfs.h" -#include "lib/file/file_system_util.h" -#include "lib/file/io/write_buffer.h" - -#include "ps/CStr.h" -#include "ps/Errors.h" - -extern PIVFS g_VFS; - -extern bool FileExists(const char* pathname); -extern bool FileExists(const VfsPath& pathname); - -ERROR_GROUP(CVFSFile); -ERROR_TYPE(CVFSFile, LoadFailed); -ERROR_TYPE(CVFSFile, AlreadyLoaded); -ERROR_TYPE(CVFSFile, InvalidBufferAccess); - -// Reads a file, then gives read-only access to the contents -class CVFSFile -{ -public: - CVFSFile(); - ~CVFSFile(); - - // Returns either PSRETURN_OK or PSRETURN_CVFSFile_LoadFailed. - // Dies if a file has already been successfully loaded. - PSRETURN Load(const VfsPath& filename); - - // These die if called when no file has been successfully loaded. - const u8* GetBuffer() const; - size_t GetBufferSize() const; - CStr GetAsString() const; - -private: - shared_ptr m_Buffer; - size_t m_BufferSize; -}; - -#endif // #ifndef INCLUDED_FILESYSTEM +#ifndef INCLUDED_FILESYSTEM +#define INCLUDED_FILESYSTEM + +#include "lib/path_util.h" +#include "lib/file/file.h" +#include "lib/file/io/io.h" +#include "lib/file/vfs/vfs.h" +#include "lib/file/file_system_util.h" +#include "lib/file/io/write_buffer.h" + +#include "ps/CStr.h" +#include "ps/Errors.h" + +extern PIVFS g_VFS; + +extern bool FileExists(const char* pathname); +extern bool FileExists(const VfsPath& pathname); + +ERROR_GROUP(CVFSFile); +ERROR_TYPE(CVFSFile, LoadFailed); +ERROR_TYPE(CVFSFile, AlreadyLoaded); +ERROR_TYPE(CVFSFile, InvalidBufferAccess); + +// Reads a file, then gives read-only access to the contents +class CVFSFile +{ +public: + CVFSFile(); + ~CVFSFile(); + + // Returns either PSRETURN_OK or PSRETURN_CVFSFile_LoadFailed. + // Dies if a file has already been successfully loaded. + PSRETURN Load(const VfsPath& filename); + + // These die if called when no file has been successfully loaded. + const u8* GetBuffer() const; + size_t GetBufferSize() const; + CStr GetAsString() const; + +private: + shared_ptr m_Buffer; + size_t m_BufferSize; +}; + +#endif // #ifndef INCLUDED_FILESYSTEM diff --git a/source/ps/KeyName.h b/source/ps/KeyName.h index e8228f3de4..113bc25c99 100644 --- a/source/ps/KeyName.h +++ b/source/ps/KeyName.h @@ -1,8 +1,8 @@ -#ifndef INCLUDED_KEYNAME -#define INCLUDED_KEYNAME - -extern void InitKeyNameMap(); -extern CStr FindKeyName( int keycode ); -extern int FindKeyCode( const CStr& keyname ); - -#endif // #ifndef INCLUDED_KEYNAME +#ifndef INCLUDED_KEYNAME +#define INCLUDED_KEYNAME + +extern void InitKeyNameMap(); +extern CStr FindKeyName( int keycode ); +extern int FindKeyCode( const CStr& keyname ); + +#endif // #ifndef INCLUDED_KEYNAME diff --git a/source/ps/XML/tests/test_XMLWriter.h b/source/ps/XML/tests/test_XMLWriter.h index f210f41cf8..c4b4cfa053 100644 --- a/source/ps/XML/tests/test_XMLWriter.h +++ b/source/ps/XML/tests/test_XMLWriter.h @@ -1,211 +1,211 @@ -#include "lib/self_test.h" - -#include "ps/XML/XMLWriter.h" - -class TestXmlWriter : public CxxTest::TestSuite -{ -public: - void test1() - { - XML_Start(); - - { - XML_Element("Root"); - { - XML_Comment("Comment test."); - XML_Comment("Comment test again."); - { - XML_Element("a"); - XML_Attribute("one", 1); - XML_Attribute("two", "TWO"); - XML_Text("b"); - XML_Text(" (etc)"); - } - { - XML_Element("c"); - XML_Text("d"); - } - XML_Setting("c2", "d2"); - { - XML_Element("e"); - { - { - XML_Element("f"); - XML_Text("g"); - } - { - XML_Element("h"); - } - { - XML_Element("i"); - XML_Attribute("j", 1.23); - { - XML_Element("k"); - XML_Attribute("l", 2.34); - XML_Text("m"); - } - } - } - } - } - } - - CStr output = XML_GetOutput(); - TS_ASSERT_STR_EQUALS(output, - "\n" - "\n" - "\n" - "\t\n" - "\t\n" - "\tb (etc)\n" - "\td\n" - "\td2\n" - "\t\n" - "\t\tg\n" - "\t\t\n" - "\t\t\n" - "\t\t\tm\n" - "\t\t\n" - "\t\n" - "" - ); - } - - void test_basic() - { - XML_Start(); - - { - XML_Element("Test"); - { - XML_Element("example"); - { - XML_Element("content"); - XML_Text("text"); - } - } - } - - CStr output = XML_GetOutput(); - TS_ASSERT_STR_EQUALS(output, - "\n" - "\n" - "\n" - "\t\n" - "\t\ttext\n" - "\t\n" - "" - ); - } - - void test_nonpretty() - { - XML_Start(); - XML_SetPrettyPrint(false); - - { - XML_Element("Test"); - { - XML_Element("example"); - { - XML_Element("content"); - XML_Text("text"); - } - } - } - - CStr output = XML_GetOutput(); - TS_ASSERT_STR_EQUALS(output, - "\n" - "text" - ); - } - - void test_text() - { - XML_Start(); - - { - XML_Element("Test"); - XML_Text("a"); - XML_Text("b"); - } - - CStr output = XML_GetOutput(); - TS_ASSERT_STR_EQUALS(output, - "\n" - "\n" - "ab" - ); - } - - - void test_utf8() - { - XML_Start(); - - { - XML_Element("Test"); - { - const wchar_t text[] = { 0x0251, 0 }; - XML_Text(text); - } - } - - CStr output = XML_GetOutput(); - TS_ASSERT_STR_EQUALS(output, - "\n\n" - "\xC9\x91" - ); - } - - void test_attr_escape() - { - XML_Start(); - - { - XML_Element("Test"); - XML_Attribute("example", "abc > ]]> < & \"\" "); - } - - CStr output = XML_GetOutput(); - TS_ASSERT_STR_EQUALS(output, - "\n\n" - " ]]> < & "" \"/>" - ); - } - - void test_chardata_escape() - { - XML_Start(); - - { - XML_Element("Test"); - XML_Text("abc > ]]> < & \"\" "); - } - - CStr output = XML_GetOutput(); - TS_ASSERT_STR_EQUALS(output, - "\n\n" - "abc > ]]> < & \"\" " - ); - } - - void test_comment_escape() - { - XML_Start(); - - { - XML_Element("Test"); - XML_Comment("test - -- --- ---- test"); - } - - CStr output = XML_GetOutput(); - TS_ASSERT_STR_EQUALS(output, - "\n\n" - "\n" - "\t\n" - "" - ); - } -}; +#include "lib/self_test.h" + +#include "ps/XML/XMLWriter.h" + +class TestXmlWriter : public CxxTest::TestSuite +{ +public: + void test1() + { + XML_Start(); + + { + XML_Element("Root"); + { + XML_Comment("Comment test."); + XML_Comment("Comment test again."); + { + XML_Element("a"); + XML_Attribute("one", 1); + XML_Attribute("two", "TWO"); + XML_Text("b"); + XML_Text(" (etc)"); + } + { + XML_Element("c"); + XML_Text("d"); + } + XML_Setting("c2", "d2"); + { + XML_Element("e"); + { + { + XML_Element("f"); + XML_Text("g"); + } + { + XML_Element("h"); + } + { + XML_Element("i"); + XML_Attribute("j", 1.23); + { + XML_Element("k"); + XML_Attribute("l", 2.34); + XML_Text("m"); + } + } + } + } + } + } + + CStr output = XML_GetOutput(); + TS_ASSERT_STR_EQUALS(output, + "\n" + "\n" + "\n" + "\t\n" + "\t\n" + "\tb (etc)\n" + "\td\n" + "\td2\n" + "\t\n" + "\t\tg\n" + "\t\t\n" + "\t\t\n" + "\t\t\tm\n" + "\t\t\n" + "\t\n" + "" + ); + } + + void test_basic() + { + XML_Start(); + + { + XML_Element("Test"); + { + XML_Element("example"); + { + XML_Element("content"); + XML_Text("text"); + } + } + } + + CStr output = XML_GetOutput(); + TS_ASSERT_STR_EQUALS(output, + "\n" + "\n" + "\n" + "\t\n" + "\t\ttext\n" + "\t\n" + "" + ); + } + + void test_nonpretty() + { + XML_Start(); + XML_SetPrettyPrint(false); + + { + XML_Element("Test"); + { + XML_Element("example"); + { + XML_Element("content"); + XML_Text("text"); + } + } + } + + CStr output = XML_GetOutput(); + TS_ASSERT_STR_EQUALS(output, + "\n" + "text" + ); + } + + void test_text() + { + XML_Start(); + + { + XML_Element("Test"); + XML_Text("a"); + XML_Text("b"); + } + + CStr output = XML_GetOutput(); + TS_ASSERT_STR_EQUALS(output, + "\n" + "\n" + "ab" + ); + } + + + void test_utf8() + { + XML_Start(); + + { + XML_Element("Test"); + { + const wchar_t text[] = { 0x0251, 0 }; + XML_Text(text); + } + } + + CStr output = XML_GetOutput(); + TS_ASSERT_STR_EQUALS(output, + "\n\n" + "\xC9\x91" + ); + } + + void test_attr_escape() + { + XML_Start(); + + { + XML_Element("Test"); + XML_Attribute("example", "abc > ]]> < & \"\" "); + } + + CStr output = XML_GetOutput(); + TS_ASSERT_STR_EQUALS(output, + "\n\n" + " ]]> < & "" \"/>" + ); + } + + void test_chardata_escape() + { + XML_Start(); + + { + XML_Element("Test"); + XML_Text("abc > ]]> < & \"\" "); + } + + CStr output = XML_GetOutput(); + TS_ASSERT_STR_EQUALS(output, + "\n\n" + "abc > ]]> < & \"\" " + ); + } + + void test_comment_escape() + { + XML_Start(); + + { + XML_Element("Test"); + XML_Comment("test - -- --- ---- test"); + } + + CStr output = XML_GetOutput(); + TS_ASSERT_STR_EQUALS(output, + "\n\n" + "\n" + "\t\n" + "" + ); + } +}; diff --git a/source/ps/tests/stub_impl_hack.h b/source/ps/tests/stub_impl_hack.h index a774d94b4b..a969bd058c 100644 --- a/source/ps/tests/stub_impl_hack.h +++ b/source/ps/tests/stub_impl_hack.h @@ -1,16 +1,16 @@ -#include "lib/self_test.h" - -// usually defined by main.cpp, used by engine's scripting/ScriptGlue.cpp, -// must be included here to placate linker. -void kill_mainloop() -{ -} - -// just so that cxxtestgen doesn't complain "No tests defined" -class TestDummy : public CxxTest::TestSuite -{ -public: - void test_dummy() - { - } -}; +#include "lib/self_test.h" + +// usually defined by main.cpp, used by engine's scripting/ScriptGlue.cpp, +// must be included here to placate linker. +void kill_mainloop() +{ +} + +// just so that cxxtestgen doesn't complain "No tests defined" +class TestDummy : public CxxTest::TestSuite +{ +public: + void test_dummy() + { + } +}; diff --git a/source/ps/tests/test_CStr.h b/source/ps/tests/test_CStr.h index 3cfc7f579a..92546f10e5 100644 --- a/source/ps/tests/test_CStr.h +++ b/source/ps/tests/test_CStr.h @@ -1,52 +1,52 @@ -#include "lib/self_test.h" - -#include "ps/CStr.h" - -class TestCStr : public CxxTest::TestSuite -{ -public: - void test_utf8_utf16_conversion() - { - const wchar_t chr_utf16[] = { - 0x12, - 0xff, - 0x1234, - 0x3456, - 0x5678, - 0x7890, - 0x9abc, - 0xbcde, - 0xfffe - }; - const unsigned char chr_utf8[] = { - 0x12, - 0xc3, 0xbf, - 0xe1, 0x88, 0xb4, - 0xe3, 0x91, 0x96, - 0xe5, 0x99, 0xb8, - 0xe7, 0xa2, 0x90, - 0xe9, 0xaa, 0xbc, - 0xeb, 0xb3, 0x9e, - 0xef, 0xbf, 0xbe - }; - CStrW str_utf16 (chr_utf16, sizeof(chr_utf16)/sizeof(wchar_t)); - - CStr8 str_utf8 = str_utf16.ToUTF8(); - TS_ASSERT_EQUALS(str_utf8.length(), sizeof(chr_utf8)); - TS_ASSERT_SAME_DATA(str_utf8.data(), chr_utf8, sizeof(chr_utf8)); - - CStrW str_utf16b = str_utf8.FromUTF8(); - TS_ASSERT_WSTR_EQUALS(str_utf16b, str_utf16); - } - - void test_invalid_utf8() - { - const unsigned char chr_utf8_a[] = { 'a', 0xef }; - const unsigned char chr_utf8_b[] = { 'b', 0xef, 0xbf }; - const unsigned char chr_utf8_c[] = { 'c', 0xef, 0xbf, 0x01 }; - - TS_ASSERT_WSTR_EQUALS(CStr8((const char*)chr_utf8_a, sizeof(chr_utf8_a)).FromUTF8(), L""); - TS_ASSERT_WSTR_EQUALS(CStr8((const char*)chr_utf8_b, sizeof(chr_utf8_b)).FromUTF8(), L""); - TS_ASSERT_WSTR_EQUALS(CStr8((const char*)chr_utf8_c, sizeof(chr_utf8_c)).FromUTF8(), L""); - } -}; +#include "lib/self_test.h" + +#include "ps/CStr.h" + +class TestCStr : public CxxTest::TestSuite +{ +public: + void test_utf8_utf16_conversion() + { + const wchar_t chr_utf16[] = { + 0x12, + 0xff, + 0x1234, + 0x3456, + 0x5678, + 0x7890, + 0x9abc, + 0xbcde, + 0xfffe + }; + const unsigned char chr_utf8[] = { + 0x12, + 0xc3, 0xbf, + 0xe1, 0x88, 0xb4, + 0xe3, 0x91, 0x96, + 0xe5, 0x99, 0xb8, + 0xe7, 0xa2, 0x90, + 0xe9, 0xaa, 0xbc, + 0xeb, 0xb3, 0x9e, + 0xef, 0xbf, 0xbe + }; + CStrW str_utf16 (chr_utf16, sizeof(chr_utf16)/sizeof(wchar_t)); + + CStr8 str_utf8 = str_utf16.ToUTF8(); + TS_ASSERT_EQUALS(str_utf8.length(), sizeof(chr_utf8)); + TS_ASSERT_SAME_DATA(str_utf8.data(), chr_utf8, sizeof(chr_utf8)); + + CStrW str_utf16b = str_utf8.FromUTF8(); + TS_ASSERT_WSTR_EQUALS(str_utf16b, str_utf16); + } + + void test_invalid_utf8() + { + const unsigned char chr_utf8_a[] = { 'a', 0xef }; + const unsigned char chr_utf8_b[] = { 'b', 0xef, 0xbf }; + const unsigned char chr_utf8_c[] = { 'c', 0xef, 0xbf, 0x01 }; + + TS_ASSERT_WSTR_EQUALS(CStr8((const char*)chr_utf8_a, sizeof(chr_utf8_a)).FromUTF8(), L""); + TS_ASSERT_WSTR_EQUALS(CStr8((const char*)chr_utf8_b, sizeof(chr_utf8_b)).FromUTF8(), L""); + TS_ASSERT_WSTR_EQUALS(CStr8((const char*)chr_utf8_c, sizeof(chr_utf8_c)).FromUTF8(), L""); + } +}; diff --git a/source/ps/tests/test_Parser.h b/source/ps/tests/test_Parser.h index f1a1461013..1d65d82a79 100644 --- a/source/ps/tests/test_Parser.h +++ b/source/ps/tests/test_Parser.h @@ -1,95 +1,95 @@ -#include "lib/self_test.h" - -#include "ps/Parser.h" - -class TestParser : public CxxTest::TestSuite -{ -public: - void test1() - { - CParser Parser; - Parser.InputTaskType("test", "_$ident_=_$value_"); - - std::string str; - int i; - - CParserLine Line; - - TS_ASSERT(Line.ParseString(Parser, "value=23")); - - TS_ASSERT(Line.GetArgString(0, str) && str == "value"); - TS_ASSERT(Line.GetArgInt(1, i) && i == 23); - } - - - void test2() - { - CParser Parser; - Parser.InputTaskType("test", "_$value_[$value]_"); - - std::string str; - - CParserLine Line; - - TS_ASSERT(Line.ParseString(Parser, "12 34")); - TS_ASSERT_EQUALS((int)Line.GetArgCount(), 2); - TS_ASSERT(Line.GetArgString(0, str) && str == "12"); - TS_ASSERT(Line.GetArgString(1, str) && str == "34"); - - TS_ASSERT(Line.ParseString(Parser, "56")); - TS_ASSERT_EQUALS((int)Line.GetArgCount(), 1); - TS_ASSERT(Line.GetArgString(0, str) && str == "56"); - - TS_ASSERT(! Line.ParseString(Parser, " ")); - } - - - void test3() - { - CParser Parser; - Parser.InputTaskType("test", "_[$value]_[$value]_[$value]_"); - - std::string str; - - CParserLine Line; - - TS_ASSERT(Line.ParseString(Parser, "12 34 56")); - TS_ASSERT_EQUALS((int)Line.GetArgCount(), 3); - TS_ASSERT(Line.GetArgString(0, str) && str == "12"); - TS_ASSERT(Line.GetArgString(1, str) && str == "34"); - TS_ASSERT(Line.GetArgString(2, str) && str == "56"); - - TS_ASSERT(Line.ParseString(Parser, "78 90")); - TS_ASSERT_EQUALS((int)Line.GetArgCount(), 2); - TS_ASSERT(Line.GetArgString(0, str) && str == "78"); - TS_ASSERT(Line.GetArgString(1, str) && str == "90"); - - TS_ASSERT(Line.ParseString(Parser, "ab")); - TS_ASSERT_EQUALS((int)Line.GetArgCount(), 1); - TS_ASSERT(Line.GetArgString(0, str) && str == "ab"); - - TS_ASSERT(Line.ParseString(Parser, " ")); - TS_ASSERT_EQUALS((int)Line.GetArgCount(), 0); - } - - - void test4() - { - CParser Parser; - Parser.InputTaskType("test", "<[_a_][_b_]_x_>"); - - std::string str; - - CParserLine Line; - TS_ASSERT(Line.ParseString(Parser, "a b x a b x")); - TS_ASSERT(Line.ParseString(Parser, "a x b x")); - TS_ASSERT(Line.ParseString(Parser, "a x")); - TS_ASSERT(Line.ParseString(Parser, "b x")); - TS_ASSERT(Line.ParseString(Parser, "x")); - TS_ASSERT(! Line.ParseString(Parser, "a x c x")); - TS_ASSERT(! Line.ParseString(Parser, "a b a x")); - TS_ASSERT(! Line.ParseString(Parser, "a")); - TS_ASSERT(! Line.ParseString(Parser, "a a x")); - TS_ASSERT(Line.ParseString(Parser, "a x a b x a x b x b x b x b x a x a x a b x a b x b x a x")); - } -}; +#include "lib/self_test.h" + +#include "ps/Parser.h" + +class TestParser : public CxxTest::TestSuite +{ +public: + void test1() + { + CParser Parser; + Parser.InputTaskType("test", "_$ident_=_$value_"); + + std::string str; + int i; + + CParserLine Line; + + TS_ASSERT(Line.ParseString(Parser, "value=23")); + + TS_ASSERT(Line.GetArgString(0, str) && str == "value"); + TS_ASSERT(Line.GetArgInt(1, i) && i == 23); + } + + + void test2() + { + CParser Parser; + Parser.InputTaskType("test", "_$value_[$value]_"); + + std::string str; + + CParserLine Line; + + TS_ASSERT(Line.ParseString(Parser, "12 34")); + TS_ASSERT_EQUALS((int)Line.GetArgCount(), 2); + TS_ASSERT(Line.GetArgString(0, str) && str == "12"); + TS_ASSERT(Line.GetArgString(1, str) && str == "34"); + + TS_ASSERT(Line.ParseString(Parser, "56")); + TS_ASSERT_EQUALS((int)Line.GetArgCount(), 1); + TS_ASSERT(Line.GetArgString(0, str) && str == "56"); + + TS_ASSERT(! Line.ParseString(Parser, " ")); + } + + + void test3() + { + CParser Parser; + Parser.InputTaskType("test", "_[$value]_[$value]_[$value]_"); + + std::string str; + + CParserLine Line; + + TS_ASSERT(Line.ParseString(Parser, "12 34 56")); + TS_ASSERT_EQUALS((int)Line.GetArgCount(), 3); + TS_ASSERT(Line.GetArgString(0, str) && str == "12"); + TS_ASSERT(Line.GetArgString(1, str) && str == "34"); + TS_ASSERT(Line.GetArgString(2, str) && str == "56"); + + TS_ASSERT(Line.ParseString(Parser, "78 90")); + TS_ASSERT_EQUALS((int)Line.GetArgCount(), 2); + TS_ASSERT(Line.GetArgString(0, str) && str == "78"); + TS_ASSERT(Line.GetArgString(1, str) && str == "90"); + + TS_ASSERT(Line.ParseString(Parser, "ab")); + TS_ASSERT_EQUALS((int)Line.GetArgCount(), 1); + TS_ASSERT(Line.GetArgString(0, str) && str == "ab"); + + TS_ASSERT(Line.ParseString(Parser, " ")); + TS_ASSERT_EQUALS((int)Line.GetArgCount(), 0); + } + + + void test4() + { + CParser Parser; + Parser.InputTaskType("test", "<[_a_][_b_]_x_>"); + + std::string str; + + CParserLine Line; + TS_ASSERT(Line.ParseString(Parser, "a b x a b x")); + TS_ASSERT(Line.ParseString(Parser, "a x b x")); + TS_ASSERT(Line.ParseString(Parser, "a x")); + TS_ASSERT(Line.ParseString(Parser, "b x")); + TS_ASSERT(Line.ParseString(Parser, "x")); + TS_ASSERT(! Line.ParseString(Parser, "a x c x")); + TS_ASSERT(! Line.ParseString(Parser, "a b a x")); + TS_ASSERT(! Line.ParseString(Parser, "a")); + TS_ASSERT(! Line.ParseString(Parser, "a a x")); + TS_ASSERT(Line.ParseString(Parser, "a x a b x a x b x b x b x b x a x a x a b x a b x b x a x")); + } +}; diff --git a/source/renderer/Scene.cpp b/source/renderer/Scene.cpp index 115ab2d26b..d097fb3885 100644 --- a/source/renderer/Scene.cpp +++ b/source/renderer/Scene.cpp @@ -1,32 +1,32 @@ -/** - * File : Scene.cpp - * Project : graphics - * Description : This file contains default implementations and utilities - * : to be used together with the Scene interface and related - * : classes. - * - * @note This file would fit just as well into the graphics/ subdirectory. - **/ - -#include "precompiled.h" - -#include "graphics/Model.h" - -#include "renderer/Scene.h" - - - -/////////////////////////////////////////////////////////// -// Default implementation traverses the model recursively and uses -// SubmitNonRecursive for the actual work. -void SceneCollector::SubmitRecursive(CModel* model) -{ - SubmitNonRecursive(model); - - const std::vector& props = model->GetProps(); - for (size_t i=0;i& props = model->GetProps(); + for (size_t i=0;iLoadFile(pathname); - return INFO::CB_CONTINUE; -} - -int CTechnologyCollection::LoadTechnologies() -{ - // Load all files in techs/ and subdirectories. - THROW_ERR( fs_ForEachFile(g_VFS, "technologies/", LoadTechThunk, (uintptr_t)this, "*.xml", DIR_RECURSIVE)); - return 0; -} - -CTechnology* CTechnologyCollection::GetTechnology( const CStrW& name, CPlayer* player ) -{ - // Find player ID - debug_assert( player != 0 ); - const size_t id = player->GetPlayerID(); - - // Check whether this template has already been loaded. - //If previously loaded, all slots will be found, so any entry works. - TechMap::iterator it = m_techs[id].find( name ); - if( it != m_techs[id].end() ) - return( it->second ); - - // Find the filename corresponding to this template - TechFilenameMap::iterator filename_it = m_techFilenames.find( name ); - if( filename_it == m_techFilenames.end() ) - return( NULL ); - - CStr path( filename_it->second ); - - //Try to load to the tech - CTechnology* newTemplate = new CTechnology( name, player ); - if( !newTemplate->LoadXml( path ) ) - { - LOG(CLogger::Error, LOG_CATEGORY, "CTechnologyCollection::GetTechnology(): Couldn't load tech \"%s\"", path.c_str()); - delete newTemplate; - return( NULL ); - - } - m_techs[id][name] = newTemplate; - - LOG(CLogger::Normal, LOG_CATEGORY, "CTechnologyCollection::GetTechnology(): Loaded tech \"%s\"", path.c_str()); - return newTemplate; -} - -CTechnologyCollection::~CTechnologyCollection() -{ - for( size_t id=0; idsecond ); -} +#include "precompiled.h" + +#include "TechnologyCollection.h" +#include "ps/CLogger.h" +#include "ps/Player.h" + +#define LOG_CATEGORY "tech" + +void CTechnologyCollection::LoadFile( const VfsPath& pathname ) +{ + const CStrW basename(fs::basename(pathname)); + m_techFilenames[basename] = pathname.string(); +} + +static LibError LoadTechThunk( const VfsPath& pathname, const FileInfo& UNUSED(fileInfo), uintptr_t cbData ) +{ + CTechnologyCollection* this_ = (CTechnologyCollection*)cbData; + this_->LoadFile(pathname); + return INFO::CB_CONTINUE; +} + +int CTechnologyCollection::LoadTechnologies() +{ + // Load all files in techs/ and subdirectories. + THROW_ERR( fs_ForEachFile(g_VFS, "technologies/", LoadTechThunk, (uintptr_t)this, "*.xml", DIR_RECURSIVE)); + return 0; +} + +CTechnology* CTechnologyCollection::GetTechnology( const CStrW& name, CPlayer* player ) +{ + // Find player ID + debug_assert( player != 0 ); + const size_t id = player->GetPlayerID(); + + // Check whether this template has already been loaded. + //If previously loaded, all slots will be found, so any entry works. + TechMap::iterator it = m_techs[id].find( name ); + if( it != m_techs[id].end() ) + return( it->second ); + + // Find the filename corresponding to this template + TechFilenameMap::iterator filename_it = m_techFilenames.find( name ); + if( filename_it == m_techFilenames.end() ) + return( NULL ); + + CStr path( filename_it->second ); + + //Try to load to the tech + CTechnology* newTemplate = new CTechnology( name, player ); + if( !newTemplate->LoadXml( path ) ) + { + LOG(CLogger::Error, LOG_CATEGORY, "CTechnologyCollection::GetTechnology(): Couldn't load tech \"%s\"", path.c_str()); + delete newTemplate; + return( NULL ); + + } + m_techs[id][name] = newTemplate; + + LOG(CLogger::Normal, LOG_CATEGORY, "CTechnologyCollection::GetTechnology(): Loaded tech \"%s\"", path.c_str()); + return newTemplate; +} + +CTechnologyCollection::~CTechnologyCollection() +{ + for( size_t id=0; idsecond ); +} diff --git a/source/simulation/TechnologyCollection.h b/source/simulation/TechnologyCollection.h index f80825caa2..960f3e3b3f 100644 --- a/source/simulation/TechnologyCollection.h +++ b/source/simulation/TechnologyCollection.h @@ -1,35 +1,35 @@ -//Manages the tech templates. More detail: see CFormation and CEntityTemplate (collections) - - -#ifndef INCLUDED_TECHNOLOGYCOLLECTION -#define INCLUDED_TECHNOLOGYCOLLECTION - -#include "ps/CStr.h" -#include "ps/Singleton.h" -#include "Technology.h" -#include "ps/Game.h" -#include "ps/Filesystem.h" - -#define g_TechnologyCollection CTechnologyCollection::GetSingleton() - -class CTechnologyCollection : public Singleton -{ - typedef std::map TechMap; - typedef std::map TechFilenameMap; - - TechMap m_techs[PS_MAX_PLAYERS+1]; - TechFilenameMap m_techFilenames; - -public: - std::vector activeTechs[PS_MAX_PLAYERS+1]; - - CTechnology* GetTechnology( const CStrW& techType, CPlayer* player ); - ~CTechnologyCollection(); - - int LoadTechnologies(); - - // called by non-member trampoline via LoadTechnologies - void LoadFile( const VfsPath& path ); -}; - -#endif +//Manages the tech templates. More detail: see CFormation and CEntityTemplate (collections) + + +#ifndef INCLUDED_TECHNOLOGYCOLLECTION +#define INCLUDED_TECHNOLOGYCOLLECTION + +#include "ps/CStr.h" +#include "ps/Singleton.h" +#include "Technology.h" +#include "ps/Game.h" +#include "ps/Filesystem.h" + +#define g_TechnologyCollection CTechnologyCollection::GetSingleton() + +class CTechnologyCollection : public Singleton +{ + typedef std::map TechMap; + typedef std::map TechFilenameMap; + + TechMap m_techs[PS_MAX_PLAYERS+1]; + TechFilenameMap m_techFilenames; + +public: + std::vector activeTechs[PS_MAX_PLAYERS+1]; + + CTechnology* GetTechnology( const CStrW& techType, CPlayer* player ); + ~CTechnologyCollection(); + + int LoadTechnologies(); + + // called by non-member trampoline via LoadTechnologies + void LoadFile( const VfsPath& path ); +}; + +#endif diff --git a/source/simulation/TriggerManager.cpp b/source/simulation/TriggerManager.cpp index aad0de3ae9..8c51daf59d 100644 --- a/source/simulation/TriggerManager.cpp +++ b/source/simulation/TriggerManager.cpp @@ -1,487 +1,487 @@ -#include "precompiled.h" - -#include "TriggerManager.h" -#include "scripting/ScriptingHost.h" -#include "ps/XML/Xeromyces.h" -#include "ps/XML/XeroXMB.h" -#include "ps/CLogger.h" - -#define LOG_CATEGORY "Triggers" - - -CTrigger::CTrigger() -{ - m_timeLeft = m_timeDelay = m_maxRunCount = m_runCount = m_active = 0; -} -CTrigger::~CTrigger() -{ - -} -CTrigger::CTrigger(const CStrW& name, bool active, float delay, int maxRuns, const CStrW& condFunc, - const CStrW& effectFunc) -{ - m_name = name; - m_active = active; - m_timeLeft = m_timeDelay = delay; - m_runCount = 0; - m_maxRunCount = maxRuns; - - CStrW validName = name; - validName.Replace(L" ", L"_"); - - m_conditionFuncString = condFunc; - m_effectFuncString = effectFunc; - m_conditionFunction.Compile( L"TriggerCondition_" + validName, condFunc ); - m_effectFunction.Compile( L"TriggerEffect_" + validName, effectFunc ); -} -CTrigger::CTrigger(const CStrW& name, bool active, float delay, int maxRuns, - CScriptObject& condFunc, CScriptObject& effectFunc) -{ - m_name = name; - m_active = active; - m_timeLeft = m_timeDelay = delay; - m_runCount = 0; - m_maxRunCount = maxRuns; - m_conditionFunction = condFunc; - m_effectFunction = effectFunc; -} - -JSBool CTrigger::Construct( JSContext* cx, JSObject* UNUSED(obj), uintN argc, jsval* argv, jsval* rval ) -{ - JSU_REQUIRE_PARAMS_CPP(6); - - CScriptObject condFunc( JS_ValueToFunction(cx, argv[4]) ); - CScriptObject effectFunc( JS_ValueToFunction(cx, argv[5]) ); - CTrigger* newTrigger = new CTrigger( ToPrimitive( argv[0] ), - ToPrimitive( argv[1] ), - ToPrimitive( argv[2] ), - ToPrimitive( argv[3]), - condFunc, effectFunc ); - - g_TriggerManager.AddTrigger(newTrigger); - *rval = OBJECT_TO_JSVAL( newTrigger->GetScript() ); - return JS_TRUE; -} - -CTrigger& CTrigger::operator= (const CTrigger& trigger) -{ - m_name = trigger.m_name; - m_active = trigger.m_active; - m_timeLeft = trigger.m_timeLeft; - m_timeDelay = trigger.m_timeDelay; - m_maxRunCount = trigger.m_maxRunCount; - m_conditionFunction = trigger.m_conditionFunction; - m_effectFunction = trigger.m_effectFunction; - - return *this; -} - -void CTrigger::ScriptingInit() -{ - AddProperty(L"runCount", &CTrigger::m_runCount, true); - AddProperty(L"name", &CTrigger::m_name, true); - AddProperty(L"maxRunCount", &CTrigger::m_maxRunCount); - AddProperty(L"timeDelay", &CTrigger::m_timeDelay); - - AddMethod( "activate", 0 ); - AddMethod( "deactivate", 0 ); - - CJSObject::ScriptingInit("Trigger", CTrigger::Construct, 6); -} - -bool CTrigger::ShouldFire() -{ - if ( !m_active ) - return false; - return m_conditionFunction.Run( this->GetScript() ); -} -bool CTrigger::Fire() -{ - m_effectFunction.Run( this->GetScript() ); - ++m_runCount; - m_timeLeft = m_timeDelay; - return (m_runCount < m_maxRunCount); -} - -void CTrigger::Activate(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv)) -{ - m_active = true; -} -void CTrigger::Deactivate(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv)) -{ - m_active = false; -} - - -void TriggerParameter::SetWindowData(const CStrW& _windowType, CStrW& windowPosition, CStrW& windowSize) -{ - windowPosition.Remove( CStrW(L" ") ); - windowSize.Remove( CStrW(L" ") ); - - xPos = windowPosition.BeforeFirst( CStrW(L",") ).ToInt(); - yPos = windowPosition.AfterFirst( CStrW(L",") ).ToInt(); - xSize = windowSize.BeforeFirst( CStrW(L",") ).ToInt(); - ySize = windowSize.AfterFirst( CStrW(L",") ).ToInt(); - windowType = _windowType; -} - -bool TriggerParameter::operator< ( const TriggerParameter& rhs ) const -{ - if ( row < rhs.row ) - return true; - else if ( row == rhs.row) - return ( column < rhs.column ); - - return false; -} -//===========Trigger Manager=========== - -CTriggerManager::CTriggerManager() : m_UpdateRate(.20f), m_UpdateTime(.20f) -{ -} - -CTriggerManager::~CTriggerManager() -{ - DestroyEngineTriggers(); -} - -void CTriggerManager::DestroyEngineTriggers() -{ - for ( TriggerIter it = m_TriggerMap.begin(); it != m_TriggerMap.end(); ++it ) - { - SAFE_DELETE(it->second); - } - m_TriggerMap.clear(); -} - -std::vector CTriggerManager::GetTriggerChoices(const std::wstring& name) -{ - return m_TriggerChoices[name]; -} -std::vector CTriggerManager::GetTriggerTranslations(const std::wstring& name) -{ - return m_TriggerTranslations[name]; -} - -void CTriggerManager::Update(float delta_ms) -{ - float delta = delta_ms / 1000.f; - m_UpdateTime -= delta; - if ( m_UpdateTime < 0 ) - m_UpdateTime += m_UpdateRate; - else - return; - - std::list expired; - for ( TriggerIter it = m_TriggerMap.begin(); it != m_TriggerMap.end(); ++it ) - { - if ( it->second->ShouldFire() ) - { - it->second->m_timeLeft -= m_UpdateRate; - if ( it->second->m_timeLeft < 0 ) - { - if ( !it->second->Fire() ) - expired.push_back(it); - } - } - } - - //Remove all expired triggers - for ( std::list::iterator it = expired.begin(); it != expired.end(); ++it ) - { - delete (*it)->second; - m_TriggerMap.erase(*it); - } -} - -void CTriggerManager::AddTrigger(CTrigger* trigger) -{ - m_TriggerMap[trigger->GetName()] = trigger; -} -void CTriggerManager::AddGroup(const MapTriggerGroup& group) -{ - m_GroupList.push_back(group); -} -void CTriggerManager::SetAllGroups(const std::list& groups) -{ - m_GroupList = groups; -} - -void CTriggerManager::AddTrigger(MapTriggerGroup& group, const MapTrigger& trigger) -{ - CStrW conditionBody; - CStrW linkLogic[] = { CStrW(L""), CStrW(L" && "), CStrW(L" || ") }; - size_t i=0; - bool allParameters = true; - - if(trigger.conditions.size() == 0) { - conditionBody = CStrW(L"return ( true );"); - } - else { - conditionBody = CStrW(L"return ( "); - - for ( std::list::const_iterator it = trigger.conditions.begin(); - it != trigger.conditions.end(); ++it, ++i ) - { - //Opening parenthesis here? - std::set::const_iterator blockIt; - if ( ( blockIt = trigger.logicBlocks.find(MapTriggerLogicBlock(i)) ) != trigger.logicBlocks.end() ) - { - if ( blockIt->negated ) - conditionBody += CStrW(L"!"); - conditionBody += CStrW(L" ("); - } - - if ( it->negated ) - conditionBody += CStrW(L"!"); - conditionBody += it->functionName; - conditionBody += CStrW(L"("); - - for ( std::list::const_iterator it2 = it->parameters.begin(); it2 != - it->parameters.end(); ++it2 ) - { - size_t params = (size_t)std::find(m_ConditionSpecs.begin(), m_ConditionSpecs.end(), it->displayName)->funcParameters; - size_t distance = std::distance(it->parameters.begin(), it2); - - //Parameters end here, additional "parameters" are used directly as script - if ( distance == params ) - { - conditionBody += CStrW(L") "); - allParameters = false; - } - - //Take display parameter and translate into JS usable code...evilness - CTriggerSpec spec = *std::find( m_ConditionSpecs.begin(), m_ConditionSpecs.end(), it->displayName ); - const std::set& specParameters = spec.GetParameters(); - - //Don't use specialized find, since we're searching for a different member - std::set::const_iterator specParam = std::find( - specParameters.begin(), specParameters.end(), (int)distance); - std::wstring combined = std::wstring( it->functionName + specParam->name ); - size_t translatedIndex = std::distance( m_TriggerChoices[combined].begin(), - std::find(m_TriggerChoices[combined].begin(), m_TriggerChoices[combined].end(), std::wstring(*it2)) ); - - if ( m_TriggerTranslations[combined].empty() ) - conditionBody += *it2; - else - conditionBody += m_TriggerTranslations[combined][translatedIndex]; - - if ( distance + 1 < params ) - conditionBody += CStrW(L", "); - } - - if ( allParameters ) //Otherwise, closed inside loop - conditionBody += CStrW(L")"); - if ( trigger.logicBlockEnds.find(i) != trigger.logicBlockEnds.end() ) - conditionBody += CStrW(L" )"); - - if ( std::distance(it, trigger.conditions.end()) != 1 ) - conditionBody += linkLogic[it->linkLogic]; - } - - conditionBody += CStrW(L" );"); - } - - CStrW effectBody; - - for ( std::list::const_iterator it = trigger.effects.begin(); - it != trigger.effects.end(); ++it ) - { - effectBody += it->functionName; - effectBody += CStrW(L"("); - for ( std::list::const_iterator it2 = it->parameters.begin(); it2 != - it->parameters.end(); ++it2 ) - { - size_t distance = std::distance(it->parameters.begin(), it2); - //Take display parameter and translate into JS usable code...evilness - CTriggerSpec spec = *std::find( m_EffectSpecs.begin(), m_EffectSpecs.end(), it->displayName ); - const std::set& specParameters = spec.GetParameters(); - - //Don't use specialized find, since we're searching for a different member - std::set::const_iterator specParam = std::find( - specParameters.begin(), specParameters.end(), (int)distance); - std::wstring combined = std::wstring( it->functionName + specParam->name ); - size_t translatedIndex = std::distance( m_TriggerChoices[combined].begin(), - std::find(m_TriggerChoices[combined].begin(), m_TriggerChoices[combined].end(), std::wstring(*it2)) ); - - if ( m_TriggerTranslations[combined].empty() ) - effectBody += *it2; - else - effectBody += m_TriggerTranslations[combined][translatedIndex]; - std::list::const_iterator endIt = it->parameters.end(); - if ( std::distance(it2, endIt) != 1 ) - effectBody += CStrW(L", "); - } - - effectBody += CStrW(L");"); - } - - group.triggers.push_back(trigger); - CTrigger* newTrigger = new CTrigger(trigger.name, trigger.active, trigger.timeValue, - trigger.maxRunCount, conditionBody, effectBody); - AddTrigger(newTrigger); -} - - -//XML stuff - -bool CTriggerManager::LoadXml( const CStr& filename ) -{ - CXeromyces XeroFile; - - if ( XeroFile.Load( filename.c_str() ) != PSRETURN_OK ) - return false; - - #define EL(x) int el_##x = XeroFile.GetElementID(#x) - #define AT(x) int at_##x = XeroFile.GetAttributeID(#x) - - EL(condition); - EL(effect); - EL(definitions); - - #undef EL - #undef AT - - //Return false on any error to be extra sure the point gets across (FIX IT) - XMBElement root = XeroFile.GetRoot(); - - if ( root.GetNodeName() != el_definitions ) - return false; - - XERO_ITER_EL(root, rootChild) - { - int name = rootChild.GetNodeName(); - if ( name == el_condition ) - { - if ( !LoadTriggerSpec(rootChild, XeroFile, true) ) - { - LOG(CLogger::Error, LOG_CATEGORY, "Error detected in Trigger XML tag. File: %s", filename.c_str()); - return false; - } - } - else if ( name == el_effect ) - { - if ( !LoadTriggerSpec(rootChild, XeroFile, false) ) - { - LOG(CLogger::Error, LOG_CATEGORY, "Error detected in Trigger XML tag. File: %s", filename.c_str()); - return false; - } - } - else - { - LOG(CLogger::Error, LOG_CATEGORY, "Invalid tag in trigger XML. File: %s", filename.c_str()); - return false; - } - } - return true; -} - -bool CTriggerManager::LoadTriggerSpec( XMBElement condition, CXeromyces& XeroFile, bool isCondition ) -{ - #define EL(x) int el_##x = XeroFile.GetElementID(#x) - #define AT(x) int at_##x = XeroFile.GetAttributeID(#x) - - EL(parameter); - EL(window); - EL(inputtype); - EL(parameterorder); - EL(windowrow); - EL(choices); - EL(choicetranslation); - - AT(type); - AT(position); - AT(name); - AT(size); - AT(function); - AT(funcParameters); - - #undef EL - #undef AT - - CTriggerSpec specStore; - specStore.functionName = CStrW( condition.GetAttributes().GetNamedItem(at_function) ); - specStore.displayName = CStrW( condition.GetAttributes().GetNamedItem(at_name) ); - specStore.funcParameters = CStr( condition.GetAttributes().GetNamedItem(at_funcParameters) ).ToInt(); - int row = -1, column = -1; - - XERO_ITER_EL(condition, child) - { - int childID = child.GetNodeName(); - - if ( childID == el_windowrow ) - { - ++row; - column = -1; - - XERO_ITER_EL(child, windowChild) - { - ++column; - TriggerParameter specParam(row, column); - specParam.name = windowChild.GetAttributes().GetNamedItem(at_name); - childID = windowChild.GetNodeName(); - - if ( childID != el_parameter) - return false; - - XERO_ITER_EL(windowChild, parameterChild) - { - childID = parameterChild.GetNodeName(); - - if ( childID == el_window ) - { - CStrW type( parameterChild.GetAttributes().GetNamedItem(at_type) ); - CStrW pos( parameterChild.GetAttributes().GetNamedItem(at_position) ); - CStrW size( parameterChild.GetAttributes().GetNamedItem(at_size) ); - specParam.SetWindowData(type, pos, size); - } - - else if ( childID == el_inputtype ) - specParam.inputType = CStrW( parameterChild.GetText() ); - else if ( childID == el_parameterorder ) - specParam.parameterOrder = CStrW( parameterChild.GetText() ).ToInt(); - else if ( childID == el_choices ) - { - std::vector choices; - CStrW comma(L","), input(parameterChild.GetText()); - CStrW substr; - while ( (substr = input.BeforeFirst(comma)) != input ) - { - choices.push_back(substr); - input = input.AfterFirst(comma); - } - - choices.push_back(substr); //Last element has no comma - m_TriggerChoices[specStore.functionName + specParam.name] = choices; - } - else if ( childID == el_choicetranslation ) - { - std::vector choices; - CStrW comma(L","), input(parameterChild.GetText()); - CStrW substr; - while ( (substr = input.BeforeFirst(comma)) != input ) - { - choices.push_back(substr); - input = input.AfterFirst(comma); - } - - choices.push_back(substr); //Last element has no comma - m_TriggerTranslations[specStore.functionName + specParam.name] = choices; - } - else - return false; - } - specStore.AddParameter(specParam); - } - } - else - return false; - } - - if ( isCondition ) - m_ConditionSpecs.push_back(specStore); - else - m_EffectSpecs.push_back(specStore); - - return true; -} +#include "precompiled.h" + +#include "TriggerManager.h" +#include "scripting/ScriptingHost.h" +#include "ps/XML/Xeromyces.h" +#include "ps/XML/XeroXMB.h" +#include "ps/CLogger.h" + +#define LOG_CATEGORY "Triggers" + + +CTrigger::CTrigger() +{ + m_timeLeft = m_timeDelay = m_maxRunCount = m_runCount = m_active = 0; +} +CTrigger::~CTrigger() +{ + +} +CTrigger::CTrigger(const CStrW& name, bool active, float delay, int maxRuns, const CStrW& condFunc, + const CStrW& effectFunc) +{ + m_name = name; + m_active = active; + m_timeLeft = m_timeDelay = delay; + m_runCount = 0; + m_maxRunCount = maxRuns; + + CStrW validName = name; + validName.Replace(L" ", L"_"); + + m_conditionFuncString = condFunc; + m_effectFuncString = effectFunc; + m_conditionFunction.Compile( L"TriggerCondition_" + validName, condFunc ); + m_effectFunction.Compile( L"TriggerEffect_" + validName, effectFunc ); +} +CTrigger::CTrigger(const CStrW& name, bool active, float delay, int maxRuns, + CScriptObject& condFunc, CScriptObject& effectFunc) +{ + m_name = name; + m_active = active; + m_timeLeft = m_timeDelay = delay; + m_runCount = 0; + m_maxRunCount = maxRuns; + m_conditionFunction = condFunc; + m_effectFunction = effectFunc; +} + +JSBool CTrigger::Construct( JSContext* cx, JSObject* UNUSED(obj), uintN argc, jsval* argv, jsval* rval ) +{ + JSU_REQUIRE_PARAMS_CPP(6); + + CScriptObject condFunc( JS_ValueToFunction(cx, argv[4]) ); + CScriptObject effectFunc( JS_ValueToFunction(cx, argv[5]) ); + CTrigger* newTrigger = new CTrigger( ToPrimitive( argv[0] ), + ToPrimitive( argv[1] ), + ToPrimitive( argv[2] ), + ToPrimitive( argv[3]), + condFunc, effectFunc ); + + g_TriggerManager.AddTrigger(newTrigger); + *rval = OBJECT_TO_JSVAL( newTrigger->GetScript() ); + return JS_TRUE; +} + +CTrigger& CTrigger::operator= (const CTrigger& trigger) +{ + m_name = trigger.m_name; + m_active = trigger.m_active; + m_timeLeft = trigger.m_timeLeft; + m_timeDelay = trigger.m_timeDelay; + m_maxRunCount = trigger.m_maxRunCount; + m_conditionFunction = trigger.m_conditionFunction; + m_effectFunction = trigger.m_effectFunction; + + return *this; +} + +void CTrigger::ScriptingInit() +{ + AddProperty(L"runCount", &CTrigger::m_runCount, true); + AddProperty(L"name", &CTrigger::m_name, true); + AddProperty(L"maxRunCount", &CTrigger::m_maxRunCount); + AddProperty(L"timeDelay", &CTrigger::m_timeDelay); + + AddMethod( "activate", 0 ); + AddMethod( "deactivate", 0 ); + + CJSObject::ScriptingInit("Trigger", CTrigger::Construct, 6); +} + +bool CTrigger::ShouldFire() +{ + if ( !m_active ) + return false; + return m_conditionFunction.Run( this->GetScript() ); +} +bool CTrigger::Fire() +{ + m_effectFunction.Run( this->GetScript() ); + ++m_runCount; + m_timeLeft = m_timeDelay; + return (m_runCount < m_maxRunCount); +} + +void CTrigger::Activate(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv)) +{ + m_active = true; +} +void CTrigger::Deactivate(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv)) +{ + m_active = false; +} + + +void TriggerParameter::SetWindowData(const CStrW& _windowType, CStrW& windowPosition, CStrW& windowSize) +{ + windowPosition.Remove( CStrW(L" ") ); + windowSize.Remove( CStrW(L" ") ); + + xPos = windowPosition.BeforeFirst( CStrW(L",") ).ToInt(); + yPos = windowPosition.AfterFirst( CStrW(L",") ).ToInt(); + xSize = windowSize.BeforeFirst( CStrW(L",") ).ToInt(); + ySize = windowSize.AfterFirst( CStrW(L",") ).ToInt(); + windowType = _windowType; +} + +bool TriggerParameter::operator< ( const TriggerParameter& rhs ) const +{ + if ( row < rhs.row ) + return true; + else if ( row == rhs.row) + return ( column < rhs.column ); + + return false; +} +//===========Trigger Manager=========== + +CTriggerManager::CTriggerManager() : m_UpdateRate(.20f), m_UpdateTime(.20f) +{ +} + +CTriggerManager::~CTriggerManager() +{ + DestroyEngineTriggers(); +} + +void CTriggerManager::DestroyEngineTriggers() +{ + for ( TriggerIter it = m_TriggerMap.begin(); it != m_TriggerMap.end(); ++it ) + { + SAFE_DELETE(it->second); + } + m_TriggerMap.clear(); +} + +std::vector CTriggerManager::GetTriggerChoices(const std::wstring& name) +{ + return m_TriggerChoices[name]; +} +std::vector CTriggerManager::GetTriggerTranslations(const std::wstring& name) +{ + return m_TriggerTranslations[name]; +} + +void CTriggerManager::Update(float delta_ms) +{ + float delta = delta_ms / 1000.f; + m_UpdateTime -= delta; + if ( m_UpdateTime < 0 ) + m_UpdateTime += m_UpdateRate; + else + return; + + std::list expired; + for ( TriggerIter it = m_TriggerMap.begin(); it != m_TriggerMap.end(); ++it ) + { + if ( it->second->ShouldFire() ) + { + it->second->m_timeLeft -= m_UpdateRate; + if ( it->second->m_timeLeft < 0 ) + { + if ( !it->second->Fire() ) + expired.push_back(it); + } + } + } + + //Remove all expired triggers + for ( std::list::iterator it = expired.begin(); it != expired.end(); ++it ) + { + delete (*it)->second; + m_TriggerMap.erase(*it); + } +} + +void CTriggerManager::AddTrigger(CTrigger* trigger) +{ + m_TriggerMap[trigger->GetName()] = trigger; +} +void CTriggerManager::AddGroup(const MapTriggerGroup& group) +{ + m_GroupList.push_back(group); +} +void CTriggerManager::SetAllGroups(const std::list& groups) +{ + m_GroupList = groups; +} + +void CTriggerManager::AddTrigger(MapTriggerGroup& group, const MapTrigger& trigger) +{ + CStrW conditionBody; + CStrW linkLogic[] = { CStrW(L""), CStrW(L" && "), CStrW(L" || ") }; + size_t i=0; + bool allParameters = true; + + if(trigger.conditions.size() == 0) { + conditionBody = CStrW(L"return ( true );"); + } + else { + conditionBody = CStrW(L"return ( "); + + for ( std::list::const_iterator it = trigger.conditions.begin(); + it != trigger.conditions.end(); ++it, ++i ) + { + //Opening parenthesis here? + std::set::const_iterator blockIt; + if ( ( blockIt = trigger.logicBlocks.find(MapTriggerLogicBlock(i)) ) != trigger.logicBlocks.end() ) + { + if ( blockIt->negated ) + conditionBody += CStrW(L"!"); + conditionBody += CStrW(L" ("); + } + + if ( it->negated ) + conditionBody += CStrW(L"!"); + conditionBody += it->functionName; + conditionBody += CStrW(L"("); + + for ( std::list::const_iterator it2 = it->parameters.begin(); it2 != + it->parameters.end(); ++it2 ) + { + size_t params = (size_t)std::find(m_ConditionSpecs.begin(), m_ConditionSpecs.end(), it->displayName)->funcParameters; + size_t distance = std::distance(it->parameters.begin(), it2); + + //Parameters end here, additional "parameters" are used directly as script + if ( distance == params ) + { + conditionBody += CStrW(L") "); + allParameters = false; + } + + //Take display parameter and translate into JS usable code...evilness + CTriggerSpec spec = *std::find( m_ConditionSpecs.begin(), m_ConditionSpecs.end(), it->displayName ); + const std::set& specParameters = spec.GetParameters(); + + //Don't use specialized find, since we're searching for a different member + std::set::const_iterator specParam = std::find( + specParameters.begin(), specParameters.end(), (int)distance); + std::wstring combined = std::wstring( it->functionName + specParam->name ); + size_t translatedIndex = std::distance( m_TriggerChoices[combined].begin(), + std::find(m_TriggerChoices[combined].begin(), m_TriggerChoices[combined].end(), std::wstring(*it2)) ); + + if ( m_TriggerTranslations[combined].empty() ) + conditionBody += *it2; + else + conditionBody += m_TriggerTranslations[combined][translatedIndex]; + + if ( distance + 1 < params ) + conditionBody += CStrW(L", "); + } + + if ( allParameters ) //Otherwise, closed inside loop + conditionBody += CStrW(L")"); + if ( trigger.logicBlockEnds.find(i) != trigger.logicBlockEnds.end() ) + conditionBody += CStrW(L" )"); + + if ( std::distance(it, trigger.conditions.end()) != 1 ) + conditionBody += linkLogic[it->linkLogic]; + } + + conditionBody += CStrW(L" );"); + } + + CStrW effectBody; + + for ( std::list::const_iterator it = trigger.effects.begin(); + it != trigger.effects.end(); ++it ) + { + effectBody += it->functionName; + effectBody += CStrW(L"("); + for ( std::list::const_iterator it2 = it->parameters.begin(); it2 != + it->parameters.end(); ++it2 ) + { + size_t distance = std::distance(it->parameters.begin(), it2); + //Take display parameter and translate into JS usable code...evilness + CTriggerSpec spec = *std::find( m_EffectSpecs.begin(), m_EffectSpecs.end(), it->displayName ); + const std::set& specParameters = spec.GetParameters(); + + //Don't use specialized find, since we're searching for a different member + std::set::const_iterator specParam = std::find( + specParameters.begin(), specParameters.end(), (int)distance); + std::wstring combined = std::wstring( it->functionName + specParam->name ); + size_t translatedIndex = std::distance( m_TriggerChoices[combined].begin(), + std::find(m_TriggerChoices[combined].begin(), m_TriggerChoices[combined].end(), std::wstring(*it2)) ); + + if ( m_TriggerTranslations[combined].empty() ) + effectBody += *it2; + else + effectBody += m_TriggerTranslations[combined][translatedIndex]; + std::list::const_iterator endIt = it->parameters.end(); + if ( std::distance(it2, endIt) != 1 ) + effectBody += CStrW(L", "); + } + + effectBody += CStrW(L");"); + } + + group.triggers.push_back(trigger); + CTrigger* newTrigger = new CTrigger(trigger.name, trigger.active, trigger.timeValue, + trigger.maxRunCount, conditionBody, effectBody); + AddTrigger(newTrigger); +} + + +//XML stuff + +bool CTriggerManager::LoadXml( const CStr& filename ) +{ + CXeromyces XeroFile; + + if ( XeroFile.Load( filename.c_str() ) != PSRETURN_OK ) + return false; + + #define EL(x) int el_##x = XeroFile.GetElementID(#x) + #define AT(x) int at_##x = XeroFile.GetAttributeID(#x) + + EL(condition); + EL(effect); + EL(definitions); + + #undef EL + #undef AT + + //Return false on any error to be extra sure the point gets across (FIX IT) + XMBElement root = XeroFile.GetRoot(); + + if ( root.GetNodeName() != el_definitions ) + return false; + + XERO_ITER_EL(root, rootChild) + { + int name = rootChild.GetNodeName(); + if ( name == el_condition ) + { + if ( !LoadTriggerSpec(rootChild, XeroFile, true) ) + { + LOG(CLogger::Error, LOG_CATEGORY, "Error detected in Trigger XML tag. File: %s", filename.c_str()); + return false; + } + } + else if ( name == el_effect ) + { + if ( !LoadTriggerSpec(rootChild, XeroFile, false) ) + { + LOG(CLogger::Error, LOG_CATEGORY, "Error detected in Trigger XML tag. File: %s", filename.c_str()); + return false; + } + } + else + { + LOG(CLogger::Error, LOG_CATEGORY, "Invalid tag in trigger XML. File: %s", filename.c_str()); + return false; + } + } + return true; +} + +bool CTriggerManager::LoadTriggerSpec( XMBElement condition, CXeromyces& XeroFile, bool isCondition ) +{ + #define EL(x) int el_##x = XeroFile.GetElementID(#x) + #define AT(x) int at_##x = XeroFile.GetAttributeID(#x) + + EL(parameter); + EL(window); + EL(inputtype); + EL(parameterorder); + EL(windowrow); + EL(choices); + EL(choicetranslation); + + AT(type); + AT(position); + AT(name); + AT(size); + AT(function); + AT(funcParameters); + + #undef EL + #undef AT + + CTriggerSpec specStore; + specStore.functionName = CStrW( condition.GetAttributes().GetNamedItem(at_function) ); + specStore.displayName = CStrW( condition.GetAttributes().GetNamedItem(at_name) ); + specStore.funcParameters = CStr( condition.GetAttributes().GetNamedItem(at_funcParameters) ).ToInt(); + int row = -1, column = -1; + + XERO_ITER_EL(condition, child) + { + int childID = child.GetNodeName(); + + if ( childID == el_windowrow ) + { + ++row; + column = -1; + + XERO_ITER_EL(child, windowChild) + { + ++column; + TriggerParameter specParam(row, column); + specParam.name = windowChild.GetAttributes().GetNamedItem(at_name); + childID = windowChild.GetNodeName(); + + if ( childID != el_parameter) + return false; + + XERO_ITER_EL(windowChild, parameterChild) + { + childID = parameterChild.GetNodeName(); + + if ( childID == el_window ) + { + CStrW type( parameterChild.GetAttributes().GetNamedItem(at_type) ); + CStrW pos( parameterChild.GetAttributes().GetNamedItem(at_position) ); + CStrW size( parameterChild.GetAttributes().GetNamedItem(at_size) ); + specParam.SetWindowData(type, pos, size); + } + + else if ( childID == el_inputtype ) + specParam.inputType = CStrW( parameterChild.GetText() ); + else if ( childID == el_parameterorder ) + specParam.parameterOrder = CStrW( parameterChild.GetText() ).ToInt(); + else if ( childID == el_choices ) + { + std::vector choices; + CStrW comma(L","), input(parameterChild.GetText()); + CStrW substr; + while ( (substr = input.BeforeFirst(comma)) != input ) + { + choices.push_back(substr); + input = input.AfterFirst(comma); + } + + choices.push_back(substr); //Last element has no comma + m_TriggerChoices[specStore.functionName + specParam.name] = choices; + } + else if ( childID == el_choicetranslation ) + { + std::vector choices; + CStrW comma(L","), input(parameterChild.GetText()); + CStrW substr; + while ( (substr = input.BeforeFirst(comma)) != input ) + { + choices.push_back(substr); + input = input.AfterFirst(comma); + } + + choices.push_back(substr); //Last element has no comma + m_TriggerTranslations[specStore.functionName + specParam.name] = choices; + } + else + return false; + } + specStore.AddParameter(specParam); + } + } + else + return false; + } + + if ( isCondition ) + m_ConditionSpecs.push_back(specStore); + else + m_EffectSpecs.push_back(specStore); + + return true; +} diff --git a/source/simulation/TriggerManager.h b/source/simulation/TriggerManager.h index bbc7fedc06..232e8a1ebb 100644 --- a/source/simulation/TriggerManager.h +++ b/source/simulation/TriggerManager.h @@ -1,221 +1,221 @@ -//======================================================== -//Description: Manages loading trigger specs (format in Atlas) and updating trigger objects -//======================================================= - -#ifndef INCLUDED_TRIGGERMANAGER -#define INCLUDED_TRIGGERMANAGER - -#include "ps/Singleton.h" -#include "scripting/ScriptableObject.h" -#include "simulation/ScriptObject.h" - -#include -#include - -class CXeromyces; -class XMBElement; - -//Trigger storage - used be Atlas to keep track of different triggers - -struct MapTriggerCondition -{ - MapTriggerCondition() : linkLogic(0), negated(false) { } - CStrW name, functionName, displayName; - std::list parameters; - int linkLogic; //0 = NONE, 1 = AND, 2 = OR - bool negated; -}; - -struct MapTriggerEffect -{ - CStrW name, functionName, displayName; - std::list parameters; -}; - -struct MapTriggerLogicBlock -{ - MapTriggerLogicBlock(size_t i, bool _not=false) : index(i), negated(_not) { } - size_t index; - bool negated; - - bool operator< (const MapTriggerLogicBlock& block) const { return (index < block.index); } - bool operator== (const MapTriggerLogicBlock& block) const { return (index == block.index); } -}; - -struct MapTrigger -{ - MapTrigger() : active(false), maxRunCount(0), timeValue(0) { } - bool active; - int maxRunCount; - float timeValue; - CStrW name, groupName; - - std::set logicBlocks; //Indices of where "(" 's go before - std::set logicBlockEnds; //Indices of where ")" 's come after - std::list conditions; - std::list effects; - - void AddLogicBlock(bool negated) { logicBlocks.insert( MapTriggerLogicBlock(conditions.size(), negated) ); } - void AddLogicBlockEnd() { logicBlockEnds.insert( effects.size() ); } -}; - -struct MapTriggerGroup -{ - MapTriggerGroup() { } - MapTriggerGroup(const CStrW& _name, const CStrW& _parentName) : name(_name), parentName(_parentName) {} - - std::list triggers; - std::list childGroups; //Indices of children - CStrW name, parentName; - - bool operator== (const CStrW& _name) const { return (name == _name); } - bool operator== (const MapTriggerGroup& group) const { return (name == group.name); } -}; - - -struct CopyIfRootChild -{ - CopyIfRootChild(std::list& groupList) : m_groupList(groupList) {} - void operator() ( const MapTriggerGroup& group ) - { - if ( group.parentName == L"Triggers" ) - m_groupList.push_back(group); - } -private: - void operator= (const CopyIfRootChild& UNUSED(group)) const {} - std::list& m_groupList; -}; - -//Triggers used by engine - -class CTrigger : public CJSObject -{ - int m_runCount; - CStrW m_conditionFuncString, m_effectFuncString, m_name, m_groupName; - CScriptObject m_effectFunction; - CScriptObject m_conditionFunction; - -public: - CTrigger(); - CTrigger(const CStrW& name, bool active, float delay, int maxRuns, const CStrW& condFunc, - const CStrW& effectFunc); - CTrigger(const CStrW& name, bool active, float delay, int maxRuns, - CScriptObject& condFunc, CScriptObject& effectFunc); - - CTrigger& operator= (const CTrigger& trigger); - ~CTrigger(); - - void SetFunctionBody(const CStrW& body); - const CStrW& GetConditionString() { return m_conditionFuncString; } - const CStrW& GetEffectString() { return m_effectFuncString; } - const CStrW& GetName() { return m_name; } - const CStrW& GetGroupName() { return m_groupName; } - - bool ShouldFire(); - //Returns false if trigger exceeds run count - bool Fire(); - - void Activate(JSContext* cx, uintN argc, jsval* argv); - void Deactivate(JSContext* cx, uintN argc, jsval* argv); - - static JSBool Construct( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval ); - static void ScriptingInit(); - - JSContext* m_cx; - - bool m_active; - //Changeable by Atlas or needed by manager, no additional affects required - int m_maxRunCount; - float m_timeLeft, m_timeDelay; - -}; - - -//*****Holds information specifying how conditions and effects should look like in atlas****** - -struct TriggerParameter -{ - TriggerParameter() {} - TriggerParameter(int _row, int _column) : row(_row), column(_column) {} - void SetWindowData(const CStrW& _windowType, CStrW& windowPosition, CStrW& windowSize); - //Sort parameters in the order they will be added to a sizer in atlas - bool operator< ( const TriggerParameter& rhs ) const; - bool operator< ( const int parameter ) const { return parameterOrder < parameter; } - bool operator== ( const int parameter ) const { return parameterOrder == parameter; } - - CStrW name, windowType, inputType; - int row, column, xPos, yPos, xSize, ySize, parameterOrder; -}; - -class CTriggerSpec -{ -public: - CTriggerSpec() {} - ~CTriggerSpec() {} - - void AddParameter(const TriggerParameter& param) { parameters.insert(param); } - const std::set& GetParameters() const { return parameters; } - - int funcParameters; - CStrW displayName, functionName; - - bool operator== (const std::wstring& display) const { return display == displayName; } - -private: - - //Sorted by parameter order to make things easy for Atlas - std::set parameters; -}; -typedef CTriggerSpec CTriggerEffect; -typedef CTriggerSpec CTriggerCondition; - - -/******************************Trigger manager******************************/ - -class CTriggerManager : public Singleton -{ -public: - typedef std::map::iterator TriggerIter ; - - CTriggerManager(); - ~CTriggerManager(); - - //Returns false on detection of error - bool LoadXml( const CStr& filename ); - void Update(float delta); - - //Add simulation trigger (probably only called from JS) - void AddTrigger(CTrigger* trigger); - void AddTrigger(MapTriggerGroup& group, const MapTrigger& trigger); - void AddGroup(const MapTriggerGroup& group); - void SetAllGroups(const std::list& groups); - void DestroyEngineTriggers(); - - const std::list& GetAllConditions() const { return m_ConditionSpecs; } - const std::list& GetAllEffects() const { return m_EffectSpecs; } - const std::list& GetAllTriggerGroups() const { return m_GroupList; } - std::vector GetTriggerChoices(const std::wstring& name); - std::vector GetTriggerTranslations(const std::wstring& name); - - std::map m_TriggerMap; //Simulation triggers - used in engine - -private: - bool LoadTriggerSpec( XMBElement condition, CXeromyces& XeroFile, bool isCondition ); - - //Contains choices for trigger choice box parameters, with key = spec.funcName+paramName - std::map > m_TriggerChoices; - std::map > m_TriggerTranslations; - - //Holds information which descibes trigger layout in atlas - std::list m_GroupList; - std::list m_ConditionSpecs; - std::list m_EffectSpecs; - - float m_UpdateRate; //TODO: Get this from a config setting - float m_UpdateTime; - -}; - -#define g_TriggerManager CTriggerManager::GetSingleton() - -#endif //ifndef INCLUDED_TRIGGERMANAGER +//======================================================== +//Description: Manages loading trigger specs (format in Atlas) and updating trigger objects +//======================================================= + +#ifndef INCLUDED_TRIGGERMANAGER +#define INCLUDED_TRIGGERMANAGER + +#include "ps/Singleton.h" +#include "scripting/ScriptableObject.h" +#include "simulation/ScriptObject.h" + +#include +#include + +class CXeromyces; +class XMBElement; + +//Trigger storage - used be Atlas to keep track of different triggers + +struct MapTriggerCondition +{ + MapTriggerCondition() : linkLogic(0), negated(false) { } + CStrW name, functionName, displayName; + std::list parameters; + int linkLogic; //0 = NONE, 1 = AND, 2 = OR + bool negated; +}; + +struct MapTriggerEffect +{ + CStrW name, functionName, displayName; + std::list parameters; +}; + +struct MapTriggerLogicBlock +{ + MapTriggerLogicBlock(size_t i, bool _not=false) : index(i), negated(_not) { } + size_t index; + bool negated; + + bool operator< (const MapTriggerLogicBlock& block) const { return (index < block.index); } + bool operator== (const MapTriggerLogicBlock& block) const { return (index == block.index); } +}; + +struct MapTrigger +{ + MapTrigger() : active(false), maxRunCount(0), timeValue(0) { } + bool active; + int maxRunCount; + float timeValue; + CStrW name, groupName; + + std::set logicBlocks; //Indices of where "(" 's go before + std::set logicBlockEnds; //Indices of where ")" 's come after + std::list conditions; + std::list effects; + + void AddLogicBlock(bool negated) { logicBlocks.insert( MapTriggerLogicBlock(conditions.size(), negated) ); } + void AddLogicBlockEnd() { logicBlockEnds.insert( effects.size() ); } +}; + +struct MapTriggerGroup +{ + MapTriggerGroup() { } + MapTriggerGroup(const CStrW& _name, const CStrW& _parentName) : name(_name), parentName(_parentName) {} + + std::list triggers; + std::list childGroups; //Indices of children + CStrW name, parentName; + + bool operator== (const CStrW& _name) const { return (name == _name); } + bool operator== (const MapTriggerGroup& group) const { return (name == group.name); } +}; + + +struct CopyIfRootChild +{ + CopyIfRootChild(std::list& groupList) : m_groupList(groupList) {} + void operator() ( const MapTriggerGroup& group ) + { + if ( group.parentName == L"Triggers" ) + m_groupList.push_back(group); + } +private: + void operator= (const CopyIfRootChild& UNUSED(group)) const {} + std::list& m_groupList; +}; + +//Triggers used by engine + +class CTrigger : public CJSObject +{ + int m_runCount; + CStrW m_conditionFuncString, m_effectFuncString, m_name, m_groupName; + CScriptObject m_effectFunction; + CScriptObject m_conditionFunction; + +public: + CTrigger(); + CTrigger(const CStrW& name, bool active, float delay, int maxRuns, const CStrW& condFunc, + const CStrW& effectFunc); + CTrigger(const CStrW& name, bool active, float delay, int maxRuns, + CScriptObject& condFunc, CScriptObject& effectFunc); + + CTrigger& operator= (const CTrigger& trigger); + ~CTrigger(); + + void SetFunctionBody(const CStrW& body); + const CStrW& GetConditionString() { return m_conditionFuncString; } + const CStrW& GetEffectString() { return m_effectFuncString; } + const CStrW& GetName() { return m_name; } + const CStrW& GetGroupName() { return m_groupName; } + + bool ShouldFire(); + //Returns false if trigger exceeds run count + bool Fire(); + + void Activate(JSContext* cx, uintN argc, jsval* argv); + void Deactivate(JSContext* cx, uintN argc, jsval* argv); + + static JSBool Construct( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval ); + static void ScriptingInit(); + + JSContext* m_cx; + + bool m_active; + //Changeable by Atlas or needed by manager, no additional affects required + int m_maxRunCount; + float m_timeLeft, m_timeDelay; + +}; + + +//*****Holds information specifying how conditions and effects should look like in atlas****** + +struct TriggerParameter +{ + TriggerParameter() {} + TriggerParameter(int _row, int _column) : row(_row), column(_column) {} + void SetWindowData(const CStrW& _windowType, CStrW& windowPosition, CStrW& windowSize); + //Sort parameters in the order they will be added to a sizer in atlas + bool operator< ( const TriggerParameter& rhs ) const; + bool operator< ( const int parameter ) const { return parameterOrder < parameter; } + bool operator== ( const int parameter ) const { return parameterOrder == parameter; } + + CStrW name, windowType, inputType; + int row, column, xPos, yPos, xSize, ySize, parameterOrder; +}; + +class CTriggerSpec +{ +public: + CTriggerSpec() {} + ~CTriggerSpec() {} + + void AddParameter(const TriggerParameter& param) { parameters.insert(param); } + const std::set& GetParameters() const { return parameters; } + + int funcParameters; + CStrW displayName, functionName; + + bool operator== (const std::wstring& display) const { return display == displayName; } + +private: + + //Sorted by parameter order to make things easy for Atlas + std::set parameters; +}; +typedef CTriggerSpec CTriggerEffect; +typedef CTriggerSpec CTriggerCondition; + + +/******************************Trigger manager******************************/ + +class CTriggerManager : public Singleton +{ +public: + typedef std::map::iterator TriggerIter ; + + CTriggerManager(); + ~CTriggerManager(); + + //Returns false on detection of error + bool LoadXml( const CStr& filename ); + void Update(float delta); + + //Add simulation trigger (probably only called from JS) + void AddTrigger(CTrigger* trigger); + void AddTrigger(MapTriggerGroup& group, const MapTrigger& trigger); + void AddGroup(const MapTriggerGroup& group); + void SetAllGroups(const std::list& groups); + void DestroyEngineTriggers(); + + const std::list& GetAllConditions() const { return m_ConditionSpecs; } + const std::list& GetAllEffects() const { return m_EffectSpecs; } + const std::list& GetAllTriggerGroups() const { return m_GroupList; } + std::vector GetTriggerChoices(const std::wstring& name); + std::vector GetTriggerTranslations(const std::wstring& name); + + std::map m_TriggerMap; //Simulation triggers - used in engine + +private: + bool LoadTriggerSpec( XMBElement condition, CXeromyces& XeroFile, bool isCondition ); + + //Contains choices for trigger choice box parameters, with key = spec.funcName+paramName + std::map > m_TriggerChoices; + std::map > m_TriggerTranslations; + + //Holds information which descibes trigger layout in atlas + std::list m_GroupList; + std::list m_ConditionSpecs; + std::list m_EffectSpecs; + + float m_UpdateRate; //TODO: Get this from a config setting + float m_UpdateTime; + +}; + +#define g_TriggerManager CTriggerManager::GetSingleton() + +#endif //ifndef INCLUDED_TRIGGERMANAGER diff --git a/source/simulation/scripting/SimulationScriptInit.cpp b/source/simulation/scripting/SimulationScriptInit.cpp index 24b72c9a68..6641514a39 100644 --- a/source/simulation/scripting/SimulationScriptInit.cpp +++ b/source/simulation/scripting/SimulationScriptInit.cpp @@ -1,96 +1,96 @@ -#include "precompiled.h" - -#include "lib/timer.h" -#include "simulation/Scheduler.h" -#include "simulation/EntityTemplate.h" -#include "simulation/EntityTemplateCollection.h" -#include "simulation/Entity.h" -#include "simulation/Projectile.h" -#include "simulation/EventHandlers.h" -#include "simulation/TriggerManager.h" -#include "simulation/FormationManager.h" -#include "simulation/FormationCollection.h" -#include "simulation/ProductionQueue.h" -#include "simulation/Technology.h" -#include "simulation/TechnologyCollection.h" -#include "simulation/PathfindEngine.h" - -void SimulationScriptInit() -{ - CJSProgressTimer::ScriptingInit(); - CEntityTemplate::ScriptingInit(); - CEntity::ScriptingInit(); - CProjectile::ScriptingInit(); - CTrigger::ScriptingInit(); - CProductionItem::ScriptingInit(); - CProductionQueue::ScriptingInit(); - CTechnology::ScriptingInit(); - - EntityCollection::Init( "EntityCollection" ); - - g_ScriptingHost.DefineConstant( "FORMATION_ENTER", CFormationEvent::FORMATION_ENTER ); - g_ScriptingHost.DefineConstant( "FORMATION_LEAVE", CFormationEvent::FORMATION_LEAVE ); - g_ScriptingHost.DefineConstant( "FORMATION_DAMAGE", CFormationEvent::FORMATION_DAMAGE ); - g_ScriptingHost.DefineConstant( "FORMATION_ATTACK", CFormationEvent::FORMATION_ATTACK ); - - g_ScriptingHost.DefineConstant( "NOTIFY_NONE", CEntityListener::NOTIFY_NONE ); - g_ScriptingHost.DefineConstant( "NOTIFY_GOTO", CEntityListener::NOTIFY_GOTO ); - g_ScriptingHost.DefineConstant( "NOTIFY_RUN", CEntityListener::NOTIFY_RUN ); - g_ScriptingHost.DefineConstant( "NOTIFY_FOLLOW", CEntityListener::NOTIFY_FOLLOW ); - g_ScriptingHost.DefineConstant( "NOTIFY_ATTACK", CEntityListener::NOTIFY_ATTACK ); - g_ScriptingHost.DefineConstant( "NOTIFY_DAMAGE", CEntityListener::NOTIFY_DAMAGE ); - g_ScriptingHost.DefineConstant( "NOTIFY_COMBAT", CEntityListener::NOTIFY_COMBAT ); - g_ScriptingHost.DefineConstant( "NOTIFY_ESCORT", CEntityListener::NOTIFY_ESCORT ); - g_ScriptingHost.DefineConstant( "NOTIFY_HEAL", CEntityListener::NOTIFY_HEAL ); - g_ScriptingHost.DefineConstant( "NOTIFY_GATHER", CEntityListener::NOTIFY_GATHER ); - g_ScriptingHost.DefineConstant( "NOTIFY_IDLE", CEntityListener::NOTIFY_IDLE ); - g_ScriptingHost.DefineConstant( "NOTIFY_ORDER_CHANGE", CEntityListener::NOTIFY_ORDER_CHANGE ); - g_ScriptingHost.DefineConstant( "NOTIFY_ALL", CEntityListener::NOTIFY_ALL ); - - g_ScriptingHost.DefineConstant( "ORDER_NONE", -1 ); - g_ScriptingHost.DefineConstant( "ORDER_GOTO", CEntityOrder::ORDER_GOTO ); - g_ScriptingHost.DefineConstant( "ORDER_RUN", CEntityOrder::ORDER_RUN ); - g_ScriptingHost.DefineConstant( "ORDER_PATROL", CEntityOrder::ORDER_PATROL ); - g_ScriptingHost.DefineConstant( "ORDER_CONTACT_ACTION", CEntityOrder::ORDER_CONTACT_ACTION ); - g_ScriptingHost.DefineConstant( "ORDER_PRODUCE", CEntityOrder::ORDER_PRODUCE ); - g_ScriptingHost.DefineConstant( "ORDER_SET_RALLY_POINT", CEntityOrder::ORDER_SET_RALLY_POINT ); - g_ScriptingHost.DefineConstant( "ORDER_SET_STANCE", CEntityOrder::ORDER_SET_STANCE ); - g_ScriptingHost.DefineConstant( "ORDER_START_CONSTRUCTION", CEntityOrder::ORDER_START_CONSTRUCTION ); -} - - -void SimulationInit() -{ - TIMER("SimulationInit"); - - new CEntityTemplateCollection; - new CFormationCollection; - new CTechnologyCollection; - g_EntityFormationCollection.LoadTemplates(); - g_TechnologyCollection.LoadTechnologies(); - new CFormationManager; - new CTriggerManager; - g_TriggerManager.LoadXml(CStr("scripts/TriggerSpecs.xml")); - g_ScriptingHost.RunScript("scripts/trigger_functions.js"); - - // CEntityManager is managed by CWorld - //new CEntityManager; - - new CPathfindEngine; -} - -void SimulationShutdown() -{ - TIMER_BEGIN("shutdown Pathfinder"); - delete &g_Pathfinder; - TIMER_END("shutdown Pathfinder"); - - // Managed by CWorld - // delete &g_EntityManager; - - delete &g_TriggerManager; - delete &g_FormationManager; - delete &g_TechnologyCollection; - delete &g_EntityFormationCollection; - delete &g_EntityTemplateCollection; -} +#include "precompiled.h" + +#include "lib/timer.h" +#include "simulation/Scheduler.h" +#include "simulation/EntityTemplate.h" +#include "simulation/EntityTemplateCollection.h" +#include "simulation/Entity.h" +#include "simulation/Projectile.h" +#include "simulation/EventHandlers.h" +#include "simulation/TriggerManager.h" +#include "simulation/FormationManager.h" +#include "simulation/FormationCollection.h" +#include "simulation/ProductionQueue.h" +#include "simulation/Technology.h" +#include "simulation/TechnologyCollection.h" +#include "simulation/PathfindEngine.h" + +void SimulationScriptInit() +{ + CJSProgressTimer::ScriptingInit(); + CEntityTemplate::ScriptingInit(); + CEntity::ScriptingInit(); + CProjectile::ScriptingInit(); + CTrigger::ScriptingInit(); + CProductionItem::ScriptingInit(); + CProductionQueue::ScriptingInit(); + CTechnology::ScriptingInit(); + + EntityCollection::Init( "EntityCollection" ); + + g_ScriptingHost.DefineConstant( "FORMATION_ENTER", CFormationEvent::FORMATION_ENTER ); + g_ScriptingHost.DefineConstant( "FORMATION_LEAVE", CFormationEvent::FORMATION_LEAVE ); + g_ScriptingHost.DefineConstant( "FORMATION_DAMAGE", CFormationEvent::FORMATION_DAMAGE ); + g_ScriptingHost.DefineConstant( "FORMATION_ATTACK", CFormationEvent::FORMATION_ATTACK ); + + g_ScriptingHost.DefineConstant( "NOTIFY_NONE", CEntityListener::NOTIFY_NONE ); + g_ScriptingHost.DefineConstant( "NOTIFY_GOTO", CEntityListener::NOTIFY_GOTO ); + g_ScriptingHost.DefineConstant( "NOTIFY_RUN", CEntityListener::NOTIFY_RUN ); + g_ScriptingHost.DefineConstant( "NOTIFY_FOLLOW", CEntityListener::NOTIFY_FOLLOW ); + g_ScriptingHost.DefineConstant( "NOTIFY_ATTACK", CEntityListener::NOTIFY_ATTACK ); + g_ScriptingHost.DefineConstant( "NOTIFY_DAMAGE", CEntityListener::NOTIFY_DAMAGE ); + g_ScriptingHost.DefineConstant( "NOTIFY_COMBAT", CEntityListener::NOTIFY_COMBAT ); + g_ScriptingHost.DefineConstant( "NOTIFY_ESCORT", CEntityListener::NOTIFY_ESCORT ); + g_ScriptingHost.DefineConstant( "NOTIFY_HEAL", CEntityListener::NOTIFY_HEAL ); + g_ScriptingHost.DefineConstant( "NOTIFY_GATHER", CEntityListener::NOTIFY_GATHER ); + g_ScriptingHost.DefineConstant( "NOTIFY_IDLE", CEntityListener::NOTIFY_IDLE ); + g_ScriptingHost.DefineConstant( "NOTIFY_ORDER_CHANGE", CEntityListener::NOTIFY_ORDER_CHANGE ); + g_ScriptingHost.DefineConstant( "NOTIFY_ALL", CEntityListener::NOTIFY_ALL ); + + g_ScriptingHost.DefineConstant( "ORDER_NONE", -1 ); + g_ScriptingHost.DefineConstant( "ORDER_GOTO", CEntityOrder::ORDER_GOTO ); + g_ScriptingHost.DefineConstant( "ORDER_RUN", CEntityOrder::ORDER_RUN ); + g_ScriptingHost.DefineConstant( "ORDER_PATROL", CEntityOrder::ORDER_PATROL ); + g_ScriptingHost.DefineConstant( "ORDER_CONTACT_ACTION", CEntityOrder::ORDER_CONTACT_ACTION ); + g_ScriptingHost.DefineConstant( "ORDER_PRODUCE", CEntityOrder::ORDER_PRODUCE ); + g_ScriptingHost.DefineConstant( "ORDER_SET_RALLY_POINT", CEntityOrder::ORDER_SET_RALLY_POINT ); + g_ScriptingHost.DefineConstant( "ORDER_SET_STANCE", CEntityOrder::ORDER_SET_STANCE ); + g_ScriptingHost.DefineConstant( "ORDER_START_CONSTRUCTION", CEntityOrder::ORDER_START_CONSTRUCTION ); +} + + +void SimulationInit() +{ + TIMER("SimulationInit"); + + new CEntityTemplateCollection; + new CFormationCollection; + new CTechnologyCollection; + g_EntityFormationCollection.LoadTemplates(); + g_TechnologyCollection.LoadTechnologies(); + new CFormationManager; + new CTriggerManager; + g_TriggerManager.LoadXml(CStr("scripts/TriggerSpecs.xml")); + g_ScriptingHost.RunScript("scripts/trigger_functions.js"); + + // CEntityManager is managed by CWorld + //new CEntityManager; + + new CPathfindEngine; +} + +void SimulationShutdown() +{ + TIMER_BEGIN("shutdown Pathfinder"); + delete &g_Pathfinder; + TIMER_END("shutdown Pathfinder"); + + // Managed by CWorld + // delete &g_EntityManager; + + delete &g_TriggerManager; + delete &g_FormationManager; + delete &g_TechnologyCollection; + delete &g_EntityFormationCollection; + delete &g_EntityTemplateCollection; +} diff --git a/source/simulation/scripting/SimulationScriptInit.h b/source/simulation/scripting/SimulationScriptInit.h index c0e2e1a02d..a5d1865d22 100644 --- a/source/simulation/scripting/SimulationScriptInit.h +++ b/source/simulation/scripting/SimulationScriptInit.h @@ -1,4 +1,4 @@ -extern void SimulationScriptInit(); - -extern void SimulationInit(); -extern void SimulationShutdown(); +extern void SimulationScriptInit(); + +extern void SimulationInit(); +extern void SimulationShutdown(); diff --git a/source/sound/SoundGroup.cpp b/source/sound/SoundGroup.cpp index 4343bb0cc9..36cd5e457b 100644 --- a/source/sound/SoundGroup.cpp +++ b/source/sound/SoundGroup.cpp @@ -1,360 +1,360 @@ -/** -* ========================================================================= -* File : SoundGroup.cpp -* Project : 0 A.D. -* Description : Loads up a group of sound files with shared properties, -* and provides a simple interface for playing them. -* ========================================================================= -*/ - -// license: GPL; see sound/license.txt - -#include "precompiled.h" -#include "SoundGroup.h" - -#include - -#include "ps/XML/Xeromyces.h" -#include "ps/CLogger.h" -#include "lib/rand.h" - -#define LOG_CATEGORY "audio" - - -void CSoundGroup::SetGain(float gain) -{ - gain = std::min(gain, 1.0f); - m_Gain = gain; -} - -void CSoundGroup::SetDefaultValues() -{ - m_index = 0; - m_Flags = 0; - m_Intensity = 0; - m_CurTime = 0.0f; - - // sane defaults; will probably be replaced by the values read during LoadSoundGroup. - SetGain(0.5f); - m_Pitch = 1.0f; - m_Priority = 60; - m_PitchUpper = 1.1f; - m_PitchLower = 0.9f; - m_GainUpper = 1.0f; - m_GainLower = 0.8f; - m_ConeOuterGain = 0.0f; - m_ConeInnerAngle = 360.0f; - m_ConeOuterAngle = 360.0f; - m_Decay = 3.0f; - m_IntensityThreshold = 3; - // WARNING: m_TimeWindow is currently unused and uninitialized -} - -CSoundGroup::CSoundGroup() -{ - SetDefaultValues(); -} - -CSoundGroup::CSoundGroup(const char *XMLfile) -{ - SetDefaultValues(); - LoadSoundGroup(XMLfile); -} - -CSoundGroup::~CSoundGroup() -{ - // clean up all the handles from this group. - ReleaseGroup(); -} - -static float RandFloat(float min, float max) -{ - return float(rand(min*100.0f, max*100.0f) / 100.0f); -} - -void CSoundGroup::UploadPropertiesAndPlay(Handle hSound, const CVector3D& position) -{ - // interface/UI sounds should always be played at the listener's - // position, which is achieved by setting position to 0 and - // having that treated as relative to the listener. - float x = 0.0f, y = 0.0f, z = 0.0f; - bool relative = true; - if(!TestFlag(eOmnipresent)) - { - x = position.X; - y = position.Y; - z = position.Z; - relative = false; - } - - snd_set_pos(hSound, x, y, z, relative); - - float gain = TestFlag(eRandGain)? RandFloat(m_GainLower, m_GainUpper) : m_Gain; - gain = std::min(gain, 1.0f); // guard against roundoff error in RandFloat or too high m_GainUpper - snd_set_gain(hSound, gain); - - const float pitch = TestFlag(eRandPitch)? RandFloat(m_PitchLower, m_PitchUpper) : m_Pitch; - snd_set_pitch(hSound, pitch); - - snd_play(hSound, m_Priority); -} - - -void CSoundGroup::PlayNext(const CVector3D& position) -{ - if(m_Intensity >= m_IntensityThreshold) - { - if(!is_playing(m_hReplacement)) - { - // load up replacement file - m_hReplacement = snd_open(m_filepath + m_intensity_file); - if(m_hReplacement < 0) // one cause: sound is disabled - return; - - UploadPropertiesAndPlay(m_hReplacement, position); - } - } - else - { - // if no sounds, return - if (filenames.size() == 0) - return; - - // try loading on the fly only when we need the sound to see if that fixes release problems... - if(TestFlag(eRandOrder)) - m_index = (size_t)rand(0, (size_t)filenames.size()); - // (note: previously snd_group[m_index] was used in place of hs) - Handle hs = snd_open(m_filepath + filenames[m_index]); - if(hs < 0) // one cause: sound is disabled - return; - - UploadPropertiesAndPlay(hs, position); - } - - playtimes[m_index] = 0.0f; - m_index++; - m_Intensity++; - if(m_Intensity > m_IntensityThreshold) - m_Intensity = m_IntensityThreshold; - - if(m_index >= filenames.size()) - Reload(); -} - -void CSoundGroup::Reload() -{ - m_index = 0; // reset our index - // get rid of the used handles - snd_group.clear(); - // clear out the old timers - playtimes.clear(); - //Reload the sounds - /*for(size_t i = 0; i < filenames.size(); i++) - { - string szTemp = m_filepath + filenames[i]; - Handle temp = snd_open(m_filepath + filenames[i]); - snd_set_gain(temp, m_Gain); - snd_set_pitch(temp, m_Pitch); - snd_set_cone(temp, m_ConeInnerAngle, m_ConeOuterAngle, m_ConeOuterGain); - snd_group.push_back(temp); - - }*/ - while(playtimes.size() < filenames.size()) - playtimes.push_back(-1.0f); - //if(TestFlag(eRandOrder)) - //random_shuffle(snd_group.begin(), snd_group.end()); -} - -void CSoundGroup::ReleaseGroup() -{ - for(size_t i = m_index; i= 0.0f) - playtimes[i] += TimeSinceLastFrame; - - if(playtimes[i] >= m_Decay) - { - playtimes[i] = -1.0f; - m_Intensity--; - } - } -} - -bool CSoundGroup::LoadSoundGroup(const char *XMLfile) -{ - CXeromyces XeroFile; - if (XeroFile.Load(XMLfile) != PSRETURN_OK) - return false; - - // adjust the path name for resources if necessary - //m_Name = XMLfile + directorypath; - - //Define elements used in XML file - #define EL(x) int el_##x = XeroFile.GetElementID(#x) - #define AT(x) int at_##x = XeroFile.GetAttributeID(#x) - EL(soundgroup); - EL(gain); - EL(looping); - EL(omnipresent); - EL(pitch); - EL(priority); - EL(randorder); - EL(randgain); - EL(randpitch); - EL(conegain); - EL(coneinner); - EL(coneouter); - EL(sound); - EL(gainupper); - EL(gainlower); - EL(pitchupper); - EL(pitchlower); - EL(path); - EL(threshold); - EL(decay); - EL(replacement); - #undef AT - #undef EL - - XMBElement root = XeroFile.GetRoot(); - - if (root.GetNodeName() != el_soundgroup) - { - LOG(CLogger::Error, LOG_CATEGORY, "Invalid SoundGroup format (unrecognised root element '%s')", XeroFile.GetElementString(root.GetNodeName()).c_str()); - return false; - } - - XERO_ITER_EL(root, child) - { - - int child_name = child.GetNodeName(); - - if(child_name == el_gain) - { - SetGain(CStr(child.GetText()).ToFloat()); - } - - if(child_name == el_looping) - { - if(CStr(child.GetText()).ToInt() == 1) - SetFlag(eLoop); - } - - if(child_name == el_omnipresent) - { - if(CStr(child.GetText()).ToInt() == 1) - SetFlag(eOmnipresent); - } - - if(child_name == el_pitch) - { - this->m_Pitch = CStr(child.GetText()).ToFloat(); - } - - if(child_name == el_priority) - { - this->m_Priority = CStr(child.GetText()).ToFloat(); - } - - if(child_name == el_randorder) - { - if(CStr(child.GetText()).ToInt() == 1) - SetFlag(eRandOrder); - } - - if(child_name == el_randgain) - { - if(CStr(child.GetText()).ToInt() == 1) - SetFlag(eRandGain); - } - - if(child_name == el_gainupper) - { - this->m_GainUpper = CStr(child.GetText()).ToFloat(); - } - - if(child_name == el_gainlower) - { - this->m_GainLower = CStr(child.GetText()).ToFloat(); - } - - if(child_name == el_randpitch) - { - if(CStr(child.GetText()).ToInt() == 1) - SetFlag(eRandPitch); - } - - if(child_name == el_pitchupper) - { - this->m_PitchUpper = CStr(child.GetText()).ToFloat(); - } - - if(child_name == el_pitchlower) - { - this->m_PitchLower = CStr(child.GetText()).ToFloat(); - } - - if(child_name == el_conegain) - { - this->m_ConeOuterGain = CStr(child.GetText()).ToFloat(); - } - - if(child_name == el_coneinner) - { - this->m_ConeInnerAngle = CStr(child.GetText()).ToFloat(); - } - - if(child_name == el_coneouter) - { - this->m_ConeOuterAngle = CStr(child.GetText()).ToFloat(); - } - - if(child_name == el_sound) - { - CStr szTemp(child.GetText()); - this->filenames.push_back(szTemp); - - } - if(child_name == el_path) - { - m_filepath = child.GetText(); - - } - if(child_name == el_threshold) - { - //m_intensity_threshold = CStr(child.GetText()).ToFloat(); - m_IntensityThreshold = CStr(child.GetText()).ToFloat(); - } - - if(child_name == el_decay) - { - //m_intensity_threshold = CStr(child.GetText()).ToFloat(); - m_Decay = CStr(child.GetText()).ToFloat(); - } - - if(child_name == el_replacement) - { - m_intensity_file = child.GetText(); - - } - } - - Reload(); - return true; - -} +/** +* ========================================================================= +* File : SoundGroup.cpp +* Project : 0 A.D. +* Description : Loads up a group of sound files with shared properties, +* and provides a simple interface for playing them. +* ========================================================================= +*/ + +// license: GPL; see sound/license.txt + +#include "precompiled.h" +#include "SoundGroup.h" + +#include + +#include "ps/XML/Xeromyces.h" +#include "ps/CLogger.h" +#include "lib/rand.h" + +#define LOG_CATEGORY "audio" + + +void CSoundGroup::SetGain(float gain) +{ + gain = std::min(gain, 1.0f); + m_Gain = gain; +} + +void CSoundGroup::SetDefaultValues() +{ + m_index = 0; + m_Flags = 0; + m_Intensity = 0; + m_CurTime = 0.0f; + + // sane defaults; will probably be replaced by the values read during LoadSoundGroup. + SetGain(0.5f); + m_Pitch = 1.0f; + m_Priority = 60; + m_PitchUpper = 1.1f; + m_PitchLower = 0.9f; + m_GainUpper = 1.0f; + m_GainLower = 0.8f; + m_ConeOuterGain = 0.0f; + m_ConeInnerAngle = 360.0f; + m_ConeOuterAngle = 360.0f; + m_Decay = 3.0f; + m_IntensityThreshold = 3; + // WARNING: m_TimeWindow is currently unused and uninitialized +} + +CSoundGroup::CSoundGroup() +{ + SetDefaultValues(); +} + +CSoundGroup::CSoundGroup(const char *XMLfile) +{ + SetDefaultValues(); + LoadSoundGroup(XMLfile); +} + +CSoundGroup::~CSoundGroup() +{ + // clean up all the handles from this group. + ReleaseGroup(); +} + +static float RandFloat(float min, float max) +{ + return float(rand(min*100.0f, max*100.0f) / 100.0f); +} + +void CSoundGroup::UploadPropertiesAndPlay(Handle hSound, const CVector3D& position) +{ + // interface/UI sounds should always be played at the listener's + // position, which is achieved by setting position to 0 and + // having that treated as relative to the listener. + float x = 0.0f, y = 0.0f, z = 0.0f; + bool relative = true; + if(!TestFlag(eOmnipresent)) + { + x = position.X; + y = position.Y; + z = position.Z; + relative = false; + } + + snd_set_pos(hSound, x, y, z, relative); + + float gain = TestFlag(eRandGain)? RandFloat(m_GainLower, m_GainUpper) : m_Gain; + gain = std::min(gain, 1.0f); // guard against roundoff error in RandFloat or too high m_GainUpper + snd_set_gain(hSound, gain); + + const float pitch = TestFlag(eRandPitch)? RandFloat(m_PitchLower, m_PitchUpper) : m_Pitch; + snd_set_pitch(hSound, pitch); + + snd_play(hSound, m_Priority); +} + + +void CSoundGroup::PlayNext(const CVector3D& position) +{ + if(m_Intensity >= m_IntensityThreshold) + { + if(!is_playing(m_hReplacement)) + { + // load up replacement file + m_hReplacement = snd_open(m_filepath + m_intensity_file); + if(m_hReplacement < 0) // one cause: sound is disabled + return; + + UploadPropertiesAndPlay(m_hReplacement, position); + } + } + else + { + // if no sounds, return + if (filenames.size() == 0) + return; + + // try loading on the fly only when we need the sound to see if that fixes release problems... + if(TestFlag(eRandOrder)) + m_index = (size_t)rand(0, (size_t)filenames.size()); + // (note: previously snd_group[m_index] was used in place of hs) + Handle hs = snd_open(m_filepath + filenames[m_index]); + if(hs < 0) // one cause: sound is disabled + return; + + UploadPropertiesAndPlay(hs, position); + } + + playtimes[m_index] = 0.0f; + m_index++; + m_Intensity++; + if(m_Intensity > m_IntensityThreshold) + m_Intensity = m_IntensityThreshold; + + if(m_index >= filenames.size()) + Reload(); +} + +void CSoundGroup::Reload() +{ + m_index = 0; // reset our index + // get rid of the used handles + snd_group.clear(); + // clear out the old timers + playtimes.clear(); + //Reload the sounds + /*for(size_t i = 0; i < filenames.size(); i++) + { + string szTemp = m_filepath + filenames[i]; + Handle temp = snd_open(m_filepath + filenames[i]); + snd_set_gain(temp, m_Gain); + snd_set_pitch(temp, m_Pitch); + snd_set_cone(temp, m_ConeInnerAngle, m_ConeOuterAngle, m_ConeOuterGain); + snd_group.push_back(temp); + + }*/ + while(playtimes.size() < filenames.size()) + playtimes.push_back(-1.0f); + //if(TestFlag(eRandOrder)) + //random_shuffle(snd_group.begin(), snd_group.end()); +} + +void CSoundGroup::ReleaseGroup() +{ + for(size_t i = m_index; i= 0.0f) + playtimes[i] += TimeSinceLastFrame; + + if(playtimes[i] >= m_Decay) + { + playtimes[i] = -1.0f; + m_Intensity--; + } + } +} + +bool CSoundGroup::LoadSoundGroup(const char *XMLfile) +{ + CXeromyces XeroFile; + if (XeroFile.Load(XMLfile) != PSRETURN_OK) + return false; + + // adjust the path name for resources if necessary + //m_Name = XMLfile + directorypath; + + //Define elements used in XML file + #define EL(x) int el_##x = XeroFile.GetElementID(#x) + #define AT(x) int at_##x = XeroFile.GetAttributeID(#x) + EL(soundgroup); + EL(gain); + EL(looping); + EL(omnipresent); + EL(pitch); + EL(priority); + EL(randorder); + EL(randgain); + EL(randpitch); + EL(conegain); + EL(coneinner); + EL(coneouter); + EL(sound); + EL(gainupper); + EL(gainlower); + EL(pitchupper); + EL(pitchlower); + EL(path); + EL(threshold); + EL(decay); + EL(replacement); + #undef AT + #undef EL + + XMBElement root = XeroFile.GetRoot(); + + if (root.GetNodeName() != el_soundgroup) + { + LOG(CLogger::Error, LOG_CATEGORY, "Invalid SoundGroup format (unrecognised root element '%s')", XeroFile.GetElementString(root.GetNodeName()).c_str()); + return false; + } + + XERO_ITER_EL(root, child) + { + + int child_name = child.GetNodeName(); + + if(child_name == el_gain) + { + SetGain(CStr(child.GetText()).ToFloat()); + } + + if(child_name == el_looping) + { + if(CStr(child.GetText()).ToInt() == 1) + SetFlag(eLoop); + } + + if(child_name == el_omnipresent) + { + if(CStr(child.GetText()).ToInt() == 1) + SetFlag(eOmnipresent); + } + + if(child_name == el_pitch) + { + this->m_Pitch = CStr(child.GetText()).ToFloat(); + } + + if(child_name == el_priority) + { + this->m_Priority = CStr(child.GetText()).ToFloat(); + } + + if(child_name == el_randorder) + { + if(CStr(child.GetText()).ToInt() == 1) + SetFlag(eRandOrder); + } + + if(child_name == el_randgain) + { + if(CStr(child.GetText()).ToInt() == 1) + SetFlag(eRandGain); + } + + if(child_name == el_gainupper) + { + this->m_GainUpper = CStr(child.GetText()).ToFloat(); + } + + if(child_name == el_gainlower) + { + this->m_GainLower = CStr(child.GetText()).ToFloat(); + } + + if(child_name == el_randpitch) + { + if(CStr(child.GetText()).ToInt() == 1) + SetFlag(eRandPitch); + } + + if(child_name == el_pitchupper) + { + this->m_PitchUpper = CStr(child.GetText()).ToFloat(); + } + + if(child_name == el_pitchlower) + { + this->m_PitchLower = CStr(child.GetText()).ToFloat(); + } + + if(child_name == el_conegain) + { + this->m_ConeOuterGain = CStr(child.GetText()).ToFloat(); + } + + if(child_name == el_coneinner) + { + this->m_ConeInnerAngle = CStr(child.GetText()).ToFloat(); + } + + if(child_name == el_coneouter) + { + this->m_ConeOuterAngle = CStr(child.GetText()).ToFloat(); + } + + if(child_name == el_sound) + { + CStr szTemp(child.GetText()); + this->filenames.push_back(szTemp); + + } + if(child_name == el_path) + { + m_filepath = child.GetText(); + + } + if(child_name == el_threshold) + { + //m_intensity_threshold = CStr(child.GetText()).ToFloat(); + m_IntensityThreshold = CStr(child.GetText()).ToFloat(); + } + + if(child_name == el_decay) + { + //m_intensity_threshold = CStr(child.GetText()).ToFloat(); + m_Decay = CStr(child.GetText()).ToFloat(); + } + + if(child_name == el_replacement) + { + m_intensity_file = child.GetText(); + + } + } + + Reload(); + return true; + +} diff --git a/source/sound/SoundGroup.h b/source/sound/SoundGroup.h index e2a5d57c14..e0b42f824f 100644 --- a/source/sound/SoundGroup.h +++ b/source/sound/SoundGroup.h @@ -1,120 +1,120 @@ -/** -* ========================================================================= -* File : SoundGroup.h -* Project : 0 A.D. -* Description : Loads up a group of sound files with shared properties, -* and provides a simple interface for playing them. -* ========================================================================= -*/ - -// license: GPL; see sound/license.txt - -/* -Example usage: (SEE SOUNDGROUPMGR.H) - - -Example SoundGroup.xml - - - 1.0 - 0 - 1.0 - 100 - 0 - 0 - 0 - 1.0 - 360 - 360 - audio/voice/hellenes/soldier/Attack_Attackx.ogg - audio/voice/hellenes/soldier/Attack_Chargex.ogg - audio/voice/hellenes/soldier/Attack_Engagex.ogg - audio/voice/hellenes/soldier/Attack_ForMyFamily.ogg - - -*/ - -#ifndef INCLUDED_SOUNDGROUP -#define INCLUDED_SOUNDGROUP - -#include "lib/res/handle.h" -#include "ps/CStr.h" -#include "maths/Vector3D.h" -#include "lib/res/sound/snd_mgr.h" - -#include - -enum eSndGrpFlags -{ - eRandOrder = 0x01, - eRandGain = 0x02, - eRandPitch = 0x04, - eLoop = 0x08, - eOmnipresent = 0x10 -}; - - -class CSoundGroup -{ -public: - CSoundGroup(const char *XMLfile); - CSoundGroup(void); - ~CSoundGroup(void); - - // Play next sound in group - // @param position world position of the entity generating the sound - // (ignored if the eOmnipresent flag is set) - void PlayNext(const CVector3D& position); - - // Load a group - bool LoadSoundGroup(const char *XMLfile); - - void Reload(); - - // Release all remaining loaded handles - void ReleaseGroup(); - - // Update SoundGroup, remove dead sounds from intensity count - void Update(float TimeSinceLastFrame); - - // Set a flag using a value from eSndGrpFlags - inline void SetFlag(int flag){ m_Flags |= flag; } - - // Test flag, returns true if flag is set. - inline bool TestFlag(int flag) { return (m_Flags & flag) != 0;} - -private: - void SetGain(float gain); - void UploadPropertiesAndPlay(Handle hSound, const CVector3D& position); - void SetDefaultValues(); - - size_t m_index; // index of the next sound to play - - Handle m_hReplacement; - - std::vector snd_group; // we store the handles so we can load now and play later - std::vector filenames; // we need the filenames so we can reload when necessary. - std::vector playtimes; // it would be better to store this in with the Handles perhaps? - CStr m_filepath; // the file path for the list of sound file resources - CStr m_intensity_file; // the replacement aggregate 'intense' sound - - float m_CurTime; // Time elapsed since soundgroup was created - float m_TimeWindow; // The Intensity Threshold Window - size_t m_IntensityThreshold; // the allowable intensity before a sound switch - size_t m_Intensity; // our current intensity(number of sounds played since m_CurTime - m_TimeWindow) - float m_Decay; // - unsigned char m_Flags; // up to eight individual parameters, use with eSndGrpFlags. - - float m_Gain; - float m_Pitch; - float m_Priority; - float m_ConeOuterGain; - float m_PitchUpper; - float m_PitchLower; - float m_GainUpper; - float m_GainLower; - float m_ConeInnerAngle; - float m_ConeOuterAngle; -}; - -#endif //#ifndef INCLUDED_SOUNDGROUP +/** +* ========================================================================= +* File : SoundGroup.h +* Project : 0 A.D. +* Description : Loads up a group of sound files with shared properties, +* and provides a simple interface for playing them. +* ========================================================================= +*/ + +// license: GPL; see sound/license.txt + +/* +Example usage: (SEE SOUNDGROUPMGR.H) + + +Example SoundGroup.xml + + + 1.0 + 0 + 1.0 + 100 + 0 + 0 + 0 + 1.0 + 360 + 360 + audio/voice/hellenes/soldier/Attack_Attackx.ogg + audio/voice/hellenes/soldier/Attack_Chargex.ogg + audio/voice/hellenes/soldier/Attack_Engagex.ogg + audio/voice/hellenes/soldier/Attack_ForMyFamily.ogg + + +*/ + +#ifndef INCLUDED_SOUNDGROUP +#define INCLUDED_SOUNDGROUP + +#include "lib/res/handle.h" +#include "ps/CStr.h" +#include "maths/Vector3D.h" +#include "lib/res/sound/snd_mgr.h" + +#include + +enum eSndGrpFlags +{ + eRandOrder = 0x01, + eRandGain = 0x02, + eRandPitch = 0x04, + eLoop = 0x08, + eOmnipresent = 0x10 +}; + + +class CSoundGroup +{ +public: + CSoundGroup(const char *XMLfile); + CSoundGroup(void); + ~CSoundGroup(void); + + // Play next sound in group + // @param position world position of the entity generating the sound + // (ignored if the eOmnipresent flag is set) + void PlayNext(const CVector3D& position); + + // Load a group + bool LoadSoundGroup(const char *XMLfile); + + void Reload(); + + // Release all remaining loaded handles + void ReleaseGroup(); + + // Update SoundGroup, remove dead sounds from intensity count + void Update(float TimeSinceLastFrame); + + // Set a flag using a value from eSndGrpFlags + inline void SetFlag(int flag){ m_Flags |= flag; } + + // Test flag, returns true if flag is set. + inline bool TestFlag(int flag) { return (m_Flags & flag) != 0;} + +private: + void SetGain(float gain); + void UploadPropertiesAndPlay(Handle hSound, const CVector3D& position); + void SetDefaultValues(); + + size_t m_index; // index of the next sound to play + + Handle m_hReplacement; + + std::vector snd_group; // we store the handles so we can load now and play later + std::vector filenames; // we need the filenames so we can reload when necessary. + std::vector playtimes; // it would be better to store this in with the Handles perhaps? + CStr m_filepath; // the file path for the list of sound file resources + CStr m_intensity_file; // the replacement aggregate 'intense' sound + + float m_CurTime; // Time elapsed since soundgroup was created + float m_TimeWindow; // The Intensity Threshold Window + size_t m_IntensityThreshold; // the allowable intensity before a sound switch + size_t m_Intensity; // our current intensity(number of sounds played since m_CurTime - m_TimeWindow) + float m_Decay; // + unsigned char m_Flags; // up to eight individual parameters, use with eSndGrpFlags. + + float m_Gain; + float m_Pitch; + float m_Priority; + float m_ConeOuterGain; + float m_PitchUpper; + float m_PitchLower; + float m_GainUpper; + float m_GainLower; + float m_ConeInnerAngle; + float m_ConeOuterAngle; +}; + +#endif //#ifndef INCLUDED_SOUNDGROUP diff --git a/source/sound/SoundGroupMgr.cpp b/source/sound/SoundGroupMgr.cpp index eaba0379c2..ac981a2e95 100644 --- a/source/sound/SoundGroupMgr.cpp +++ b/source/sound/SoundGroupMgr.cpp @@ -1,146 +1,146 @@ -/** -* ========================================================================= -* File : SoundGroupMgr.h -* Project : 0 A.D. -* Description : Manages and updates SoundGroups -* ========================================================================= -*/ - -// license: GPL; see sound/license.txt - -// Example usage: - -// size_t index; -// CSoundGroupMgr *sgm = CSoundGroupMgr::GetInstance(); -// index = sgm->AddGroup("SoundGroup.xml"); - -// sgm->UpdateSoundGroups(TimeSinceLastFrame); // call in Frame() - -// sgm->PlayNext(index); // wash-rinse-repeat - - -// sgm->RemoveGroup(index); // Remove the group if you like - -// sgm->DeleteInstance(); // Delete instance in shutdown - - -#include "precompiled.h" -#include "SoundGroupMgr.h" - -typedef std::vector SoundGroups; -typedef SoundGroups::iterator SoundGroupIt; - - -CSoundGroupMgr *CSoundGroupMgr::m_pInstance = 0; - -CSoundGroupMgr::CSoundGroupMgr() -{ - -} - -CSoundGroupMgr *CSoundGroupMgr::GetInstance() -{ - if(!m_pInstance) - m_pInstance = new CSoundGroupMgr(); - - return m_pInstance; - -} - -void CSoundGroupMgr::DeleteInstance() -{ - if(m_pInstance) - { - SoundGroupIt vIter = m_pInstance->m_Groups.begin(); - while(vIter != m_pInstance->m_Groups.end()) - vIter = m_pInstance->RemoveGroup(vIter); - - delete m_pInstance; - } - m_pInstance = 0; - -} - -/////////////////////////////////////////// -// AddGroup() -// in: const char *XMLFile - the filename of the SoundGroup.xml to open -// out: size_t index into m_Groups -// Loads the given XML file and returns an index for later use -/////////////////////////////////////////// -size_t CSoundGroupMgr::AddGroup(const char *XMLFile) -{ - CSoundGroup* newGroup = new CSoundGroup(XMLFile); - m_Groups.push_back(newGroup); - - return m_Groups.size() - 1; -} - -/////////////////////////////////////////// -// RemoveGroup() -// in: size_t index into m_Groups -// out: SoundGroupIt - one past the index removed (sometimes useful) -// Removes and Releases a given soundgroup -/////////////////////////////////////////// -SoundGroupIt CSoundGroupMgr::RemoveGroup(size_t index) -{ - SoundGroupIt vIter = m_Groups.begin(); - if(index >= m_Groups.size()) - return vIter; - - CSoundGroup *temp = (*vIter); - (*vIter)->ReleaseGroup(); - vIter = m_Groups.erase(vIter+index); - - delete temp; - - return vIter; - -} - -/////////////////////////////////////////// -// RemoveGroup() -// in: SoundGroupIt - item to remove -// out: SoundGroupIt - one past the index removed (sometimes useful) -// Removes and Releases a given soundgroup -/////////////////////////////////////////// -SoundGroupIt CSoundGroupMgr::RemoveGroup(SoundGroupIt iter) -{ - - (*iter)->ReleaseGroup(); - - CSoundGroup *temp = (*iter); - - iter = m_Groups.erase(iter); - - delete temp; - - return iter; - -} - -/////////////////////////////////////////// -// UpdateSoundGroups() -// updates all soundgroups, call in Frame() -/////////////////////////////////////////// -void CSoundGroupMgr::UpdateSoundGroups(float TimeSinceLastFrame) -{ - SoundGroupIt vIter = m_Groups.begin(); - while(vIter != m_Groups.end()) - { - (*vIter)->Update(TimeSinceLastFrame); - vIter++; - } -} - -/////////////////////////////////////////// -// PlayNext() -// in: size_t index - index into m_Groups -// Plays the next queued sound in an indexed group -/////////////////////////////////////////// -void CSoundGroupMgr::PlayNext(size_t index, const CVector3D& position) -{ - if(index < m_Groups.size()) - m_Groups[index]->PlayNext(position); - else - debug_printf("SND: PlayNext(%d) invalid, %d groups defined\n", index, m_Groups.size()); -} +/** +* ========================================================================= +* File : SoundGroupMgr.h +* Project : 0 A.D. +* Description : Manages and updates SoundGroups +* ========================================================================= +*/ + +// license: GPL; see sound/license.txt + +// Example usage: + +// size_t index; +// CSoundGroupMgr *sgm = CSoundGroupMgr::GetInstance(); +// index = sgm->AddGroup("SoundGroup.xml"); + +// sgm->UpdateSoundGroups(TimeSinceLastFrame); // call in Frame() + +// sgm->PlayNext(index); // wash-rinse-repeat + + +// sgm->RemoveGroup(index); // Remove the group if you like + +// sgm->DeleteInstance(); // Delete instance in shutdown + + +#include "precompiled.h" +#include "SoundGroupMgr.h" + +typedef std::vector SoundGroups; +typedef SoundGroups::iterator SoundGroupIt; + + +CSoundGroupMgr *CSoundGroupMgr::m_pInstance = 0; + +CSoundGroupMgr::CSoundGroupMgr() +{ + +} + +CSoundGroupMgr *CSoundGroupMgr::GetInstance() +{ + if(!m_pInstance) + m_pInstance = new CSoundGroupMgr(); + + return m_pInstance; + +} + +void CSoundGroupMgr::DeleteInstance() +{ + if(m_pInstance) + { + SoundGroupIt vIter = m_pInstance->m_Groups.begin(); + while(vIter != m_pInstance->m_Groups.end()) + vIter = m_pInstance->RemoveGroup(vIter); + + delete m_pInstance; + } + m_pInstance = 0; + +} + +/////////////////////////////////////////// +// AddGroup() +// in: const char *XMLFile - the filename of the SoundGroup.xml to open +// out: size_t index into m_Groups +// Loads the given XML file and returns an index for later use +/////////////////////////////////////////// +size_t CSoundGroupMgr::AddGroup(const char *XMLFile) +{ + CSoundGroup* newGroup = new CSoundGroup(XMLFile); + m_Groups.push_back(newGroup); + + return m_Groups.size() - 1; +} + +/////////////////////////////////////////// +// RemoveGroup() +// in: size_t index into m_Groups +// out: SoundGroupIt - one past the index removed (sometimes useful) +// Removes and Releases a given soundgroup +/////////////////////////////////////////// +SoundGroupIt CSoundGroupMgr::RemoveGroup(size_t index) +{ + SoundGroupIt vIter = m_Groups.begin(); + if(index >= m_Groups.size()) + return vIter; + + CSoundGroup *temp = (*vIter); + (*vIter)->ReleaseGroup(); + vIter = m_Groups.erase(vIter+index); + + delete temp; + + return vIter; + +} + +/////////////////////////////////////////// +// RemoveGroup() +// in: SoundGroupIt - item to remove +// out: SoundGroupIt - one past the index removed (sometimes useful) +// Removes and Releases a given soundgroup +/////////////////////////////////////////// +SoundGroupIt CSoundGroupMgr::RemoveGroup(SoundGroupIt iter) +{ + + (*iter)->ReleaseGroup(); + + CSoundGroup *temp = (*iter); + + iter = m_Groups.erase(iter); + + delete temp; + + return iter; + +} + +/////////////////////////////////////////// +// UpdateSoundGroups() +// updates all soundgroups, call in Frame() +/////////////////////////////////////////// +void CSoundGroupMgr::UpdateSoundGroups(float TimeSinceLastFrame) +{ + SoundGroupIt vIter = m_Groups.begin(); + while(vIter != m_Groups.end()) + { + (*vIter)->Update(TimeSinceLastFrame); + vIter++; + } +} + +/////////////////////////////////////////// +// PlayNext() +// in: size_t index - index into m_Groups +// Plays the next queued sound in an indexed group +/////////////////////////////////////////// +void CSoundGroupMgr::PlayNext(size_t index, const CVector3D& position) +{ + if(index < m_Groups.size()) + m_Groups[index]->PlayNext(position); + else + debug_printf("SND: PlayNext(%d) invalid, %d groups defined\n", index, m_Groups.size()); +} diff --git a/source/sound/SoundGroupMgr.h b/source/sound/SoundGroupMgr.h index 599ac5cc37..aee4dcd8f2 100644 --- a/source/sound/SoundGroupMgr.h +++ b/source/sound/SoundGroupMgr.h @@ -1,67 +1,67 @@ -/** -* ========================================================================= -* File : SoundGroupMgr.h -* Project : 0 A.D. -* Description : Manages and updates SoundGroups -* ========================================================================= -*/ - -// license: GPL; see sound/license.txt - -#include "SoundGroup.h" -#include - -class CSoundGroupMgr -{ -public: - std::vector m_Groups; // a collection of sound groups - static CSoundGroupMgr *m_pInstance; // our static instance of the manager - static CSoundGroupMgr *GetInstance(); - static void DeleteInstance(); - - /////////////////////////////////////////// - // UpdateSoundGroups() - // updates all soundgroups, call in Frame() - /////////////////////////////////////////// - void UpdateSoundGroups(float TimeSinceLastFrame); - - /////////////////////////////////////////// - // PlayNext() - // in: size_t index - index into m_Groups - // Plays the next queued sound in an indexed group - /////////////////////////////////////////// - void PlayNext(size_t index, const CVector3D& position); - - /////////////////////////////////////////// - // AddGroup() - // in: const char *XMLFile - the filename of the SoundGroup.xml to open - // out: size_t index into m_Groups - // Loads the given XML file and returns an index for later use - /////////////////////////////////////////// - size_t AddGroup(const char *XMLFile); - - /////////////////////////////////////////// - // RemoveGroup() - // in: size_t index into m_Groups - // out: std::vector::iterator - one past the index removed (sometimes useful) - // Removes and Releases a given soundgroup - /////////////////////////////////////////// - std::vector::iterator RemoveGroup(size_t index); - - /////////////////////////////////////////// - // RemoveGroup() - // in: std::vector::iterator - item to remove - // out: std::vector::iterator - one past the index removed (sometimes useful) - // Removes and Releases a given soundgroup - /////////////////////////////////////////// - std::vector::iterator RemoveGroup(std::vector::iterator iter); - - -private: - CSoundGroupMgr(); - CSoundGroupMgr(const CSoundGroupMgr &ref); - CSoundGroupMgr &operator=(const CSoundGroupMgr &ref); - -}; - -#define g_soundGroupMgr CSoundGroupMgr::GetInstance() +/** +* ========================================================================= +* File : SoundGroupMgr.h +* Project : 0 A.D. +* Description : Manages and updates SoundGroups +* ========================================================================= +*/ + +// license: GPL; see sound/license.txt + +#include "SoundGroup.h" +#include + +class CSoundGroupMgr +{ +public: + std::vector m_Groups; // a collection of sound groups + static CSoundGroupMgr *m_pInstance; // our static instance of the manager + static CSoundGroupMgr *GetInstance(); + static void DeleteInstance(); + + /////////////////////////////////////////// + // UpdateSoundGroups() + // updates all soundgroups, call in Frame() + /////////////////////////////////////////// + void UpdateSoundGroups(float TimeSinceLastFrame); + + /////////////////////////////////////////// + // PlayNext() + // in: size_t index - index into m_Groups + // Plays the next queued sound in an indexed group + /////////////////////////////////////////// + void PlayNext(size_t index, const CVector3D& position); + + /////////////////////////////////////////// + // AddGroup() + // in: const char *XMLFile - the filename of the SoundGroup.xml to open + // out: size_t index into m_Groups + // Loads the given XML file and returns an index for later use + /////////////////////////////////////////// + size_t AddGroup(const char *XMLFile); + + /////////////////////////////////////////// + // RemoveGroup() + // in: size_t index into m_Groups + // out: std::vector::iterator - one past the index removed (sometimes useful) + // Removes and Releases a given soundgroup + /////////////////////////////////////////// + std::vector::iterator RemoveGroup(size_t index); + + /////////////////////////////////////////// + // RemoveGroup() + // in: std::vector::iterator - item to remove + // out: std::vector::iterator - one past the index removed (sometimes useful) + // Removes and Releases a given soundgroup + /////////////////////////////////////////// + std::vector::iterator RemoveGroup(std::vector::iterator iter); + + +private: + CSoundGroupMgr(); + CSoundGroupMgr(const CSoundGroupMgr &ref); + CSoundGroupMgr &operator=(const CSoundGroupMgr &ref); + +}; + +#define g_soundGroupMgr CSoundGroupMgr::GetInstance()