0ad/source/network/Client.cpp

468 lines
11 KiB
C++
Raw Normal View History

#include "precompiled.h"
#include "scripting/DOMEvent.h"
#include "scripting/JSConversions.h"
#include "scripting/ScriptableObject.h"
#include "Client.h"
#include "JSEvents.h"
#include "ps/CStr.h"
#include "ps/CLogger.h"
#include "ps/CConsole.h"
#include "ps/Game.h"
#include "ps/GameAttributes.h"
#include "simulation/Simulation.h"
#define LOG_CAT_NET "net"
CNetClient *g_NetClient=NULL;
extern int fps;
CNetClient::CServerSession::CServerSession(int sessionID, const CStrW& name):
m_SessionID(sessionID),
m_Name(name)
{
ONCE( ScriptingInit(); );
}
void CNetClient::CServerSession::ScriptingInit()
{
AddProperty(L"id", &CNetClient::CServerSession::m_SessionID, true);
AddProperty(L"name", &CNetClient::CServerSession::m_Name, true);
CJSObject<CServerSession>::ScriptingInit("NetClient_ServerSession");
}
CNetClient::CNetClient(CGame *pGame, CGameAttributes *pGameAttribs):
CNetSession(ConnectHandler),
m_JSI_ServerSessions(&m_ServerSessions),
m_pLocalPlayerSlot(NULL),
m_pGame(pGame),
m_pGameAttributes(pGameAttribs)
{
ONCE( ScriptingInit(); );
m_pGame->GetSimulation()->SetTurnManager(this);
g_ScriptingHost.SetGlobal("g_NetClient", OBJECT_TO_JSVAL(GetScript()));
}
CNetClient::~CNetClient()
{
g_ScriptingHost.SetGlobal("g_NetClient", JSVAL_NULL);
SessionMap::iterator it=m_ServerSessions.begin();
while (it != m_ServerSessions.end())
{
delete it->second;
}
// (Isn't this an infinite loop?)
}
void CNetClient::ScriptingInit()
{
AddMethod<bool, &CNetClient::JSI_BeginConnect>("beginConnect", 1);
AddProperty(L"onStartGame", &CNetClient::m_OnStartGame);
AddProperty(L"onChat", &CNetClient::m_OnChat);
AddProperty(L"onConnectComplete", &CNetClient::m_OnConnectComplete);
AddProperty(L"onDisconnect", &CNetClient::m_OnDisconnect);
AddProperty(L"onClientConnect", &CNetClient::m_OnClientConnect);
AddProperty(L"onClientDisconnect", &CNetClient::m_OnClientDisconnect);
AddProperty(L"password", &CNetClient::m_Password);
AddProperty<CStrW>(L"playerName", &CNetClient::m_Name);
AddProperty(L"sessionId", &CNetClient::m_SessionID);
AddProperty(L"sessions", &CNetClient::m_JSI_ServerSessions);
CJSMap<SessionMap>::ScriptingInit("NetClient_SessionMap");
CJSObject<CNetClient>::ScriptingInit("NetClient");
}
bool CNetClient::JSI_BeginConnect(JSContext* UNUSED(cx), uintN argc, jsval *argv)
{
CStr connectHostName;
uint connectPort=PS_DEFAULT_PORT;
if (argc >= 1)
{
connectHostName=g_ScriptingHost.ValueToString(argv[0]);
}
if (argc >= 2)
{
connectPort=ToPrimitive<int>(argv[1]);
}
PS_RESULT res=BeginConnect(connectHostName.c_str(), connectPort);
if (res != PS_OK)
{
LOG(ERROR, LOG_CAT_NET, "CNetClient::JSI_Connect(): BeginConnect error: %s", res);
return false;
}
else
return true;
}
/* TEMPLATE FOR MESSAGE HANDLERS:
bool CNetClient::<X>Handler(CNetMessage *pMsg, CNetSession *pSession)
{
CNetClient *pClient=(CNetClient *)pSession;
switch (pMsg->GetType())
{
case XXX:
break;
default:
UNHANDLED(pMsg);
}
HANDLED(pMsg);
}
*/
#define UNHANDLED(_pMsg) return false;
#define HANDLED(_pMsg) delete _pMsg; return true;
#define TAKEN(_pMsg) return true;
// Uglily assumes the arguments are called pMsg and pSession (which they are
// all through this file)
#define CHAIN(_chainHandler) STMT(if (_chainHandler(pMsg, pSession)) return true;)
bool CNetClient::ConnectHandler(CNetMessage *pMsg, CNetSession *pSession)
{
CNetClient *pClient=(CNetClient *)pSession;
switch (pMsg->GetType())
{
case NMT_CONNECT_COMPLETE:
pClient->m_pMessageHandler=HandshakeHandler;
if (pClient->m_OnConnectComplete.Defined())
{
CConnectCompleteEvent evt(CStr((char *)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_OnConnectComplete.Defined())
{
CConnectCompleteEvent evt(CStr(msg->m_Error), false);
pClient->m_OnConnectComplete.DispatchEvent(pClient->GetScript(), &evt);
}
break;
}
default:
UNHANDLED(pMsg);
}
HANDLED(pMsg);
}
bool CNetClient::BaseHandler(CNetMessage *pMsg, CNetSession *pSession)
{
CNetClient *pClient=(CNetClient *)pSession;
switch (pMsg->GetType())
{
case NMT_ERROR:
{
CNetErrorMessage *msg=(CNetErrorMessage *)pMsg;
if (msg->m_State == SS_UNCONNECTED)
{
CStr message=msg->m_Error;
CDisconnectEvent evt(message);
if (pClient->m_OnDisconnect.Defined())
pClient->m_OnDisconnect.DispatchEvent(pClient->GetScript(), &evt);
}
break;
}
default:
UNHANDLED(pMsg);
}
HANDLED(pMsg);
}
bool CNetClient::HandshakeHandler(CNetMessage *pMsg, CNetSession *pSession)
{
CNetClient *pClient=(CNetClient *)pSession;
CHAIN(BaseHandler);
switch (pMsg->GetType())
{
case NMT_ServerHandshake:
{
CClientHandshake *msg=new CClientHandshake();
msg->m_MagicResponse=PS_PROTOCOL_MAGIC_RESPONSE;
msg->m_ProtocolVersion=PS_PROTOCOL_VERSION;
msg->m_SoftwareVersion=PS_PROTOCOL_VERSION;
pClient->Push(msg);
break;
}
case NMT_ServerHandshakeResponse:
{
CAuthenticate *msg=new CAuthenticate();
msg->m_Name=pClient->m_Name;
msg->m_Password=pClient->m_Password;
pClient->m_pMessageHandler=AuthenticateHandler;
pClient->Push(msg);
break;
}
default:
UNHANDLED(pMsg);
}
HANDLED(pMsg);
}
bool CNetClient::AuthenticateHandler(CNetMessage *pMsg, CNetSession *pSession)
{
CNetClient *pClient=(CNetClient *)pSession;
CHAIN(BaseHandler);
switch (pMsg->GetType())
{
case NMT_AuthenticationResult:
{
CAuthenticationResult *msg=(CAuthenticationResult *)pMsg;
if (msg->m_Code != NRC_OK)
{
LOG(ERROR, LOG_CAT_NET, "CNetClient::AuthenticateHandler(): Authentication failed: %ls", msg->m_Message.c_str());
}
else
{
LOG(NORMAL, LOG_CAT_NET, "CNetClient::AuthenticateHandler(): Authenticated!");
pClient->m_SessionID=msg->m_SessionID;
pClient->m_pMessageHandler=PreGameHandler;
}
break;
}
default:
UNHANDLED(pMsg);
}
HANDLED(pMsg);
}
bool CNetClient::PreGameHandler(CNetMessage *pMsg, CNetSession *pSession)
{
CNetClient *pClient=(CNetClient *)pSession;
CHAIN(BaseHandler);
CHAIN(ChatHandler);
switch (pMsg->GetType())
{
case NMT_StartGame:
{
pClient->OnStartGameMessage();
HANDLED(pMsg);
}
case NMT_ClientConnect:
{
CClientConnect *msg=(CClientConnect *)pMsg;
for (uint i=0;i<msg->m_Clients.size();i++)
{
pClient->OnClientConnect(msg->m_Clients[i].m_SessionID,
msg->m_Clients[i].m_Name);
}
HANDLED(pMsg);
}
case NMT_ClientDisconnect:
{
CClientDisconnect *msg=(CClientDisconnect *)pMsg;
pClient->OnClientDisconnect(msg->m_SessionID);
HANDLED(pMsg);
}
case NMT_SetGameConfig:
{
CSetGameConfig *msg=(CSetGameConfig *)pMsg;
for (uint i=0;i<msg->m_Values.size();i++)
{
pClient->m_pGameAttributes->SetValue(msg->m_Values[i].m_Name, msg->m_Values[i].m_Value);
}
HANDLED(pMsg);
}
case NMT_AssignPlayerSlot:
{
CAssignPlayerSlot *msg=(CAssignPlayerSlot *)pMsg;
// FIXME Validate slot id to prevent us from going boom
CPlayerSlot *pSlot=pClient->m_pGameAttributes->GetSlot(msg->m_SlotID);
if (pSlot == pClient->m_pLocalPlayerSlot)
pClient->m_pLocalPlayerSlot=NULL;
switch (msg->m_Assignment)
{
case PS_ASSIGN_SESSION:
if ((int)msg->m_SessionID == (int)pClient->m_SessionID) // squelch bogus sign mismatch warning
pClient->m_pLocalPlayerSlot=pSlot;
pSlot->AssignToSessionID(msg->m_SessionID);
break;
case PS_ASSIGN_CLOSED:
pSlot->AssignClosed();
break;
case PS_ASSIGN_OPEN:
pSlot->AssignOpen();
break;
default:
LOG(WARNING, LOG_CAT_NET, "CNetClient::PreGameHandler(): Received an invalid slot assignment %s", msg->GetString().c_str());
}
HANDLED(pMsg);
}
case NMT_SetPlayerConfig:
{
CSetPlayerConfig *msg=(CSetPlayerConfig *)pMsg;
// FIXME Check player ID
CPlayer *pPlayer=pClient->m_pGameAttributes->GetPlayer(msg->m_PlayerID);
for (uint i=0;i<msg->m_Values.size();i++)
{
pPlayer->SetValue(msg->m_Values[i].m_Name, msg->m_Values[i].m_Value);
}
HANDLED(pMsg);
}
default:
{
UNHANDLED(pMsg);
}
}
}
bool CNetClient::InGameHandler(CNetMessage *pMsg, CNetSession *pSession)
{
CNetClient *pClient=(CNetClient *)pSession;
ENetMessageType msgType=pMsg->GetType();
CHAIN(BaseHandler);
CHAIN(ChatHandler);
if (msgType >= NMT_COMMAND_FIRST && msgType < NMT_COMMAND_LAST)
{
pClient->QueueMessage(1, pMsg);
TAKEN(pMsg);
}
switch (msgType)
{
case NMT_EndCommandBatch:
{
CEndCommandBatch *msg=(CEndCommandBatch *)pMsg;
pClient->SetTurnLength(1, msg->m_TurnLength);
// FIXME When the command batch has ended, we should start accepting
// commands for the next turn. This will be accomplished by calling
// NewTurn. *BUT* we shouldn't prematurely proceed game simulation
// since this will produce jerky playback (everything expects a sim
// turn to have a certain duration).
// We should make sure that any commands received after this message
// are queued in the next batch (#2 instead of #1). If we're already
// putting everything new in batch 2 - we should fast-forward a bit to
// catch up with the server.
HANDLED(pMsg);
}
default:
UNHANDLED(pMsg);
}
}
bool CNetClient::ChatHandler(CNetMessage *pMsg, CNetSession *pSession)
{
CNetClient *pClient=(CNetClient *)pSession;
switch (pMsg->GetType())
{
case NMT_ChatMessage:
{
CChatMessage *msg=(CChatMessage *)pMsg;
g_Console->ReceivedChatMessage(msg->m_Sender, msg->m_Message);
if (pClient->m_OnChat.Defined())
{
CChatEvent evt(msg->m_Sender, msg->m_Message);
pClient->m_OnChat.DispatchEvent(pClient->GetScript(), &evt);
}
HANDLED(pMsg);
}
default:
UNHANDLED(pMsg);
}
}
void CNetClient::OnClientConnect(int sessionID, const CStrW& name)
{
// Find existing server session, if any, and delete it
SessionMap::iterator it=m_ServerSessions.find(sessionID);
if (it != m_ServerSessions.end())
{
delete it->second;
m_ServerSessions.erase(it);
}
// Insert new serversession
m_ServerSessions[sessionID]=new CServerSession(sessionID, name);
// Call JS Callback
if (m_OnClientConnect.Defined())
{
CClientConnectEvent evt(sessionID, name);
m_OnClientConnect.DispatchEvent(GetScript(), &evt);
}
}
void CNetClient::OnClientDisconnect(int sessionID)
{
SessionMap::iterator it=m_ServerSessions.find(sessionID);
if (it == m_ServerSessions.end())
{
LOG(WARNING, LOG_CAT_NET, "Server said session %d disconnected. I don't know of such a session.");
}
// Call JS Callback
if (m_OnClientConnect.Defined())
{
CClientDisconnectEvent evt(it->second->m_SessionID, it->second->m_Name);
m_OnClientConnect.DispatchEvent(GetScript(), &evt);
}
// Delete server session and remove from map
delete it->second;
m_ServerSessions.erase(it);
}
void CNetClient::OnStartGameMessage()
{
m_pMessageHandler=InGameHandler;
debug_assert( m_OnStartGame.Defined() );
CStartGameEvent evt;
m_OnStartGame.DispatchEvent(GetScript(), &evt);
}
int CNetClient::StartGame()
{
if (m_pGame->StartGame(m_pGameAttributes) != PSRETURN_OK)
{
return -1;
}
else
{
return 0;
}
}
CPlayer* CNetClient::GetLocalPlayer()
{
return m_pLocalPlayerSlot->GetPlayer();
}
void CNetClient::NewTurn()
{
RotateBatches();
ClearBatch(2);
CEndCommandBatch *pMsg=new CEndCommandBatch();
pMsg->m_TurnLength=1000/fps;
Push(pMsg);
}
void CNetClient::QueueLocalCommand(CNetMessage *pMsg)
{
// Don't save these locally, since they'll be bounced by the server anyway
Push(pMsg);
}