1
0
forked from 0ad/0ad

Revamped CNetClient/CNetServer JS Interface and new CGameAttributes

implementation (moving towards a merger of CGameAttribs into CGame).

This was SVN commit r1746.
This commit is contained in:
Simon Brenner 2005-01-19 22:32:25 +00:00
parent b391fb89ef
commit ae38aee3d2
24 changed files with 803 additions and 522 deletions

View File

@ -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,

View File

@ -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<CPlayerAttributes *>::iterator it=m_PlayerAttribs.begin();
while (it != m_PlayerAttribs.end())
std::vector<CPlayer *>::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;i<m_Players.size();i++)
{
m_Players[i]->SetUpdateCallback(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; i<m_Players.size(); ++i)
delete m_Players[i];
m_NumPlayers=pAttribs->GetValue("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;i<m_NumPlayers && i<pAttribs->m_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

View File

@ -11,31 +11,55 @@ ERROR_GROUP(Game);
#include "Player.h"
#include "GameView.h"
#include "AttributeMap.h"
#include "scripting/SynchedJSObject.h"
#include <vector>
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<CGameAttributes>,
public Singleton<CGameAttributes>
{
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 <CPlayer *> m_Players;
JSObject *m_PlayerArrayJS;
std::vector <CPlayerAttributes *> 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<CPlayer*>* GetPlayers()
{ return( &m_Players ); }

View File

@ -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()

View File

@ -1,5 +1,7 @@
#include "precompiled.h"
#include "lib.h"
#include <scripting/DOMEvent.h>
#include <scripting/JSConversions.h>
#include <scripting/ScriptableObject.h>
@ -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<bool, &CNetClient::JSI_BeginConnect>("beginConnect", 1));
CJSObject<CNetClient>::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<bool, &CNetClient::JSI_BeginConnect>("beginConnect", 1);
CJSObject<CNetClient>::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;
}

View File

@ -21,7 +21,7 @@ class CNetClient: public CNetSession, protected CTurnManager, public CJSObject<C
// JS event scripts
CScriptObject m_OnStartGame;
CScriptObject m_OnChat;
CScriptObject m_OnConnect;
CScriptObject m_OnConnectComplete;
protected:
virtual void NewTurn();
@ -46,7 +46,6 @@ public:
static MessageHandler ChatHandler;
static MessageHandler InGameHandler;
static void ScriptingInit();
bool JSI_BeginConnect(JSContext *cx, uintN argc, jsval *argv);
};

View File

@ -67,7 +67,7 @@ public:
typedef CNetMessage * (*NetMessageDeserializer) (const u8 *buffer, uint length);
#include "Entity.h"
#include "EntityHandles.h"
struct SNetMessageDeserializerRegistration
{

View File

@ -15,7 +15,6 @@
extern CConsole *g_Console;
CNetServer *g_NetServer=NULL;
CNetServerAttributes g_NetServerAttributes;
using namespace std;
@ -40,227 +39,55 @@ void CNetServer::OnAccept(const CSocketAddress &addr)
CNetServerSession *pSession=CreateSession(pInt);
}
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. 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<CClientHandshake>*/ 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<bool, &CNetServer::JSI_Open>("open", 0));
CJSObject<CNetServer>::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;

View File

@ -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<CNetServer>
{
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

View File

@ -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);
}
}

View File

@ -0,0 +1,56 @@
/*
CNetServerSession - the server's representation of a connected client
AUTHOR: Simon Brenner <simon.brenner@home.se>
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

View File

@ -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;
}

View File

@ -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<jsval, &CPlayer::ToString>( "toString", 0 );
CJSObject<CPlayer>::ScriptingInit( "Player" );
}
}

View File

@ -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<CPlayer>
class CPlayer : public CSynchedJSObject<CPlayer>
{
// 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<HEntity>* GetControlledEntities();

View File

@ -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;

View File

@ -127,7 +127,7 @@ void XMLWriter_Element::Text(const char* text)
template <> void XMLWriter_File::ElementAttribute<CStr>(const char* name, CStr& value, bool newelement)
template <> void XMLWriter_File::ElementAttribute<CStr>(const char* name, const CStr& value, bool newelement)
{
if (newelement)
{
@ -145,35 +145,30 @@ template <> void XMLWriter_File::ElementAttribute<CStr>(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 CStr>(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<T>(const char* name, T& value, bool newelement) \
#define TYPE2(ID_T, ARG_T) \
template <> void XMLWriter_File::ElementAttribute<ID_T>(const char* name, ARG_T value, bool newelement) \
{ \
ElementAttribute<const CStr>(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<CStrW>(const char* name, CStrW& value, bool newelement)
template <> void XMLWriter_File::ElementAttribute<CStrW>(const char* name, const CStrW& value, bool newelement)
{
ElementAttribute<const CStr>(name, value.utf8(), newelement);
ElementAttribute(name, value.utf8(), newelement);
}
template <> void XMLWriter_File::ElementAttribute<CPlayer*>(const char* name, CPlayer*& value, bool newelement)
template <> void XMLWriter_File::ElementAttribute<CPlayer*>(const char* name, CPlayer*const & value, bool newelement)
{
ElementAttribute(name, value->m_PlayerID, newelement);
}
ElementAttribute(name, value->GetPlayerID(), newelement);
}

View File

@ -100,7 +100,7 @@ private:
void ElementStart(XMLWriter_Element* element, const char* name);
void ElementText(const char* text);
template <typename T> void ElementAttribute(const char* name, T& value, bool newelement);
template <typename T> void ElementAttribute(const char* name, const T& value, bool newelement);
void ElementClose();
void ElementEnd(const char* name, int type);

View File

@ -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 );
}
}

View File

@ -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)

View File

@ -35,9 +35,8 @@ JSFunc getGlobal;
JSFunc setCursor;
JSFunc GetGameObject;
JSFunc startServer;
JSFunc createServer;
JSFunc createClient;
JSFunc joinGame;
JSFunc startGame;
JSFunc endGame;

View File

@ -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;

View File

@ -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;
}

View File

@ -0,0 +1,138 @@
/*
CSynchedJSObject
AUTHOR: Simon Brenner <simon.brenner@home.se>
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 <typename T>
void SetFromNetString(T &data, CStrW string);
template <typename T>
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 <typename PropType, bool ReadOnly = false>
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<PropType, ReadOnly>*)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<CStrW, ISynchedJSProperty *, CStrW_hash_compare> 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 <typename Class>
class CSynchedJSObject: public CJSObject<Class>, public CSynchedJSObjectBase
{
protected:
template <typename T> void AddSynchedProperty(CStrW name, T *native)
{
ISynchedJSProperty *prop=new CSynchedJSProperty<T>(name, native, this);
m_Properties[name]=prop;
m_SynchedProperties[name]=prop;
}
};
#endif

View File

@ -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
}