1
0
forked from 0ad/0ad

Basic disconnection handling.

Pre-game chat.
Fix dynamic updates of focused input controls.
Allow scrollable texts to automatically scroll to the bottom.
Force usernames to be unique.

This was SVN commit r7664.
This commit is contained in:
Ykkrosh 2010-07-02 21:28:48 +00:00
parent 0d58a10604
commit 051aa70940
17 changed files with 382 additions and 64 deletions

View File

@ -11,8 +11,7 @@ var g_IsController;
// (and therefore shouldn't send further messages to the network)
var g_IsInGuiUpdate;
// Default single-player player assignments
var g_PlayerAssignments = { "local": { "name": "You", "player": 1 } };
var g_PlayerAssignments = {};
// Default game setup attributes
var g_GameAttributes = { "map": "Latium" };
@ -20,6 +19,8 @@ var g_GameAttributes = { "map": "Latium" };
// Number of players for currently selected map
var g_MaxPlayers = 8;
var g_ChatMessages = [];
function init(attribs)
{
switch (attribs.type)
@ -49,20 +50,30 @@ function init(attribs)
getGUIObjectByName("playerAssignment["+i+"]").enabled = false;
}
// Set up offline-only bits:
if (!g_IsNetworked)
{
getGUIObjectByName("chatPanel").hidden = true;
g_PlayerAssignments = { "local": { "name": "You", "player": 1 } };
}
updatePlayerList();
}
function cancelSetup()
{
Engine.DisconnectNetworkGame();
}
function onTick()
{
if (g_IsNetworked)
while (true)
{
while (true)
{
var message = Engine.PollNetworkClient();
if (!message)
break;
handleNetMessage(message);
}
var message = Engine.PollNetworkClient();
if (!message)
break;
handleNetMessage(message);
}
}
@ -72,6 +83,20 @@ function handleNetMessage(message)
switch (message.type)
{
case "netstatus":
switch (message.status)
{
case "disconnected":
Engine.DisconnectNetworkGame();
Engine.PopGuiPage();
messageBox(400, 200, "Connection to the server has been lost.", "Disconnected", 2);
break;
default:
error("Unrecognised netstatus type "+message.status);
break;
}
break;
case "gamesetup":
if (message.data) // (the host gets undefined data on first connect, so skip that)
g_GameAttributes = message.data;
@ -80,6 +105,14 @@ function handleNetMessage(message)
break;
case "players":
// Find and report all joinings/leavings
for (var host in message.hosts)
if (! g_PlayerAssignments[host])
addChatMessage({ "type": "connect", "username": message.hosts[host].name });
for (var host in g_PlayerAssignments)
if (! message.hosts[host])
addChatMessage({ "type": "disconnect", "username": g_PlayerAssignments[host].name });
// Update the player list
g_PlayerAssignments = message.hosts;
updatePlayerList();
break;
@ -88,6 +121,10 @@ function handleNetMessage(message)
Engine.PushGuiPage("page_loading.xml", { "attribs": g_GameAttributes });
break;
case "chat":
addChatMessage({ "type": "message", "username": message.username, "text": message.text });
break;
default:
error("Unrecognised net message type "+message.type);
}
@ -257,3 +294,44 @@ function updatePlayerList()
g_IsInGuiUpdate = false;
}
function submitChatInput()
{
var input = getGUIObjectByName("chatInput");
var text = input.caption;
if (text.length)
{
Engine.SendNetworkChat(text);
input.caption = "";
}
}
function addChatMessage(msg)
{
// TODO: we ought to escape all values before displaying them,
// to prevent people inserting colours and newlines etc
var formatted;
switch (msg.type)
{
case "connect":
formatted = '[font="serif-bold-13"][color="255 0 0"]' + msg.username + '[/color][/font] [color="64 64 64"]has joined[/color]';
break;
case "disconnect":
formatted = '[font="serif-bold-13"][color="255 0 0"]' + msg.username + '[/color][/font] [color="64 64 64"]has left[/color]';
break;
case "message":
formatted = '[font="serif-bold-13"]<[color="255 0 0"]' + msg.username + '[/color]>[/font] ' + msg.text;
break;
default:
error("Invalid chat message '" + uneval(msg) + "'");
return;
}
g_ChatMessages.push(formatted);
getGUIObjectByName("chatText").caption = g_ChatMessages.join("\n");
}

View File

@ -2,6 +2,7 @@
<objects>
<script file="gui/common/functions_global_object.js"/>
<script file="gui/gamesetup/gamesetup.js"/>
<!-- Add a translucent black background to fade out the menu page -->
@ -17,10 +18,13 @@
<object type="button" style="wheatExit" tooltip_style="snToolTip">
<action on="Press"><![CDATA[
cancelSetup();
Engine.PopGuiPage();
]]></action>
</object>
<!-- Map selection -->
<object size="0 0 250 100%">
<object name="mapSelection"
@ -40,6 +44,8 @@
</object>
<!-- Player assignments -->
<object size="260 0 100% 100%-200" type="image" sprite="wheatIndentFillLight">
<repeat count="8">
<object name="playerBox[n]" size="0 0 100% 30">
@ -49,13 +55,32 @@
</repeat>
</object>
<!-- Chat window -->
<object name="chatPanel" size="260 100%-190 100% 100%-45" type="image" sprite="wheatIndentFillLight">
<object name="chatText" size="3 1 100%-1 100%-25" type="text" style="chatPanel"/>
<object name="chatInput" size="2 100%-23 100%-66 100%-3" type="input" style="wheatInput">
<action on="Press">submitChatInput();</action>
</object>
<object size="100%-65 100%-25 100%-1 100%" type="button" style="wheatButton">
Send
<action on="Press">submitChatInput();</action>
</object>
</object>
<!-- Other things -->
<object name="onscreenToolTip"
type="text"
font="serif-14"
textcolor="white"
sprite="bkTranslucent"
hidden="true"
size="100%-300 100%-190 100% 100%-45"
size="260 100%-40 100%-150 100%"
>[Tooltip text]</object>
<object

View File

@ -12,4 +12,16 @@
text_valign="center"
/>
<style name="chatPanel"
buffer_zone="5"
font="serif-13"
scrollbar="true"
scrollbar_style="wheatScrollBar"
scroll_bottom="true"
textcolor="black"
textcolor_selected="white"
text_align="left"
text_valign="center"
/>
</styles>

View File

@ -31,26 +31,49 @@ function getHotloadData()
return { selection: g_Selection.selected };
}
function changeNetStatus(status)
function handleNetMessage(message)
{
var obj = getGUIObjectByName("netStatus");
switch (status)
warn("Net message: "+uneval(message));
switch (message.type)
{
case "normal":
obj.caption = "";
obj.hidden = true;
break;
case "waiting_for_connect":
obj.caption = "Waiting for other players to connect";
obj.hidden = false;
case "netstatus":
var obj = getGUIObjectByName("netStatus");
switch (message.status)
{
case "waiting_for_players":
obj.caption = "Waiting for other players to connect";
obj.hidden = false;
break;
case "active":
obj.caption = "";
obj.hidden = true;
break;
case "disconnected":
obj.caption = "Connection to the server has been lost";
obj.hidden = false;
// TODO: we need to give players some way to exit
break;
default:
error("Unrecognised netstatus type "+message.status);
break;
}
break;
default:
error("Unexpected net status "+status);
error("Unrecognised net message type "+message.type);
}
}
function onTick()
{
while (true)
{
var message = Engine.PollNetworkClient();
if (!message)
break;
handleNetMessage(message);
}
g_DevSettings.controlAll = getGUIObjectByName("devControlAll").checked;
// TODO: at some point this controlAll needs to disable the simulation code's
// player checks (once it has some player checks)

View File

@ -131,9 +131,9 @@ InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev)
*pCaption = pCaption->Left( m_iBufferPos-1 ) +
pCaption->Right( (long) pCaption->length()-m_iBufferPos );
UpdateText(m_iBufferPos-1, m_iBufferPos, m_iBufferPos-1);
--m_iBufferPos;
UpdateText(m_iBufferPos, m_iBufferPos+1, m_iBufferPos);
}
UpdateAutoScroll();
@ -413,13 +413,17 @@ InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev)
/* END: Message History Lookup */
case '\r':
// 'Return' should do nothing for singe liners
// 'Return' should do a Press event for single liners (e.g. submitting forms)
// otherwise a '\n' character will be added.
{
bool multiline;
GUI<bool>::GetSetting(this, "multiline", multiline);
if (!multiline)
{
HandleMessage(GUIM_PRESSED);
ScriptEvent("press");
break;
}
cooked = '\n'; // Change to '\n' and do default:
// NOTE: Fall-through
@ -630,7 +634,7 @@ void CInput::HandleMessage(const SGUIMessage &Message)
}
}
void CInput::Draw()
void CInput::Draw()
{
float bz = GetBufferedZ();
@ -1033,6 +1037,10 @@ void CInput::UpdateText(int from, int to_before, int to_after)
GUI<float>::GetSetting(this, "buffer_zone", buffer_zone);
GUI<bool>::GetSetting(this, "multiline", multiline);
// Ensure positions are valid after caption changes
m_iBufferPos = std::min(m_iBufferPos, (int)caption.size());
m_iBufferPos_Tail = std::min(m_iBufferPos_Tail, (int)caption.size());
if (font_name == CStrW())
{
// Destroy everything stored, there's no font, so there can be

View File

@ -39,6 +39,7 @@ CText::CText()
AddSetting(GUIST_CStrW, "font");
AddSetting(GUIST_bool, "scrollbar");
AddSetting(GUIST_CStr, "scrollbar_style");
AddSetting(GUIST_bool, "scroll_bottom");
AddSetting(GUIST_CGUISpriteInstance, "sprite");
AddSetting(GUIST_EAlign, "text_align");
AddSetting(GUIST_EVAlign, "text_valign");
@ -98,8 +99,22 @@ void CText::SetupText()
// Setup scrollbar
if (scrollbar)
{
GetScrollBar(0).SetScrollRange( m_GeneratedTexts[0]->m_Size.cy );
GetScrollBar(0).SetScrollSpace( m_CachedActualSize.GetHeight() );
bool scrollbottom = false;
GUI<bool>::GetSetting(this, "scroll_bottom", scrollbottom);
// If we are currently scrolled to the bottom of the text,
// then add more lines of text, update the scrollbar so we
// stick to the bottom.
// (Use 1.5px delta so this triggers the first time caption is set)
bool bottom = false;
if (scrollbottom && GetScrollBar(0).GetPos() > GetScrollBar(0).GetMaxPos() - 1.5f)
bottom = true;
GetScrollBar(0).SetScrollRange(m_GeneratedTexts[0]->m_Size.cy);
GetScrollBar(0).SetScrollSpace(m_CachedActualSize.GetHeight());
if (bottom)
GetScrollBar(0).SetPos(GetScrollBar(0).GetMaxPos());
}
}

View File

@ -202,6 +202,11 @@ public:
*/
virtual void SetPos(const float &f) { m_Pos = f; UpdatePosBoundaries(); }
/**
* Get the value of Pos that corresponds to the bottom of the scrollable region
*/
float GetMaxPos() const { return m_ScrollRange - m_ScrollSpace; }
/**
* Scroll towards 1.0 one step
*/
@ -436,7 +441,7 @@ protected:
/**
* Position of scroll bar, 0 means scrolled all the way to one side.
* It is meassured in pixels, it is up to the host to make it actually
* It is measured in pixels, it is up to the host to make it actually
* apply in pixels.
*/
float m_Pos;

View File

@ -218,13 +218,21 @@ void StartNetworkJoin(void* UNUSED(cbdata), std::wstring playerName, std::string
g_NetClient->SetupConnection(serverAddress);
}
// TODO: we need some way to disconnect the server/client
void DisconnectNetworkGame(void* UNUSED(cbdata))
{
// TODO: we ought to do async reliable disconnections
SAFE_DELETE(g_NetServer);
SAFE_DELETE(g_NetClient);
SAFE_DELETE(g_Game);
}
CScriptVal PollNetworkClient(void* cbdata)
{
CGUIManager* guiManager = static_cast<CGUIManager*> (cbdata);
debug_assert(g_NetClient);
if (!g_NetClient)
return CScriptVal();
CScriptValRooted poll = g_NetClient->GuiPoll();
@ -239,6 +247,14 @@ void AssignNetworkPlayer(void* UNUSED(cbdata), int playerID, std::string guid)
g_NetServer->AssignPlayer(playerID, guid);
}
void SendNetworkChat(void* UNUSED(cbdata), std::wstring message)
{
debug_assert(g_NetClient);
g_NetClient->SendChatMessage(message);
}
} // namespace
void GuiScriptingInit(ScriptInterface& scriptInterface)
@ -264,9 +280,11 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
scriptInterface.RegisterFunction<void, CScriptVal, int, &StartGame>("StartGame");
scriptInterface.RegisterFunction<void, std::wstring, &StartNetworkHost>("StartNetworkHost");
scriptInterface.RegisterFunction<void, std::wstring, std::string, &StartNetworkJoin>("StartNetworkJoin");
scriptInterface.RegisterFunction<void, &DisconnectNetworkGame>("DisconnectNetworkGame");
scriptInterface.RegisterFunction<CScriptVal, &PollNetworkClient>("PollNetworkClient");
scriptInterface.RegisterFunction<void, CScriptVal, &SetNetworkGameAttributes>("SetNetworkGameAttributes");
scriptInterface.RegisterFunction<void, int, std::string, &AssignNetworkPlayer>("AssignNetworkPlayer");
scriptInterface.RegisterFunction<void, std::wstring, &SendNetworkChat>("SendNetworkChat");
// Misc functions
scriptInterface.RegisterFunction<std::wstring, std::wstring, &SetCursor>("SetCursor");

View File

@ -53,14 +53,17 @@ CNetClient::CNetClient(CGame* game) :
AddTransition(NCS_INITIAL_GAMESETUP, (uint)NMT_GAME_SETUP, NCS_PREGAME, (void*)&OnGameSetup, context);
AddTransition(NCS_PREGAME, (uint)NMT_CHAT, NCS_PREGAME, (void*)&OnChat, context);
AddTransition(NCS_PREGAME, (uint)NMT_GAME_SETUP, NCS_PREGAME, (void*)&OnGameSetup, context);
AddTransition(NCS_PREGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_PREGAME, (void*)&OnPlayerAssignment, context);
AddTransition(NCS_PREGAME, (uint)NMT_GAME_START, NCS_LOADING, (void*)&OnGameStart, context);
AddTransition(NCS_LOADING, (uint)NMT_CHAT, NCS_LOADING, (void*)&OnChat, context);
AddTransition(NCS_LOADING, (uint)NMT_GAME_SETUP, NCS_LOADING, (void*)&OnGameSetup, context);
AddTransition(NCS_LOADING, (uint)NMT_PLAYER_ASSIGNMENT, NCS_LOADING, (void*)&OnPlayerAssignment, context);
AddTransition(NCS_LOADING, (uint)NMT_LOADED_GAME, NCS_INGAME, (void*)&OnLoadedGame, context);
AddTransition(NCS_INGAME, (uint)NMT_CHAT, NCS_INGAME, (void*)&OnChat, context);
AddTransition(NCS_INGAME, (uint)NMT_GAME_SETUP, NCS_INGAME, (void*)&OnGameSetup, context);
AddTransition(NCS_INGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_INGAME, (void*)&OnPlayerAssignment, context);
AddTransition(NCS_INGAME, (uint)NMT_SIMULATION_COMMAND, NCS_INGAME, (void*)&OnInGame, context);
@ -178,7 +181,16 @@ void CNetClient::HandleConnect()
void CNetClient::HandleDisconnect()
{
// TODO: should do something
CScriptValRooted msg;
GetScriptInterface().Eval("({'type':'netstatus','status':'disconnected'})", msg);
PushGuiMessage(msg);
}
void CNetClient::SendChatMessage(const std::wstring& text)
{
CChatMessage chat;
chat.m_Message = text;
SendMessage(&chat);
}
bool CNetClient::HandleMessage(CNetMessage* message)
@ -192,7 +204,9 @@ bool CNetClient::HandleMessage(CNetMessage* message)
void CNetClient::LoadFinished()
{
m_Game->ChangeNetStatus(CGame::NET_WAITING_FOR_CONNECT);
CScriptValRooted msg;
GetScriptInterface().Eval("({'type':'netstatus','status':'waiting_for_players'})", msg);
PushGuiMessage(msg);
CLoadedGameMessage loaded;
SendMessage(&loaded);
@ -260,6 +274,23 @@ bool CNetClient::OnAuthenticate(void* context, CFsmEvent* event)
return true;
}
bool CNetClient::OnChat(void* context, CFsmEvent* event)
{
debug_assert(event->GetType() == (uint)NMT_CHAT);
CNetClient* client = (CNetClient*)context;
CChatMessage* message = (CChatMessage*)event->GetParamRef();
CScriptValRooted msg;
client->GetScriptInterface().Eval("({'type':'chat'})", msg);
client->GetScriptInterface().SetProperty(msg.get(), "username", std::wstring(message->m_Sender), false);
client->GetScriptInterface().SetProperty(msg.get(), "text", std::wstring(message->m_Message), false);
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnGameSetup(void* context, CFsmEvent* event)
{
debug_assert(event->GetType() == (uint)NMT_GAME_SETUP);
@ -287,15 +318,17 @@ bool CNetClient::OnPlayerAssignment(void* context, CFsmEvent* event)
CPlayerAssignmentMessage* message = (CPlayerAssignmentMessage*)event->GetParamRef();
// Unpack the message
client->m_PlayerAssignments.clear();
PlayerAssignmentMap newPlayerAssignments;
for (size_t i = 0; i < message->m_Hosts.size(); ++i)
{
PlayerAssignment assignment;
assignment.m_Name = message->m_Hosts[i].m_Name;
assignment.m_PlayerID = message->m_Hosts[i].m_PlayerID;
client->m_PlayerAssignments[message->m_Hosts[i].m_GUID] = assignment;
newPlayerAssignments[message->m_Hosts[i].m_GUID] = assignment;
}
client->m_PlayerAssignments.swap(newPlayerAssignments);
client->PostPlayerAssignmentsToScript();
return true;
@ -333,7 +366,10 @@ bool CNetClient::OnLoadedGame(void* context, CFsmEvent* event)
// All players have loaded the game - start running the turn manager
// so that the game begins
client->m_Game->SetTurnManager(client->m_ClientTurnManager);
client->m_Game->ChangeNetStatus(CGame::NET_NORMAL);
CScriptValRooted msg;
client->GetScriptInterface().Eval("({'type':'netstatus','status':'active'})", msg);
client->PushGuiMessage(msg);
return true;
}

View File

@ -153,6 +153,8 @@ public:
*/
void LoadFinished();
void SendChatMessage(const std::wstring& text);
private:
// Net message / FSM transition handlers
static bool OnConnect(void* context, CFsmEvent* event);

View File

@ -38,6 +38,7 @@
enum NetMessageType
{
NMT_CONNECT_COMPLETE = -256, // Connection is complete
NMT_CONNECTION_LOST,
NMT_INVALID = 0, // Invalid message
NMT_SERVER_HANDSHAKE, // Handshake stage
NMT_CLIENT_HANDSHAKE,
@ -106,8 +107,7 @@ START_NMT_CLASS_(AuthenticateResult, NMT_AUTHENTICATE_RESULT)
END_NMT_CLASS()
START_NMT_CLASS_(Chat, NMT_CHAT)
NMT_FIELD(CStrW, m_Sender)
NMT_FIELD_INT(m_Recipient, u32, 2)
NMT_FIELD(CStrW, m_Sender) // ignored when client->server, valid when server->client
NMT_FIELD(CStrW, m_Message)
END_NMT_CLASS()

View File

@ -59,6 +59,17 @@ CNetServer::CNetServer() :
CNetServer::~CNetServer()
{
for (size_t i = 0; i < m_Sessions.size(); ++i)
{
m_Sessions[i]->Disconnect();
delete m_Sessions[i];
}
if (m_Host)
{
enet_host_destroy(m_Host);
}
m_GameAttributes = CScriptValRooted(); // clear root before deleting its context
delete m_ScriptInterface;
}
@ -182,6 +193,8 @@ void CNetServer::Poll()
{
LOGMESSAGE(L"Net server: %hs disconnected", DebugName(session).c_str());
session->Update((uint)NMT_CONNECTION_LOST, NULL);
HandleDisconnect(session);
delete session;
@ -251,10 +264,16 @@ void CNetServer::SetupSession(CNetServerSession* session)
void* context = session;
// Set up transitions for session
session->AddTransition(NSS_HANDSHAKE, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED, (void*)&OnDisconnect, context);
session->AddTransition(NSS_HANDSHAKE, (uint)NMT_CLIENT_HANDSHAKE, NSS_AUTHENTICATE, (void*)&OnClientHandshake, context);
session->AddTransition(NSS_AUTHENTICATE, (uint)NMT_AUTHENTICATE, NSS_PREGAME, (void*)&OnAuthenticate, context);
session->AddTransition(NSS_PREGAME, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED, (void*)&OnDisconnect, context);
session->AddTransition(NSS_PREGAME, (uint)NMT_CHAT, NSS_PREGAME, (void*)&OnChat, context);
session->AddTransition(NSS_PREGAME, (uint)NMT_LOADED_GAME, NSS_INGAME, (void*)&OnLoadedGame, context);
session->AddTransition(NSS_INGAME, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED, (void*)&OnDisconnect, context);
session->AddTransition(NSS_INGAME, (uint)NMT_CHAT, NSS_INGAME, (void*)&OnChat, context);
session->AddTransition(NSS_INGAME, (uint)NMT_SIMULATION_COMMAND, NSS_INGAME, (void*)&OnInGame, context);
session->AddTransition(NSS_INGAME, (uint)NMT_SYNC_CHECK, NSS_INGAME, (void*)&OnInGame, context);
@ -298,6 +317,13 @@ void CNetServer::OnUserJoin(CNetServerSession* session)
OnAddPlayer();
}
void CNetServer::OnUserLeave(CNetServerSession* session)
{
RemovePlayer(session->GetGUID());
OnRemovePlayer();
}
void CNetServer::AddPlayer(const CStr& guid, const CStrW& name)
{
// Find the first free player ID
@ -326,6 +352,8 @@ void CNetServer::RemovePlayer(const CStr& guid)
{
m_PlayerAssignments.erase(guid);
SendPlayerAssignments();
OnRemovePlayer();
}
@ -407,7 +435,9 @@ bool CNetServer::OnAuthenticate(void* context, CFsmEvent* event)
u32 newHostID = server.m_NextHostID++;
session->SetUserName(message->m_Name);
CStrW username = server.DeduplicatePlayerName(SanitisePlayerName(message->m_Name));
session->SetUserName(username);
session->SetGUID(message->m_GUID);
session->SetHostID(newHostID);
@ -463,6 +493,7 @@ bool CNetServer::OnChat(void* context, CFsmEvent* event)
CNetServer& server = session->GetServer();
CChatMessage* message = (CChatMessage*)event->GetParamRef();
message->m_Sender = session->GetUserName();
server.Broadcast(message);
@ -482,6 +513,20 @@ bool CNetServer::OnLoadedGame(void* context, CFsmEvent* event)
return true;
}
bool CNetServer::OnDisconnect(void* context, CFsmEvent* event)
{
debug_assert(event->GetType() == (uint)NMT_CONNECTION_LOST);
CNetServerSession* session = (CNetServerSession*)context;
CNetServer& server = session->GetServer();
// If the user had authenticated, we need to handle their leaving
if (session->GetCurrState() == NSS_PREGAME || session->GetCurrState() == NSS_INGAME)
server.OnUserLeave(session);
return true;
}
void CNetServer::CheckGameLoadStatus(CNetServerSession* changedSession)
{
for (size_t i = 0; i < m_Sessions.size(); ++i)
@ -524,3 +569,49 @@ void CNetServer::UpdateGameAttributes(const CScriptValRooted& attrs)
gameSetupMessage.m_Data = m_GameAttributes;
Broadcast(&gameSetupMessage);
}
CStrW CNetServer::SanitisePlayerName(const CStrW& original)
{
const size_t MAX_LENGTH = 32;
CStrW name = original;
name.Replace(L"[", L"{"); // remove GUI tags
name.Replace(L"]", L"}"); // remove for symmetry
// Restrict the length
if (name.length() > MAX_LENGTH)
name = name.Left(MAX_LENGTH);
// Don't allow surrounding whitespace
name.Trim(PS_TRIM_BOTH);
// Don't allow empty name
if (name.empty())
name = L"Anonymous";
return name;
}
CStrW CNetServer::DeduplicatePlayerName(const CStrW& original)
{
CStrW name = original;
size_t id = 2;
while (true)
{
bool unique = true;
for (size_t i = 0; i < m_Sessions.size(); ++i)
{
if (m_Sessions[i]->GetUserName() == name)
{
unique = false;
break;
}
}
if (unique)
return name;
name = original + L" (" + CStrW(id++) + L")";
}
}

View File

@ -58,6 +58,7 @@ enum NetServerState
*/
enum
{
NSS_UNCONNECTED,
NSS_HANDSHAKE,
NSS_AUTHENTICATE,
NSS_PREGAME,
@ -143,6 +144,16 @@ public:
*/
void UpdateGameAttributes(const CScriptValRooted& attrs);
/**
* Make a player name 'nicer' by limiting the length and removing forbidden characters etc.
*/
static CStrW SanitisePlayerName(const CStrW& original);
/**
* Make a player name unique, if it matches any existing session's name.
*/
CStrW DeduplicatePlayerName(const CStrW& original);
/**
* Get the script context used for game attributes.
*/
@ -165,13 +176,16 @@ private:
void SetupSession(CNetServerSession* session);
bool HandleConnect(CNetServerSession* session);
bool HandleDisconnect(CNetServerSession* session);
void OnUserJoin(CNetServerSession* session);
void OnUserLeave(CNetServerSession* session);
static bool OnClientHandshake(void* context, CFsmEvent* event);
static bool OnAuthenticate(void* context, CFsmEvent* event);
static bool OnInGame(void* context, CFsmEvent* event);
static bool OnChat(void* context, CFsmEvent* event);
static bool OnLoadedGame(void* context, CFsmEvent* event);
static bool OnDisconnect(void* context, CFsmEvent* event);
void CheckGameLoadStatus(CNetServerSession* changedSession);

View File

@ -38,6 +38,9 @@ CNetClientSession::~CNetClientSession()
}
// TODO: the whole disconnection process is a tangled mess - need
// to sort out exactly what happens at what times, and the interactions
// between ENet and session and client and GUI
CNetClientSessionRemote::CNetClientSessionRemote(CNetClient& client) :
CNetClientSession(client), m_Host(NULL), m_Server(NULL)
@ -46,7 +49,15 @@ CNetClientSessionRemote::CNetClientSessionRemote(CNetClient& client) :
CNetClientSessionRemote::~CNetClientSessionRemote()
{
if (m_Host && m_Server)
{
// Disconnect without waiting for confirmation
enet_peer_disconnect_now(m_Server, 0);
enet_host_destroy(m_Host);
m_Host = NULL;
m_Server = NULL;
}
}
bool CNetClientSessionRemote::Connect(u16 port, const CStr& server)
@ -176,7 +187,7 @@ void CNetClientSessionLocal::Poll()
void CNetClientSessionLocal::Disconnect()
{
// TODO
GetClient().HandleDisconnect();
}
bool CNetClientSessionLocal::SendMessage(const CNetMessage* message)
@ -216,7 +227,8 @@ CNetServerSessionRemote::CNetServerSessionRemote(CNetServer& server, ENetPeer* p
void CNetServerSessionRemote::Disconnect()
{
// TODO
// TODO: ought to do reliable async disconnects, probably
enet_peer_disconnect_now(m_Peer, 0);
}
bool CNetServerSessionRemote::SendMessage(const CNetMessage* message)
@ -233,7 +245,7 @@ CNetServerSessionLocal::CNetServerSessionLocal(CNetServer& server, CNetClientSes
void CNetServerSessionLocal::Disconnect()
{
// TODO
m_ClientSession.Disconnect();
}
bool CNetServerSessionLocal::SendMessage(const CNetMessage* message)

View File

@ -165,21 +165,6 @@ PSRETURN CGame::ReallyStartGame()
return 0;
}
void CGame::ChangeNetStatus(ENetStatus status)
{
if (g_GUI && g_GUI->HasPages())
{
const char* statusStr = "?";
switch (status)
{
case NET_WAITING_FOR_CONNECT: statusStr = "waiting_for_connect"; break;
case NET_NORMAL: statusStr = "normal"; break;
}
g_GUI->GetScriptInterface().CallFunctionVoid(OBJECT_TO_JSVAL(g_GUI->GetScriptObject()), "changeNetStatus", statusStr);
}
}
int CGame::GetPlayerID()
{
return m_PlayerID;

View File

@ -98,11 +98,6 @@ public:
void StartGame(const CScriptValRooted& attribs);
PSRETURN ReallyStartGame();
/**
* Notify the game of changes in the network connection status.
*/
void ChangeNetStatus(ENetStatus status);
/*
Perform all per-frame updates
*/

View File

@ -568,8 +568,7 @@ void Shutdown(int UNUSED(flags))
{
MICROLOG(L"Shutdown");
if (g_Game)
EndGame();
EndGame();
ShutdownPs(); // Must delete g_GUI before g_ScriptingHost