From ae38aee3d24f0b02bfeb43c2dc571e1e273c6cc0 Mon Sep 17 00:00:00 2001 From: olsner Date: Wed, 19 Jan 2005 22:32:25 +0000 Subject: [PATCH] Revamped CNetClient/CNetServer JS Interface and new CGameAttributes implementation (moving towards a merger of CGameAttribs into CGame). This was SVN commit r1746. --- source/main.cpp | 25 +- source/ps/Game.cpp | 140 ++++++------ source/ps/Game.h | 64 ++++-- source/ps/Network/AllNetMessages.h | 6 +- source/ps/Network/Client.cpp | 40 ++-- source/ps/Network/Client.h | 3 +- source/ps/Network/NetMessage.h | 2 +- source/ps/Network/Server.cpp | 303 ++++++------------------- source/ps/Network/Server.h | 85 ++----- source/ps/Network/ServerSession.cpp | 214 +++++++++++++++++ source/ps/Network/ServerSession.h | 56 +++++ source/ps/Network/SocketBase.cpp | 2 + source/ps/Player.cpp | 19 +- source/ps/Player.h | 27 ++- source/ps/World.cpp | 2 +- source/ps/XMLWriter.cpp | 31 ++- source/ps/XMLWriter.h | 2 +- source/scripting/ScriptCustomTypes.cpp | 5 +- source/scripting/ScriptGlue.cpp | 76 +------ source/scripting/ScriptGlue.h | 3 +- source/scripting/ScriptingHost.cpp | 2 +- source/scripting/SynchedJSObject.cpp | 78 +++++++ source/scripting/SynchedJSObject.h | 138 +++++++++++ source/simulation/Entity.cpp | 2 +- 24 files changed, 803 insertions(+), 522 deletions(-) create mode 100644 source/ps/Network/ServerSession.cpp create mode 100644 source/ps/Network/ServerSession.h create mode 100644 source/scripting/SynchedJSObject.cpp create mode 100644 source/scripting/SynchedJSObject.h diff --git a/source/main.cpp b/source/main.cpp index fda1e4a3eb..93e3fc872e 100755 --- a/source/main.cpp +++ b/source/main.cpp @@ -121,7 +121,6 @@ static bool g_EntGraph = false; static float g_Gamma = 1.0f; -CGameAttributes g_GameAttributes; extern int game_view_handler(const SDL_Event* ev); static CMusicPlayer MusicPlayer; @@ -568,12 +567,6 @@ static void Render() oglCheck(); } -static void InitDefaultGameAttributes() -{ - g_GameAttributes.SetValue( "mapFile", L"combattest.pmp" ); -} - - static void LoadProfile( CStr profile ) { CStr filename = CStr( "profiles/" ) + profile + CStr( ".cfg" ); @@ -658,10 +651,6 @@ static void ParseArgs(int argc, char* argv[]) if(strncmp(name, "listfiles", 9) == 0) vfs_enable_file_listing(true); break; - case 'm': - if(strncmp(name, "m=", 2) == 0) - g_GameAttributes.SetValue( "mapFile", CStr( argv[i] + 3 ) ); - break; case 'n': if(strncmp(name, "novbo", 5) == 0) g_ConfigDB.CreateValue(CFG_COMMAND, "novbo")->m_String="true"; @@ -734,8 +723,6 @@ TIMER(InitScripting) JSI_Camera::init(); JSI_Console::init(); - - CNetClient::ScriptingInit(); } @@ -873,8 +860,6 @@ TIMER(InitConfig) g_ConfigDB.SetConfigFile(CFG_MOD, true, "config/mod.cfg"); // No point in reloading mod.cfg here - we haven't mounted mods yet - // We init the defaults here; command line options might want to override - InitDefaultGameAttributes(); ParseArgs(argc, argv); LoadGlobals(); // Collects information from system.cfg, the profile file, and any command-line overrides @@ -935,9 +920,6 @@ static void Shutdown() { psShutdown(); // Must delete g_GUI before g_ScriptingHost - // Release script references to the globals before ScriptingHost shuts down - // g_GameAttributes.ReleaseScriptObject(); - if (g_Game) delete g_Game; @@ -952,6 +934,8 @@ static void Shutdown() // Managed by CWorld // delete &g_EntityManager; + delete &g_GameAttributes; + delete &g_EntityTemplateCollection; delete &g_ScriptingHost; @@ -1130,9 +1114,10 @@ TIMER(init_after_InitRenderer); new CSessionManager; + new CGameAttributes; + // Register a few Game/Network JS globals - g_ScriptingHost.SetGlobal("g_NetServerAttributes", OBJECT_TO_JSVAL(g_NetServerAttributes.GetJSObject())); - g_ScriptingHost.SetGlobal("g_GameAttributes", OBJECT_TO_JSVAL(g_GameAttributes.GetJSObject())); + g_ScriptingHost.SetGlobal("g_GameAttributes", OBJECT_TO_JSVAL(g_GameAttributes.GetScript())); // Check for heap corruption after every allocation. Very, very slowly. // (And it highlights the allocation just after the one you care about, diff --git a/source/ps/Game.cpp b/source/ps/Game.cpp index aca15a593f..3103985d2f 100755 --- a/source/ps/Game.cpp +++ b/source/ps/Game.cpp @@ -16,20 +16,22 @@ namespace PlayerArray_JS if (!JSVAL_IS_INT(id)) return JS_FALSE; uint index=g_ScriptingHost.ValueToInt(id); - uint numPlayers=pInstance->GetValue("numPlayers").ToUInt(); + uint numPlayers=pInstance->m_NumPlayers; - if (numPlayers > pInstance->m_PlayerAttribs.size()) + if (numPlayers+1 > pInstance->m_Players.size()) { - for (size_t i=pInstance->m_PlayerAttribs.size();i<=index;i++) + for (size_t i=pInstance->m_Players.size();i<=index;i++) { - pInstance->m_PlayerAttribs.push_back(new CGameAttributes::CPlayerAttributes()); + CPlayer *pNewPlayer=new CPlayer(i); + pNewPlayer->SetUpdateCallback(pInstance->m_PlayerUpdateCB, pInstance->m_PlayerUpdateCBData); + pInstance->m_Players.push_back(pNewPlayer); } } if (index > numPlayers) return JS_FALSE; - *vp=OBJECT_TO_JSVAL(pInstance->m_PlayerAttribs[index]->GetJSObject()); + *vp=OBJECT_TO_JSVAL(pInstance->m_Players[index]->GetScript()); return JS_TRUE; } @@ -57,57 +59,82 @@ namespace PlayerArray_JS } }; -namespace PlayerAttribs_JS -{ - void Finalize(JSContext *cx, JSObject *obj) - { - CAttributeMap *pAttribMap=(CAttributeMap *)JS_GetPrivate(cx, obj); - delete pAttribMap; - } - - JSClass Class = { - "PlayerAttributes", JSCLASS_HAS_PRIVATE, - JS_PropertyStub, JS_PropertyStub, - AttributeMap_JS::GetProperty, AttributeMap_JS::SetProperty, - JS_EnumerateStub, JS_ResolveStub, - JS_ConvertStub, Finalize - }; - -}; - -void CGameAttributes::CPlayerAttributes::CreateJSObject() +CGameAttributes::CGameAttributes(): + m_UpdateCB(NULL), + m_MapFile("test01.pmp"), + m_NumPlayers(2) { ONCE( - g_ScriptingHost.DefineCustomObjectType(&PlayerAttribs_JS::Class, AttributeMap_JS::Construct, 0, NULL, NULL, NULL, NULL); + g_ScriptingHost.DefineCustomObjectType(&PlayerArray_JS::Class, + PlayerArray_JS::Construct, 0, NULL, NULL, NULL, NULL); + + ScriptingInit("GameAttributes"); ); - m_JSObject=g_ScriptingHost.CreateCustomObject("PlayerAttributes"); - JS_SetPrivate(g_ScriptingHost.getContext(), m_JSObject, (CAttributeMap *)this); - CAttributeMap::CreateJSObject(); -} -CGameAttributes::CPlayerAttributes::CPlayerAttributes() -{ - AddValue("name", L"Player Default Name"); -} + m_PlayerArrayJS=g_ScriptingHost.CreateCustomObject("PlayerArray"); + JS_SetPrivate(g_ScriptingHost.GetContext(), m_PlayerArrayJS, this); -CGameAttributes::CGameAttributes(): - m_PlayerArrayJS(NULL) -{ - AddValue("mapFile", L"test01.pmp"); - AddValue("numPlayers", L"2"); + AddSynchedProperty(L"mapFile", &m_MapFile); + AddSynchedProperty(L"numPlayers", &m_NumPlayers); + + AddProperty(L"players", (GetFn)&CGameAttributes::JSGetPlayers); + + m_Players.resize(3); + for (int i=0;i<3;i++) + m_Players[i]=new CPlayer(i); + + m_Players[0]->SetName(L"Gaia"); + m_Players[0]->SetColour(SPlayerColour(0.2f, 0.7f, 0.2f)); + + m_Players[1]->SetName(L"Acumen"); + m_Players[1]->SetColour(SPlayerColour(1.0f, 0.0f, 0.0f)); + + m_Players[2]->SetName(L"Boco the Insignificant"); + m_Players[2]->SetColour(SPlayerColour(0.0f, 0.0f, 1.0f)); } CGameAttributes::~CGameAttributes() { - std::vector::iterator it=m_PlayerAttribs.begin(); - while (it != m_PlayerAttribs.end()) + std::vector::iterator it=m_Players.begin(); + while (it != m_Players.end()) { delete *it; ++it; } } -void CGameAttributes::CreateJSObject() +jsval CGameAttributes::JSGetPlayers() +{ + return OBJECT_TO_JSVAL(m_PlayerArrayJS); +} + +void CGameAttributes::SetValue(CStrW name, CStrW value) +{ + ISynchedJSProperty *prop=GetSynchedProperty(name); + if (prop) + { + prop->FromString(value); + } +} + +void CGameAttributes::Update(CStrW name, ISynchedJSProperty *attrib) +{ + if (m_UpdateCB) + m_UpdateCB(name, attrib->ToString(), m_UpdateCBData); +} + +void CGameAttributes::SetPlayerUpdateCallback(CPlayer::UpdateCallback *cb, void *userdata) +{ + m_PlayerUpdateCB=cb; + m_PlayerUpdateCBData=userdata; + + for (int i=0;iSetUpdateCallback(cb, userdata); + } +} + +/*void CGameAttributes::CreateJSObject() { CAttributeMap::CreateJSObject(); @@ -129,7 +156,7 @@ JSBool CGameAttributes::GetJSProperty(jsval id, jsval *ret) if (name == CStr("players")) return JS_TRUE; return CAttributeMap::GetJSProperty(id, ret); -} +}*/ // Disable "warning C4355: 'this' : used in base member initializer list". // "The base-class constructors and class member constructors are called before @@ -172,7 +199,8 @@ PSRETURN CGame::StartGame(CGameAttributes *pAttribs) for (size_t i=0; iGetValue("numPlayers").ToUInt(); + //m_NumPlayers=pAttribs->GetValue("numPlayers").ToUInt(); + m_NumPlayers=pAttribs->m_NumPlayers; // Note: If m_Players is resized after this point (causing a reallocation) // various bits of code will still contain pointers to data at the original @@ -183,32 +211,8 @@ PSRETURN CGame::StartGame(CGameAttributes *pAttribs) m_Players.resize(m_NumPlayers + 1); for (uint i=0;i <= m_NumPlayers;i++) - m_Players[i]=new CPlayer(i); + m_Players[i]=pAttribs->m_Players[i]; - // FIXME If the GUI hasn't set attributes for all players, the CPlayer - // object's state will be whatever is set by the CPlayer constructor. - for (uint i=0;im_PlayerAttribs.size();i++) - { - CGameAttributes::CPlayerAttributes *pPlayerAttribs= - pAttribs->m_PlayerAttribs[i]; - // TODO Set player attributes in the player object - } - - m_Players[0]->m_Name = L"Gaia"; - m_Players[0]->m_Colour.r = 0.2f; - m_Players[0]->m_Colour.g = 0.7f; - m_Players[0]->m_Colour.b = 0.2f; - - m_Players[1]->m_Name = L"Acumen"; - m_Players[1]->m_Colour.r = 1.0f; - m_Players[1]->m_Colour.g = 0.0f; - m_Players[1]->m_Colour.b = 0.0f; - - m_Players[2]->m_Name = L"Boco the Insignificant"; - m_Players[2]->m_Colour.r = 0.0f; - m_Players[2]->m_Colour.g = 0.0f; - m_Players[2]->m_Colour.b = 1.0f; - m_pLocalPlayer=m_Players[1]; // RC, 040804 - GameView needs to be initialised before World, otherwise GameView initialisation diff --git a/source/ps/Game.h b/source/ps/Game.h index cd4ae7d86a..6addc40da2 100755 --- a/source/ps/Game.h +++ b/source/ps/Game.h @@ -11,31 +11,55 @@ ERROR_GROUP(Game); #include "Player.h" #include "GameView.h" -#include "AttributeMap.h" +#include "scripting/SynchedJSObject.h" #include -class CGameAttributes: public CAttributeMap +namespace PlayerArray_JS { -protected: - virtual void CreateJSObject(); - virtual JSBool GetJSProperty(jsval id, jsval *ret); + JSBool GetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp ); +}; + +#define g_GameAttributes CGameAttributes::GetSingleton() +class CGameAttributes: + public CSynchedJSObject, + public Singleton +{ +public: + typedef void (UpdateCallback)(CStrW name, CStrW newValue, void *data); + +private: + friend JSBool PlayerArray_JS::GetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp ); + + virtual void Update(CStrW name, ISynchedJSProperty *attrib); + + UpdateCallback *m_UpdateCB; + void *m_UpdateCBData; + + CPlayer::UpdateCallback *m_PlayerUpdateCB; + void *m_PlayerUpdateCBData; + + jsval JSGetPlayers(); public: + CStrW m_MapFile; + uint m_NumPlayers; + CGameAttributes(); virtual ~CGameAttributes(); - - // NOTE: Public only for JS interface - class CPlayerAttributes: public CAttributeMap + + void SetValue(CStrW name, CStrW value); + + inline void SetUpdateCallback(UpdateCallback *cb, void *userdata) { - protected: - virtual void CreateJSObject(); - public: - CPlayerAttributes(); - }; - + m_UpdateCB=cb; + m_UpdateCBData=userdata; + } + + void SetPlayerUpdateCallback(CPlayer::UpdateCallback *cb, void *userdata); + + std::vector m_Players; JSObject *m_PlayerArrayJS; - std::vector m_PlayerAttribs; }; class CGame @@ -73,7 +97,15 @@ public: { m_pLocalPlayer=pLocalPlayer; } inline CPlayer *GetPlayer(uint idx) - { if (idx >= 0 && idx < m_NumPlayers) return m_Players[idx]; else { debug_warn("Invalid player ID"); return m_Players[0]; } } + { + if (idx >= 0 && idx < m_NumPlayers) + return m_Players[idx]; + else + { + debug_warn("Invalid player ID"); + return m_Players[0]; + } + } inline std::vector* GetPlayers() { return( &m_Players ); } diff --git a/source/ps/Network/AllNetMessages.h b/source/ps/Network/AllNetMessages.h index 09de415fba..57b5bef3c0 100755 --- a/source/ps/Network/AllNetMessages.h +++ b/source/ps/Network/AllNetMessages.h @@ -133,7 +133,7 @@ END_NMT_CLASS() START_NMT_CLASS_(Authenticate) NMT_FIELD(CStrW, m_Name) //NMT_FIELD(CPasswordHash, m_Password) - NMT_FIELD(CStr, m_Password) + NMT_FIELD(CStrW, m_Password) END_NMT_CLASS() START_NMT_CLASS_(ChatMessage) @@ -144,7 +144,7 @@ END_NMT_CLASS() START_NMT_CLASS_(SetGameConfig) NMT_START_ARRAY(m_Values) - NMT_FIELD(CStr, m_Name) + NMT_FIELD(CStrW, m_Name) NMT_FIELD(CStrW, m_Value) NMT_END_ARRAY() END_NMT_CLASS() @@ -152,7 +152,7 @@ END_NMT_CLASS() START_NMT_CLASS_(SetPlayerConfig) NMT_FIELD_INT(m_PlayerID, u32, 2) NMT_START_ARRAY(m_Values) - NMT_FIELD(CStr, m_Name) + NMT_FIELD(CStrW, m_Name) NMT_FIELD(CStrW, m_Value) NMT_END_ARRAY() END_NMT_CLASS() diff --git a/source/ps/Network/Client.cpp b/source/ps/Network/Client.cpp index e7107b4aee..c1ed5567d8 100755 --- a/source/ps/Network/Client.cpp +++ b/source/ps/Network/Client.cpp @@ -1,5 +1,7 @@ #include "precompiled.h" +#include "lib.h" + #include #include #include @@ -18,7 +20,7 @@ enum CClientEvents { CLIENT_EVENT_START_GAME, CLIENT_EVENT_CHAT, - CLIENT_EVENT_CONNECT, + CLIENT_EVENT_CONNECT_COMPLETE, CLIENT_EVENT_LAST }; @@ -45,13 +47,13 @@ public: } }; -class CConnectEvent: public CScriptEvent +class CConnectCompleteEvent: public CScriptEvent { CStrW m_Message; bool m_Success; public: - CConnectEvent(CStrW message, bool success): - CScriptEvent(L"connect", false, CLIENT_EVENT_CONNECT), + CConnectCompleteEvent(CStrW message, bool success): + CScriptEvent(L"connectComplete", false, CLIENT_EVENT_CONNECT_COMPLETE), m_Message(message), m_Success(success) { @@ -66,11 +68,20 @@ CNetClient::CNetClient(CGame *pGame, CGameAttributes *pGameAttribs): m_pGame(pGame), m_pGameAttributes(pGameAttribs) { + ONCE( + // This one's funny: if you remove the parantheses around this stmt + // the preprocessor will take the comma inside the template decl and + // interpret it as a macro argument delimiter ;-) + (AddMethod("beginConnect", 1)); + + CJSObject::ScriptingInit("NetClient"); + ); + m_pGame->GetSimulation()->SetTurnManager(this); AddProperty(L"onStartGame", &m_OnStartGame); AddProperty(L"onChat", &m_OnChat); - AddProperty(L"onConnect", &m_OnConnect); + AddProperty(L"onConnectComplete", &m_OnConnectComplete); AddProperty(L"password", &m_Password); AddProperty(L"playerName", &m_Name); @@ -83,13 +94,6 @@ CNetClient::~CNetClient() g_ScriptingHost.SetGlobal("g_NetClient", JSVAL_NULL); } -void CNetClient::ScriptingInit() -{ - AddMethod("beginConnect", 1); - - CJSObject::ScriptingInit("NetClient"); -} - bool CNetClient::JSI_BeginConnect(JSContext *cx, uintN argc, jsval *argv) { CStr connectHostName; @@ -143,20 +147,20 @@ bool CNetClient::ConnectHandler(CNetMessage *pMsg, CNetSession *pSession) { case NMT_CONNECT_COMPLETE: pClient->m_pMessageHandler=HandshakeHandler; - if (pClient->m_OnConnect.Defined()) + if (pClient->m_OnConnectComplete.Defined()) { - CConnectEvent evt=CConnectEvent(CStr(PS_OK), true); - pClient->m_OnConnect.DispatchEvent(pClient->GetScript(), &evt); + CConnectCompleteEvent evt=CConnectCompleteEvent(CStr(PS_OK), true); + pClient->m_OnConnectComplete.DispatchEvent(pClient->GetScript(), &evt); } break; case NMT_ERROR: { CNetErrorMessage *msg=(CNetErrorMessage *)pMsg; LOG(ERROR, LOG_CAT_NET, "CNetClient::ConnectHandler(): Connect Failed: %s", msg->m_Error); - if (pClient->m_OnConnect.Defined()) + if (pClient->m_OnConnectComplete.Defined()) { - CConnectEvent evt=CConnectEvent(CStr(msg->m_Error), false); - pClient->m_OnConnect.DispatchEvent(pClient->GetScript(), &evt); + CConnectCompleteEvent evt=CConnectCompleteEvent(CStr(msg->m_Error), false); + pClient->m_OnConnectComplete.DispatchEvent(pClient->GetScript(), &evt); } break; } diff --git a/source/ps/Network/Client.h b/source/ps/Network/Client.h index 35cd4b190e..f63c3e3658 100755 --- a/source/ps/Network/Client.h +++ b/source/ps/Network/Client.h @@ -21,7 +21,7 @@ class CNetClient: public CNetSession, protected CTurnManager, public CJSObjectRemoveSession(this); -} - -void CNetServerSession::StartGame() -{ - if (m_pMessageHandler==PreGameHandler) - m_pMessageHandler=InGameHandler; -} - -#define UNHANDLED(_pMsg) return false; -#define HANDLED(_pMsg) delete _pMsg; return true; -#define TAKEN(_pMsg) return true; - -bool CNetServerSession::BaseHandler(CNetMessage *pMsg, CNetSession *pNetSession) -{ - CNetServerSession *pSession=(CNetServerSession *)pNetSession; - switch (pMsg->GetType()) - { - case NMT_ERROR: - { - CNetErrorMessage *msg=(CNetErrorMessage *)pMsg; - LOG(WARNING, LOG_CAT_NET, "CNetServerSession::BaseHandler(): NMT_ERROR: %s", msg->GetString().c_str()); - if (msg->m_State == SS_UNCONNECTED) - { - /* We were disconnected... What happens with our session? - * - * Note that deleting a session also removes it from m_Sessions - * in CNetServer. If the session is an observer it is also - * removed from m_Observers. - * - * Sessions that are observers or chatters should perhaps - * generate an exit message that is sent to all other sessions. - * - * Player sessions require more care. In Pre-Game, each player - * session has an associated CPlayer object and player session - * slot requiring special care when deleting. - */ - if (!pSession->m_pPlayer) - { - delete pSession; - } - else - { - LOG(ERROR, LOG_CAT_NET, "CNetServerSession::BaseHandler(): Player disconnection not implemented!!"); - } - } - HANDLED(pMsg); - } - } - UNHANDLED(pMsg); -} - -bool CNetServerSession::HandshakeHandler(CNetMessage *pMsg, CNetSession *pNetSession) -{ - CNetServerSession *pSession=(CNetServerSession *)pNetSession; - LOG(NORMAL, LOG_CAT_NET, "CNetServerSession::HandshakeHandler(): %s.", pMsg->GetString().c_str()); - switch (pMsg->GetType()) - { - case NMT_ClientHandshake: - { - CClientHandshake * /*SmartPointer*/ msg=(CClientHandshake *)pMsg/*.GetRawPointer()*/; - - if (msg->m_ProtocolVersion != PS_PROTOCOL_VERSION) - do {} while(0); // This will never happen to us here, but anyways ;-) - - CServerHandshakeResponse *retmsg=new CServerHandshakeResponse(); - retmsg->m_UseProtocolVersion=PS_PROTOCOL_VERSION; - retmsg->m_Flags=0; - retmsg->m_Message=pSession->m_pServer->GetAttributes()->GetValue("welcomeMessage"); - pSession->Push(retmsg); - - pSession->m_pMessageHandler=AuthenticateHandler; - - HANDLED(pMsg); - } - } - return BaseHandler(pMsg, pNetSession); -} - -bool CNetServerSession::AuthenticateHandler(CNetMessage *pMsg, CNetSession *pNetSession) -{ - CNetServerSession *pSession=(CNetServerSession *)pNetSession; - CNetServer *pServer=pSession->m_pServer; - LOG(NORMAL, LOG_CAT_NET, "CNetServerSession::AuthenticateHandler(): %s.", pMsg->GetString().c_str()); - if (pMsg->GetType() == NMT_Authenticate) - { - CAuthenticate *msg=(CAuthenticate *)pMsg; - - if (msg->m_Password == pSession->m_pServer->m_Password) - { - LOG(NORMAL, LOG_CAT_NET, "CNetServerSession::AuthenticateHandler(): Login Successful"); - pSession->m_Name=msg->m_Name; - - pSession->m_pServer->m_Sessions.push_back(pSession); - - CResult *msg=new CResult(); - msg->m_Code=NRC_OK; - msg->m_Message=L"Logged in"; - pSession->Push(msg); - if (pServer->GetServerState() == NSS_PreGame) - { - // We're in pre-game. The connected client becomes a Player. - // Find the first free player slot and assign the player to the - // client. - // If no free player slots could be found - demote to chatter/ - // observer. - pSession->m_pMessageHandler=PreGameHandler; - if (!pServer->AddNewPlayer(pSession)) - pSession->m_pMessageHandler=ObserverHandler; - } - else // We're not in pre-game. The session becomes a chatter/observer here. - { - pSession->m_pMessageHandler=ObserverHandler; - } - } - else - { - LOG(WARNING, LOG_CAT_NET, "CNetServerSession::AuthenticateHandler(): Login Failed"); - CResult *msg=new CResult(); - msg->m_Code=NRC_PasswordInvalid; - msg->m_Message=L"Invalid Password"; - pSession->Push(msg); - } - - HANDLED(pMsg); - } - return BaseHandler(pMsg, pNetSession); -} - -bool CNetServerSession::PreGameHandler(CNetMessage *pMsg, CNetSession *pNetSession) -{ - CNetServerSession *pSession=(CNetServerSession *)pNetSession; - LOG(NORMAL, LOG_CAT_NET, "CNetServerSession::PreGameHandler(): %s.", pMsg->GetString().c_str()); - - return ChatHandler(pMsg, pNetSession); -} - -bool CNetServerSession::ObserverHandler(CNetMessage *pMsg, CNetSession *pNetSession) -{ - printf("CNetServerSession::ObserverHandler(): %s.\n", pMsg->GetString().c_str()); - - // TODO Implement observers and chatter => observer promotion - /* - if (pMsg->GetType() == NMT_RequestObserve) - */ - - return ChatHandler(pMsg, pNetSession); -} - -bool CNetServerSession::ChatHandler(CNetMessage *pMsg, CNetSession *pNetSession) -{ - CNetServerSession *pSession=(CNetServerSession *)pNetSession; - if (pMsg->GetType() == NMT_ChatMessage) - { - CChatMessage *msg=(CChatMessage *)pMsg; - msg->m_Sender=pSession->m_Name; - CStrW wstr=msg->m_Message; - g_Console->ReceivedChatMessage(pSession->GetName().c_str(), wstr.c_str()); - pSession->m_pServer->Broadcast(msg); - - TAKEN(pMsg); - } - - return BaseHandler(pMsg, pNetSession); -} - -bool CNetServerSession::InGameHandler(CNetMessage *pMsg, CNetSession *pNetSession) -{ - CNetServerSession *pSession=(CNetServerSession *)pNetSession; - if (pMsg->GetType() != NMT_EndCommandBatch) - LOG(NORMAL, LOG_CAT_NET, "CNetServerSession::InGameHandler(): %s.", pMsg->GetString().c_str()); - - if (BaseHandler(pMsg, pNetSession)) - return true; - - if (ChatHandler(pMsg, pNetSession)) - return true; - - if (pMsg->GetType() >= NMT_COMMAND_FIRST && pMsg->GetType() <= NMT_COMMAND_LAST) - { - // All Command Messages (i.e. simulation turn synchronized messages) - //pSession->m_pPlayer->ValidateCommand(pMsg); - pSession->m_pServer->QueueIncomingCommand(pMsg); - TAKEN(pMsg); - } - - switch (pMsg->GetType()) - { - case NMT_EndCommandBatch: - // TODO Update client timing information and recalculate turn length - HANDLED(pMsg); - - default: - UNHANDLED(pMsg); - } -} - -// TODO Replace with next generation CNetServer JS Interface -CNetServerAttributes::CNetServerAttributes() -{ - AddValue("serverPlayerName", L"Noname Server Player"); - AddValue("serverName", L"Noname Server"); - AddValue("welcomeMessage", L"Noname Server Welcome Message"); -} - -CNetServer::CNetServer(CNetServerAttributes *pServerAttribs, CGame *pGame, CGameAttributes *pGameAttribs): - m_pServerAttributes(pServerAttribs), +CNetServer::CNetServer(CGame *pGame, CGameAttributes *pGameAttribs): m_pGame(pGame), m_pGameAttributes(pGameAttribs), - m_MaxObservers(5) + m_MaxObservers(5), + m_ServerPlayerName(L"Noname Server Player"), + m_ServerName(L"Noname Server"), + m_WelcomeMessage(L"Noname Server Welcome Message"), + m_Port(-1) { + ONCE( + (AddMethod("open", 0)); + + CJSObject::ScriptingInit("NetServer"); + ); + + AddProperty(L"serverPlayerName", &m_ServerPlayerName); + AddProperty(L"serverName", &m_ServerName); + AddProperty(L"welcomeMessage", &m_WelcomeMessage); + + AddProperty(L"port", &m_Port); + m_pGameAttributes->SetUpdateCallback(AttributeUpdate, this); - m_ServerPlayerName=m_pServerAttributes->GetValue("serverPlayerName"); - m_NumPlayers=m_pGameAttributes->GetValue("numPlayers").ToUInt(); + m_pGameAttributes->SetPlayerUpdateCallback(PlayerAttributeUpdate, this); + m_NumPlayers=m_pGameAttributes->m_NumPlayers; m_pGame->GetSimulation()->SetTurnManager(this); // Set an incredibly long turn length - less command batch spam that way for (int i=0;i<3;i++) CTurnManager::SetTurnLength(i, 3000); + + g_ScriptingHost.SetGlobal("g_NetServer", OBJECT_TO_JSVAL(GetScript())); +} + +bool CNetServer::JSI_Open(JSContext *cx, uintN argc, jsval *argv) +{ + CSocketAddress addr; + if (m_Port == -1) + GetDefaultListenAddress(addr); + else + addr=CSocketAddress(m_Port, /*m_UseIPv6?IPv6:*/IPv4); + + PS_RESULT res=Bind(addr); + if (res != PS_OK) + { + LOG(ERROR, LOG_CAT_NET, "CNetServer::JSI_Open(): Bind error: %s", res); + return false; + } + + return true; } PS_RESULT CNetServer::Bind(const CSocketAddress &address) @@ -271,20 +98,24 @@ PS_RESULT CNetServer::Bind(const CSocketAddress &address) return res; } +void FillSetGameConfigCB(CStrW name, ISynchedJSProperty *prop, void *userdata) +{ + CSetGameConfig *pMsg=(CSetGameConfig *)userdata; + uint size=pMsg->m_Values.size(); + pMsg->m_Values.resize(size+1); + pMsg->m_Values[size].m_Name=name; + pMsg->m_Values[size].m_Value=prop->ToString(); +} + +void CNetServer::FillSetGameConfig(CSetGameConfig *pMsg) +{ + m_pGameAttributes->IterateSynchedProperties(FillSetGameConfigCB, pMsg); +} + bool CNetServer::AddNewPlayer(CNetServerSession *pSession) { - CAttributeMap::MapType &attribs=m_pGameAttributes->GetInternalValueMap(); - CAttributeMap::MapType::iterator it=attribs.begin(); - CSetGameConfig *pMsg=new CSetGameConfig(); - uint n=0; - while (it != attribs.end()) - { - pMsg->m_Values.resize(++n); - pMsg->m_Values.back().m_Name=it->first; - pMsg->m_Values.back().m_Value=it->second; - ++it; - } + FillSetGameConfig(pMsg); pSession->Push(pMsg); if (m_PlayerSessions.size() < m_NumPlayers-1) @@ -294,8 +125,7 @@ bool CNetServer::AddNewPlayer(CNetServerSession *pSession) // Broadcast a message for the newly added player session CPlayerConnect *pMsg=new CPlayerConnect(); pMsg->m_Players.resize(1); - // The player ID is the player session index plus one - pMsg->m_Players[0].m_PlayerID=(u32)m_PlayerSessions.size(); + pMsg->m_Players[0].m_PlayerID=pSession->m_pPlayer->GetPlayerID(); pMsg->m_Players[0].m_Nick=pSession->GetName(); Broadcast(pMsg); @@ -303,7 +133,7 @@ bool CNetServer::AddNewPlayer(CNetServerSession *pSession) // Server Player pMsg->m_Players.resize(1); - pMsg->m_Players.back().m_PlayerID=0; + pMsg->m_Players.back().m_PlayerID=m_pServerPlayer->GetPlayerID(); pMsg->m_Players.back().m_Nick=m_ServerPlayerName; // All the other players @@ -321,11 +151,12 @@ bool CNetServer::AddNewPlayer(CNetServerSession *pSession) return false; } -void CNetServer::AttributeUpdate(CStr name, CStrW newValue, void *userdata) +void CNetServer::AttributeUpdate(CStrW name, CStrW newValue, void *userdata) { CNetServer *pServer=(CNetServer *)userdata; + g_Console->InsertMessage(L"AttributeUpdate: %ls = \"%ls\"", name.c_str(), newValue.c_str()); - if (name == CStr("numPlayers")) + if (name == CStrW(L"numPlayers")) { pServer->m_NumPlayers=newValue.ToUInt(); } @@ -338,6 +169,20 @@ void CNetServer::AttributeUpdate(CStr name, CStrW newValue, void *userdata) pServer->Broadcast(pMsg); } +void CNetServer::PlayerAttributeUpdate(CStrW name, CStrW newValue, CPlayer *pPlayer, void *userdata) +{ + CNetServer *pServer=(CNetServer *)userdata; + g_Console->InsertMessage(L"PlayerAttributeUpdate(%d): %ls = \"%ls\"", pPlayer->GetPlayerID(), name.c_str(), newValue.c_str()); + + CSetPlayerConfig *pMsg=new CSetPlayerConfig; + pMsg->m_PlayerID=pPlayer->GetPlayerID(); + pMsg->m_Values.resize(1); + pMsg->m_Values[0].m_Name=name; + pMsg->m_Values[0].m_Value=newValue; + + pServer->Broadcast(pMsg); +} + bool CNetServer::AllowObserver(CNetServerSession *pSession) { return m_Observers.size() < m_MaxObservers; diff --git a/source/ps/Network/Server.h b/source/ps/Network/Server.h index 8bec25a91f..6d53b275dd 100755 --- a/source/ps/Network/Server.h +++ b/source/ps/Network/Server.h @@ -2,50 +2,10 @@ #define _Network_NetServer_H #include "Network/Session.h" +#include "Network/ServerSession.h" #include "Game.h" #include "TurnManager.h" -class CNetServer; -class CPlayer; - -class CNetServerSession: public CNetSession -{ - CNetServer *m_pServer; - CPlayer *m_pPlayer; - bool m_IsObserver; - -protected: - friend class CNetServer; - - inline void SetPlayer(CPlayer *pPlayer) - { m_pPlayer=pPlayer; } - -public: - virtual ~CNetServerSession(); - - inline CNetServerSession(CNetServer *pServer, CSocketInternal *pInt, MessageHandler *pMsgHandler=HandshakeHandler): - CNetSession(pInt, pMsgHandler), - m_pServer(pServer), - m_pPlayer(NULL), - m_IsObserver(false) - {} - - inline bool IsObserver() - { return m_IsObserver; } - - // Called by server when starting the game, after sending NMT_StartGame to - // all connected clients. - void StartGame(); - - static MessageHandler BaseHandler; - static MessageHandler HandshakeHandler; - static MessageHandler AuthenticateHandler; - static MessageHandler PreGameHandler; - static MessageHandler ObserverHandler; - static MessageHandler ChatHandler; - static MessageHandler InGameHandler; -}; - enum ENetServerState { // We haven't opened the port yet, we're just setting some stuff up. @@ -55,19 +15,17 @@ enum ENetServerState // rules are set up by the operator and where players join and select civs // and stuff. NSS_PreGame, + // In-Game state: the one with all the killing ;-) NSS_InGame, // The game is over and someone has won. Players might linger to chat or // download the replay log. NSS_PostGame }; -class CNetServerAttributes: public CAttributeMap -{ -public: - CNetServerAttributes(); -}; - -class CNetServer: protected CServerSocket, protected CTurnManager +class CNetServer: + protected CServerSocket, + protected CTurnManager, + public CJSObject { private: /* @@ -94,12 +52,20 @@ private: CGame *m_pGame; CGameAttributes *m_pGameAttributes; - CNetServerAttributes *m_pServerAttributes; - CStr m_Password; + CPlayer *m_pServerPlayer; + + CStrW m_Password; CStrW m_ServerPlayerName; - - static CAttributeMap::UpdateCallback AttributeUpdate; + CStrW m_ServerName; + CStrW m_WelcomeMessage; + + int m_Port; + + static CGameAttributes::UpdateCallback AttributeUpdate; + static CPlayer::UpdateCallback PlayerAttributeUpdate; + + void FillSetGameConfig(CSetGameConfig *pMsg); protected: friend class CNetServerSession; @@ -120,14 +86,6 @@ protected: // by the caller. void QueueIncomingCommand(CNetMessage *pMsg); - // Simple accessor. NOTE: Most attributes are read in when creating the - // server object, so changing the attributes should not have any effect on - // the server's operation. Hence, return const-pointer. - inline const CNetServerAttributes *GetAttributes() const - { - return m_pServerAttributes; - } - // OVERRIDES FROM CServerSocket virtual void OnAccept(const CSocketAddress &); @@ -147,7 +105,7 @@ protected: virtual bool AllowObserver(CNetServerSession *pSession); public: - CNetServer(CNetServerAttributes *pServerAttribs, CGame *pGame, CGameAttributes *pGameAttribs); + CNetServer(CGame *pGame, CGameAttributes *pGameAttribs); static void GetDefaultListenAddress(CSocketAddress &address); PS_RESULT Bind(const CSocketAddress &address); @@ -163,11 +121,12 @@ public: int StartGame(); - // Synchronized, safe to call from any thread void Broadcast(CNetMessage *); + + // JS Interface Methods + bool JSI_Open(JSContext *cx, uintN argc, jsval *argv); }; extern CNetServer *g_NetServer; -extern CNetServerAttributes g_NetServerAttributes; #endif // _Network_NetServer_H diff --git a/source/ps/Network/ServerSession.cpp b/source/ps/Network/ServerSession.cpp new file mode 100644 index 0000000000..6f0ed5cd40 --- /dev/null +++ b/source/ps/Network/ServerSession.cpp @@ -0,0 +1,214 @@ +#include "precompiled.h" + +#include "Network/ServerSession.h" +#include "Network/Server.h" +#include "CLogger.h" +#include "CConsole.h" + +extern CConsole *g_Console; + +CNetServerSession::~CNetServerSession() +{ + m_pServer->RemoveSession(this); +} + +void CNetServerSession::StartGame() +{ + if (m_pMessageHandler==PreGameHandler) + m_pMessageHandler=InGameHandler; +} + +#define UNHANDLED(_pMsg) return false; +#define HANDLED(_pMsg) delete _pMsg; return true; +#define TAKEN(_pMsg) return true; + +bool CNetServerSession::BaseHandler(CNetMessage *pMsg, CNetSession *pNetSession) +{ + CNetServerSession *pSession=(CNetServerSession *)pNetSession; + switch (pMsg->GetType()) + { + case NMT_ERROR: + { + CNetErrorMessage *msg=(CNetErrorMessage *)pMsg; + LOG(WARNING, LOG_CAT_NET, "CNetServerSession::BaseHandler(): NMT_ERROR: %s", msg->GetString().c_str()); + if (msg->m_State == SS_UNCONNECTED) + { + /* We were disconnected... What happens with our session? + * + * Note that deleting a session also removes it from m_Sessions + * in CNetServer. If the session is an observer it is also + * removed from m_Observers. + * + * Sessions that are observers or chatters should perhaps + * generate an exit message that is sent to all other sessions. + * + * Player sessions require more care. + * Pre-Game: each player session has an associated CPlayer + * object and player session slot requiring special care when + * deleting. + * In-Game: Revert all player's entities to Gaia control, + * awaiting the client's reconnect attempts [IF we implement it] + * Post-Game: Just do your basic clean-up + */ + if (!pSession->m_pPlayer) + { + delete pSession; + } + else + { + LOG(ERROR, LOG_CAT_NET, "CNetServerSession::BaseHandler(): Player disconnection not implemented!!"); + } + } + HANDLED(pMsg); + } + } + UNHANDLED(pMsg); +} + +bool CNetServerSession::HandshakeHandler(CNetMessage *pMsg, CNetSession *pNetSession) +{ + CNetServerSession *pSession=(CNetServerSession *)pNetSession; + LOG(NORMAL, LOG_CAT_NET, "CNetServerSession::HandshakeHandler(): %s.", pMsg->GetString().c_str()); + switch (pMsg->GetType()) + { + case NMT_ClientHandshake: + { + CClientHandshake *msg=(CClientHandshake *)pMsg; + + if (msg->m_ProtocolVersion != PS_PROTOCOL_VERSION) + { + pSession->Push(new CCloseRequestMessage()); + BaseHandler(new CNetErrorMessage(PS_OK, SS_UNCONNECTED), pSession); + } + + CServerHandshakeResponse *retmsg=new CServerHandshakeResponse(); + retmsg->m_UseProtocolVersion=PS_PROTOCOL_VERSION; + retmsg->m_Flags=0; + retmsg->m_Message=pSession->m_pServer->m_WelcomeMessage; + pSession->Push(retmsg); + + pSession->m_pMessageHandler=AuthenticateHandler; + + HANDLED(pMsg); + } + } + return BaseHandler(pMsg, pNetSession); +} + +bool CNetServerSession::AuthenticateHandler(CNetMessage *pMsg, CNetSession *pNetSession) +{ + CNetServerSession *pSession=(CNetServerSession *)pNetSession; + CNetServer *pServer=pSession->m_pServer; + LOG(NORMAL, LOG_CAT_NET, "CNetServerSession::AuthenticateHandler(): %s.", pMsg->GetString().c_str()); + if (pMsg->GetType() == NMT_Authenticate) + { + CAuthenticate *msg=(CAuthenticate *)pMsg; + + if (msg->m_Password == pSession->m_pServer->m_Password) + { + LOG(NORMAL, LOG_CAT_NET, "CNetServerSession::AuthenticateHandler(): Login Successful"); + pSession->m_Name=msg->m_Name; + + pSession->m_pServer->m_Sessions.push_back(pSession); + + CResult *msg=new CResult(); + msg->m_Code=NRC_OK; + msg->m_Message=L"Logged in"; + pSession->Push(msg); + if (pServer->GetServerState() == NSS_PreGame) + { + // We're in pre-game. The connected client becomes a Player. + // Find the first free player slot and assign the player to the + // client. + // If no free player slots could be found - demote to chatter/ + // observer. + pSession->m_pMessageHandler=PreGameHandler; + if (!pServer->AddNewPlayer(pSession)) + pSession->m_pMessageHandler=ObserverHandler; + } + else // We're not in pre-game. The session becomes a chatter/observer here. + { + pSession->m_pMessageHandler=ObserverHandler; + } + } + else + { + LOG(WARNING, LOG_CAT_NET, "CNetServerSession::AuthenticateHandler(): Login Failed"); + CResult *msg=new CResult(); + msg->m_Code=NRC_PasswordInvalid; + msg->m_Message=L"Invalid Password"; + pSession->Push(msg); + } + + HANDLED(pMsg); + } + return BaseHandler(pMsg, pNetSession); +} + +bool CNetServerSession::PreGameHandler(CNetMessage *pMsg, CNetSession *pNetSession) +{ + CNetServerSession *pSession=(CNetServerSession *)pNetSession; + LOG(NORMAL, LOG_CAT_NET, "CNetServerSession::PreGameHandler(): %s.", pMsg->GetString().c_str()); + + return ChatHandler(pMsg, pNetSession); +} + +bool CNetServerSession::ObserverHandler(CNetMessage *pMsg, CNetSession *pNetSession) +{ + printf("CNetServerSession::ObserverHandler(): %s.\n", pMsg->GetString().c_str()); + + // TODO Implement observers and chatter => observer promotion + /* + if (pMsg->GetType() == NMT_RequestObserve) + */ + + return ChatHandler(pMsg, pNetSession); +} + +bool CNetServerSession::ChatHandler(CNetMessage *pMsg, CNetSession *pNetSession) +{ + CNetServerSession *pSession=(CNetServerSession *)pNetSession; + if (pMsg->GetType() == NMT_ChatMessage) + { + CChatMessage *msg=(CChatMessage *)pMsg; + msg->m_Sender=pSession->m_Name; + CStrW wstr=msg->m_Message; + g_Console->ReceivedChatMessage(pSession->GetName().c_str(), wstr.c_str()); + pSession->m_pServer->Broadcast(msg); + + TAKEN(pMsg); + } + + return BaseHandler(pMsg, pNetSession); +} + +bool CNetServerSession::InGameHandler(CNetMessage *pMsg, CNetSession *pNetSession) +{ + CNetServerSession *pSession=(CNetServerSession *)pNetSession; + if (pMsg->GetType() != NMT_EndCommandBatch) + LOG(NORMAL, LOG_CAT_NET, "CNetServerSession::InGameHandler(): %s.", pMsg->GetString().c_str()); + + if (BaseHandler(pMsg, pNetSession)) + return true; + + if (ChatHandler(pMsg, pNetSession)) + return true; + + if (pMsg->GetType() >= NMT_COMMAND_FIRST && pMsg->GetType() <= NMT_COMMAND_LAST) + { + // All Command Messages (i.e. simulation turn synchronized messages) + //pSession->m_pPlayer->ValidateCommand(pMsg); + pSession->m_pServer->QueueIncomingCommand(pMsg); + TAKEN(pMsg); + } + + switch (pMsg->GetType()) + { + case NMT_EndCommandBatch: + // TODO Update client timing information and recalculate turn length + HANDLED(pMsg); + + default: + UNHANDLED(pMsg); + } +} diff --git a/source/ps/Network/ServerSession.h b/source/ps/Network/ServerSession.h new file mode 100644 index 0000000000..3fc16b77a4 --- /dev/null +++ b/source/ps/Network/ServerSession.h @@ -0,0 +1,56 @@ +/* + CNetServerSession - the server's representation of a connected client + + AUTHOR: Simon Brenner + + DESCRIPTION: + +*/ + +#ifndef _Network_ServerSession_H +#define _Network_ServerSession_H + +#include "Network/Session.h" + +class CNetServer; +class CPlayer; + +class CNetServerSession: public CNetSession +{ + CNetServer *m_pServer; + CPlayer *m_pPlayer; + bool m_IsObserver; + +protected: + friend class CNetServer; + + inline void SetPlayer(CPlayer *pPlayer) + { m_pPlayer=pPlayer; } + +public: + virtual ~CNetServerSession(); + + inline CNetServerSession(CNetServer *pServer, CSocketInternal *pInt, MessageHandler *pMsgHandler=HandshakeHandler): + CNetSession(pInt, pMsgHandler), + m_pServer(pServer), + m_pPlayer(NULL), + m_IsObserver(false) + {} + + inline bool IsObserver() + { return m_IsObserver; } + + // Called by server when starting the game, after sending NMT_StartGame to + // all connected clients. + void StartGame(); + + static MessageHandler BaseHandler; + static MessageHandler HandshakeHandler; + static MessageHandler AuthenticateHandler; + static MessageHandler PreGameHandler; + static MessageHandler ObserverHandler; + static MessageHandler ChatHandler; + static MessageHandler InGameHandler; +}; + +#endif diff --git a/source/ps/Network/SocketBase.cpp b/source/ps/Network/SocketBase.cpp index fd74265463..bccd6e5eb8 100755 --- a/source/ps/Network/SocketBase.cpp +++ b/source/ps/Network/SocketBase.cpp @@ -583,6 +583,7 @@ void CSocketBase::RunWaitLoop() if (res == -1) { perror("CSocketSet::RunWaitLoop(), select"); + pthread_mutex_lock(&g_SocketSetInternal.m_Mutex); continue; } @@ -597,6 +598,7 @@ void CSocketBase::RunWaitLoop() return; else if (bt=='r') { + pthread_mutex_lock(&g_SocketSetInternal.m_Mutex); //printf("Op mask reload after select\n"); continue; } diff --git a/source/ps/Player.cpp b/source/ps/Player.cpp index 4bbf8fde2f..6477b9c00c 100755 --- a/source/ps/Player.cpp +++ b/source/ps/Player.cpp @@ -7,12 +7,21 @@ #include "scripting/JSCollection.h" CPlayer::CPlayer(uint playerID): - m_PlayerID(playerID) + m_PlayerID(playerID), + m_Name(CStrW(L"Player #")+CStrW(playerID)), + m_Colour(0.7f, 0.7f, 0.7f), + m_UpdateCB(0) { AddReadOnlyProperty( L"id", &m_PlayerID ); - AddProperty( L"controlled", (IJSObject::GetFn)GetControlledEntities_JS ); - AddProperty( L"name", &m_Name ); - AddProperty( L"colour", &m_Colour ); + AddProperty( L"controlled", (IJSObject::GetFn)&CPlayer::GetControlledEntities_JS ); + AddSynchedProperty( L"name", &m_Name ); + AddSynchedProperty( L"colour", &m_Colour ); +} + +void CPlayer::Update(CStrW name, ISynchedJSProperty *prop) +{ + if (m_UpdateCB) + m_UpdateCB(name, prop->ToString(), this, m_UpdateCBData); } bool CPlayer::ValidateCommand(CNetMessage *pMsg) @@ -51,4 +60,4 @@ void CPlayer::ScriptingInit() { AddMethod( "toString", 0 ); CJSObject::ScriptingInit( "Player" ); -} \ No newline at end of file +} diff --git a/source/ps/Player.h b/source/ps/Player.h index e7fae72bba..fa04273f6b 100755 --- a/source/ps/Player.h +++ b/source/ps/Player.h @@ -2,6 +2,7 @@ #define _Player_H #include "CStr.h" +#include "scripting/SynchedJSObject.h" #include "scripting/ScriptableObject.h" #include "scripting/ScriptCustomTypes.h" #include "EntityHandles.h" @@ -10,22 +11,44 @@ class CNetMessage; typedef SColour SPlayerColour; -class CPlayer : public CJSObject +class CPlayer : public CSynchedJSObject { - // FIXME: These shouldn't be public (need load-from-attributes method in game.cpp) public: + typedef void (UpdateCallback)(CStrW name, CStrW value, CPlayer *player, void *userdata); +private: CStrW m_Name; uint m_PlayerID; SPlayerColour m_Colour; + UpdateCallback *m_UpdateCB; + void *m_UpdateCBData; + + virtual void Update(CStrW name, ISynchedJSProperty *prop); + public: CPlayer( uint playerID ); bool ValidateCommand(CNetMessage *pMsg); + inline uint GetPlayerID() const + { return m_PlayerID; } + inline const CStrW &GetName() const { return m_Name; } + inline void SetName(const CStrW &name) + { m_Name = name; } + + inline const SPlayerColour &GetColour() const + { return m_Colour; } + inline void SetColour(const SPlayerColour &colour) + { m_Colour = colour; } + + inline void SetUpdateCallback(UpdateCallback *cb, void *userdata) + { + m_UpdateCB=cb; + m_UpdateCBData=userdata; + } // Caller frees... std::vector* GetControlledEntities(); diff --git a/source/ps/World.cpp b/source/ps/World.cpp index a77627be42..2f1578fc66 100755 --- a/source/ps/World.cpp +++ b/source/ps/World.cpp @@ -22,7 +22,7 @@ void CWorld::Initialize(CGameAttributes *pAttribs) CStr mapfilename("maps/scenarios/"); - mapfilename += (CStr)pAttribs->GetValue( "mapFile" ); + mapfilename += (CStr)pAttribs->m_MapFile; try { CMapReader reader; diff --git a/source/ps/XMLWriter.cpp b/source/ps/XMLWriter.cpp index 7d8a08aa96..3a1222ca21 100644 --- a/source/ps/XMLWriter.cpp +++ b/source/ps/XMLWriter.cpp @@ -127,7 +127,7 @@ void XMLWriter_Element::Text(const char* text) -template <> void XMLWriter_File::ElementAttribute(const char* name, CStr& value, bool newelement) +template <> void XMLWriter_File::ElementAttribute(const char* name, const CStr& value, bool newelement) { if (newelement) { @@ -145,35 +145,30 @@ template <> void XMLWriter_File::ElementAttribute(const char* name, CStr& } } -// (Simon) Since GCC refuses to pass temporaries through non-const reference, -// define this wrapper function to convert [ugly] a const CStr to a non-const -template <> -inline void XMLWriter_File::ElementAttribute(const char *name, const CStr& value, bool newelement) -{ - ElementAttribute(name, (CStr &)value, newelement); -} - // Attribute/setting value-to-string template specialisations: // Use CStr's conversion for most types: -#define TYPE(T) \ -template <> void XMLWriter_File::ElementAttribute(const char* name, T& value, bool newelement) \ +#define TYPE2(ID_T, ARG_T) \ +template <> void XMLWriter_File::ElementAttribute(const char* name, ARG_T value, bool newelement) \ { \ - ElementAttribute(name, CStr(value), newelement); \ + ElementAttribute(name, CStr(value), newelement); \ } +#define TYPE(T) TYPE2(T, const T &) TYPE(int) TYPE(unsigned int) TYPE(float) TYPE(double) -TYPE(const char*) +// This is the effect of doing const T& with T=const char* - char const* const& +// Weird - I know ;-) +TYPE2(const char *, char const* const&) -template <> void XMLWriter_File::ElementAttribute(const char* name, CStrW& value, bool newelement) +template <> void XMLWriter_File::ElementAttribute(const char* name, const CStrW& value, bool newelement) { - ElementAttribute(name, value.utf8(), newelement); + ElementAttribute(name, value.utf8(), newelement); } -template <> void XMLWriter_File::ElementAttribute(const char* name, CPlayer*& value, bool newelement) +template <> void XMLWriter_File::ElementAttribute(const char* name, CPlayer*const & value, bool newelement) { - ElementAttribute(name, value->m_PlayerID, newelement); -} \ No newline at end of file + ElementAttribute(name, value->GetPlayerID(), newelement); +} diff --git a/source/ps/XMLWriter.h b/source/ps/XMLWriter.h index bae6731370..db8bf9d01e 100644 --- a/source/ps/XMLWriter.h +++ b/source/ps/XMLWriter.h @@ -100,7 +100,7 @@ private: void ElementStart(XMLWriter_Element* element, const char* name); void ElementText(const char* text); - template void ElementAttribute(const char* name, T& value, bool newelement); + template void ElementAttribute(const char* name, const T& value, bool newelement); void ElementClose(); void ElementEnd(const char* name, int type); diff --git a/source/scripting/ScriptCustomTypes.cpp b/source/scripting/ScriptCustomTypes.cpp index b6dd2da406..2a14f9228c 100755 --- a/source/scripting/ScriptCustomTypes.cpp +++ b/source/scripting/ScriptCustomTypes.cpp @@ -64,8 +64,7 @@ jsval SColour::ToString( JSContext* cx, uintN argc, jsval* argv ) buffer[255] = 0; utf16string str16(buffer, buffer+wcslen(buffer)); - - return( STRING_TO_JSVAL( JS_NewUCStringCopyZ( cx, buffer ) ) ); + return( STRING_TO_JSVAL( JS_NewUCStringCopyZ( cx, str16.c_str() ) ) ); } @@ -85,4 +84,4 @@ JSBool SColour::Construct( JSContext* cx, JSObject* obj, unsigned int argc, jsva *rval = OBJECT_TO_JSVAL( col->GetScript() ); return( JS_TRUE ); -} \ No newline at end of file +} diff --git a/source/scripting/ScriptGlue.cpp b/source/scripting/ScriptGlue.cpp index 480173100f..38341bfb91 100755 --- a/source/scripting/ScriptGlue.cpp +++ b/source/scripting/ScriptGlue.cpp @@ -59,9 +59,8 @@ JSFunctionSpec ScriptFunctionTable[] = {"setCursor", setCursor, 1, 0, 0 }, {"startGame", startGame, 0, 0, 0 }, {"endGame", endGame, 0, 0, 0 }, - {"joinGame", joinGame, 0, 0, 0 }, {"createClient", createClient, 0, 0, 0 }, - {"startServer", startServer, 0, 0, 0 }, + {"createServer", createServer, 0, 0, 0 }, {"loadLanguage", loadLanguage, 1, 0, 0 }, {"getLanguageID", getLanguageID, 0, 0, 0 }, {"getFPS", getFPS, 0, 0, 0 }, @@ -325,38 +324,16 @@ JSBool setCursor(JSContext* UNUSEDPARAM(context), JSObject* UNUSEDPARAM(globalOb return JS_TRUE; } -// Some globals from main.cpp -extern void CreateGame(); -extern CGameAttributes g_GameAttributes; - -JSBool startServer(JSContext* cx, JSObject* UNUSEDPARAM(globalObject), unsigned int argc, jsval* argv, jsval* rval) +JSBool createServer(JSContext* cx, JSObject* UNUSEDPARAM(globalObject), unsigned int argc, jsval* argv, jsval* rval) { - CSocketAddress listenAddress; - if (argc == 0) - { - CNetServer::GetDefaultListenAddress(listenAddress); - } - if (argc == 1) - { - LOG(MESSAGE, LOG_CAT_NET, "startServer: Server port is %d\n", g_ScriptingHost.ValueToInt(argv[0])); - listenAddress=CSocketAddress(g_ScriptingHost.ValueToInt(argv[0]), IPv4); - } - g_Game=new CGame(); - g_NetServer=new CNetServer(&g_NetServerAttributes, g_Game, &g_GameAttributes); - PS_RESULT res=g_NetServer->Bind(listenAddress); - if (res != PS_OK) - { - LOG(ERROR, LOG_CAT_NET, "startServer: Bind error: %s", res); - *rval = BOOLEAN_TO_JSVAL(JS_FALSE); - return JS_TRUE; - } - - *rval = BOOLEAN_TO_JSVAL(JS_TRUE); + g_NetServer=new CNetServer(g_Game, &g_GameAttributes); + + *rval=OBJECT_TO_JSVAL(g_NetServer->GetScript()); return JS_TRUE; } -// More from main.cpp +// from main.cpp extern void StartGame(); JSBool createClient(JSContext* cx, JSObject* UNUSEDPARAM(globalObject), unsigned int argc, jsval* argv, jsval* rval) @@ -373,45 +350,8 @@ JSBool createClient(JSContext* cx, JSObject* UNUSEDPARAM(globalObject), unsigned return JS_TRUE; } -JSBool joinGame(JSContext* cx, JSObject* UNUSEDPARAM(globalObject), unsigned int argc, jsval* argv, jsval* rval) -{ - CStrW username; - CStr password; - CStr connectHostName; - int connectPort=PS_DEFAULT_PORT; - if (argc >= 2) // Two args; name, hostname and default port - { - username=g_ScriptingHost.ValueToUCString(argv[0]); - password=""; - connectHostName=g_ScriptingHost.ValueToString(argv[1]); - } - else - return JS_FALSE; - - if (argc == 3) - { - connectPort=g_ScriptingHost.ValueToInt(argv[2]); - } - - if (g_Game) - { - return JS_FALSE; - } - - g_Game=new CGame(); - g_NetClient=new CNetClient(g_Game, &g_GameAttributes); - g_NetClient->SetLoginInfo(username, password); - PS_RESULT res=g_NetClient->BeginConnect(connectHostName.c_str(), connectPort); - if (res != PS_OK) - { - LOG(ERROR, LOG_CAT_NET, "joinGame: BeginConnect error: %s", res); - *rval=BOOLEAN_TO_JSVAL(JS_FALSE); - } - else - *rval=BOOLEAN_TO_JSVAL(JS_TRUE); - return JS_TRUE; -} - +// TODO Replace startGame with create(Game|Server|Client)/game.start() - after +// merging CGame and CGameAttributes JSBool startGame(JSContext* cx, JSObject* UNUSEDPARAM(globalObject), unsigned int argc, jsval* argv, jsval* rval) { if (argc != 0) diff --git a/source/scripting/ScriptGlue.h b/source/scripting/ScriptGlue.h index dd60571895..b1caa3215c 100755 --- a/source/scripting/ScriptGlue.h +++ b/source/scripting/ScriptGlue.h @@ -35,9 +35,8 @@ JSFunc getGlobal; JSFunc setCursor; JSFunc GetGameObject; -JSFunc startServer; +JSFunc createServer; JSFunc createClient; -JSFunc joinGame; JSFunc startGame; JSFunc endGame; diff --git a/source/scripting/ScriptingHost.cpp b/source/scripting/ScriptingHost.cpp index fef21ec8f5..aa33d9d4ec 100755 --- a/source/scripting/ScriptingHost.cpp +++ b/source/scripting/ScriptingHost.cpp @@ -127,7 +127,7 @@ jsval ScriptingHost::ExecuteScript(const CStrW& script, const CStrW& calledFrom, JSBool ok = JS_EvaluateUCScript(m_Context, contextObject ? contextObject : m_GlobalObject, script.utf16().c_str(), (int)script.Length(), asciiName, 0, &rval); - delete( asciiName ); + delete[]( asciiName ); if (!ok) return JSVAL_NULL; diff --git a/source/scripting/SynchedJSObject.cpp b/source/scripting/SynchedJSObject.cpp new file mode 100644 index 0000000000..72830d4acc --- /dev/null +++ b/source/scripting/SynchedJSObject.cpp @@ -0,0 +1,78 @@ +#include "precompiled.h" + +#include "SynchedJSObject.h" +#include "Parser.h" +#include "ScriptCustomTypes.h" + +template <> +CStrW ToNetString(const uint &val) +{ + return CStrW(val); +} + +template <> +void SetFromNetString(uint &val, CStrW string) +{ + val=string.ToUInt(); +} + +template <> +CStrW ToNetString(const CStrW &data) +{ return data; } + +template <> void SetFromNetString(CStrW &data, CStrW string) +{ data=string; } + +template <> +CStrW ToNetString(const SColour &data) +{ + wchar_t buf[256]; + swprintf(buf, 256, L"%f %f %f %f", data.r, data.g, data.b, data.a); + buf[255]=0; + + return CStrW(buf); +} + +template <> +void SetFromNetString(SColour &data, CStrW wstring) +{ + CParser &parser(CParserCache::Get("$value_$value_$value_$value")); + CParserLine line; + + line.ParseString(parser, CStr(wstring)); + + float values[4]; + if (line.GetArgCount() != 4) return; + for (uint i=0; i<4; ++i) + { + if (!line.GetArgFloat(i, values[i])) + { + return; + } + } + + data.r = values[0]; + data.g = values[1]; + data.b = values[2]; + data.a = values[3]; +} + +void CSynchedJSObjectBase::IterateSynchedProperties(IterateCB *cb, void *userdata) +{ + SynchedPropertyIterator it=m_SynchedProperties.begin(); + while (it != m_SynchedProperties.end()) + { + cb(it->first, it->second, userdata); + ++it; + } +} + +ISynchedJSProperty *CSynchedJSObjectBase::GetSynchedProperty(CStrW name) +{ + SynchedPropertyIterator prop=m_SynchedProperties.find(name); + if (prop != m_SynchedProperties.end()) + return prop->second; + else + return NULL; +} + diff --git a/source/scripting/SynchedJSObject.h b/source/scripting/SynchedJSObject.h new file mode 100644 index 0000000000..ee4733e847 --- /dev/null +++ b/source/scripting/SynchedJSObject.h @@ -0,0 +1,138 @@ +/* + CSynchedJSObject + + AUTHOR: Simon Brenner + + DESCRIPTION: + A helper class for CJSObject that enables a callback to be called + whenever an attribute of the class changes and enables all (synched) + properties to be set and retrieved as strings for network sync. + + All string conversions are performed by specific functions that use a + strictly (hrm) defined format - or at least a format that is specific + for the type in question (which is why JSParseString can't be used - + the JS interface's ToString function is also not usable since it often + produces a human-readable format that doesn't parse well and might + change outside the control of the network protocol). + + This replaces CAttributeMap for both player and game attributes. + + USAGE: + First you must create your subclass, make it inherit from + CSynchedJSObject and implement the pure virtual method Update (see + prototype below) + + Then you may use it just like CJSObject (see ScriptableObject.h) - with + one exception: Any property you want to be synchronized (i.e. have the + new property functionality including the update callback) is added using + the AddSynchedProperty method instead: AddSynchedProperty(name, &m_Property) + + The extra arguments that exist in the AddProperty method haven't been + implemented (if you by any chance would need to, just do it ;-) + +*/ + +#ifndef _SynchedJSObject_H +#define _SynchedJSObject_H + +#include "CStr.h" +#include "ScriptableObject.h" + +template +void SetFromNetString(T &data, CStrW string); + +template +CStrW ToNetString(const T &data); + +#define TYPE(type) \ + template <> CStrW ToNetString(const type &data); \ + template <> void SetFromNetString(type &data, CStrW string); + +TYPE(uint) +TYPE(CStrW) + +#undef TYPE + +class ISynchedJSProperty: public IJSProperty +{ +public: + virtual void FromString(CStrW value)=0; + virtual CStrW ToString()=0; +}; + +// non-templated base class +struct CSynchedJSObjectBase +{ + template + class CSynchedJSProperty: public ISynchedJSProperty + { + PropType *m_Data; + CStrW m_Name; + CSynchedJSObjectBase *m_Owner; + + virtual void Set(JSContext *cx, jsval value) + { + if (!ReadOnly) + { + if (ToPrimitive(cx, value, *m_Data)) + m_Owner->Update(m_Name, this); + } + } + virtual jsval Get(JSContext *cx) + { + return ToJSVal(*m_Data); + } + + virtual void ImmediateCopy(IJSProperty *other) + { + *m_Data = *( ((CSynchedJSProperty*)other)->m_Data ); + } + + virtual void FromString(CStrW value) + { + SetFromNetString(*m_Data, value); + } + + virtual CStrW ToString() + { + return ToNetString(*m_Data); + } + + public: + inline CSynchedJSProperty(CStrW name, PropType* native, CSynchedJSObjectBase *owner): + m_Data(native), + m_Name(name), + m_Owner(owner) + { + m_AllowsInheritance = false; + m_Intrinsic = true; + } + }; + + typedef STL_HASH_MAP SynchedPropertyTable; + typedef SynchedPropertyTable::iterator SynchedPropertyIterator; + SynchedPropertyTable m_SynchedProperties; + +protected: + virtual void Update(CStrW name, ISynchedJSProperty *prop)=0; + +public: + ISynchedJSProperty *GetSynchedProperty(CStrW name); + + typedef void (IterateCB)(CStrW name, ISynchedJSProperty *prop, void *userdata); + void IterateSynchedProperties(IterateCB *cb, void *userdata); +}; + +template +class CSynchedJSObject: public CJSObject, public CSynchedJSObjectBase +{ +protected: + template void AddSynchedProperty(CStrW name, T *native) + { + ISynchedJSProperty *prop=new CSynchedJSProperty(name, native, this); + m_Properties[name]=prop; + m_SynchedProperties[name]=prop; + } +}; + +#endif diff --git a/source/simulation/Entity.cpp b/source/simulation/Entity.cpp index 953d531264..20ccf25ce7 100755 --- a/source/simulation/Entity.cpp +++ b/source/simulation/Entity.cpp @@ -521,7 +521,7 @@ void CEntity::renderSelectionOutline( float alpha ) #ifdef SCED // HACK: ScEd doesn't have a g_Game, so we can't use its CPlayers glColor3fv(PlayerColours[ (intptr_t)m_player ]); #else - SColour& col = m_player->m_Colour; + const SPlayerColour& col = m_player->GetColour(); glColor3f( col.r, col.g, col.b ); #endif }