Cleanup client init states by bumping protocol version

Don't use TOSERVER_RECEIVED_MEDIA but TOSERVER_CLIENT_READY as indicatio for client ready
Handle clients with protocol version < 23 (almost) same way as before
Make client tell server about it's version
Add client state to not send bogus player position updates prior init complete
Add access to statistics information (peer connction time,rtt,version)
Fix clients standing stalled in world while preloading item visuals (new clients only)
Add get_player_information to read client specific information from lua
This commit is contained in:
sapier 2014-02-13 20:17:42 +01:00
parent 556bdc260a
commit 142e2d3b74
15 changed files with 625 additions and 144 deletions

@ -1200,6 +1200,29 @@ minetest.features
minetest.has_feature(arg) -> bool, missing_features minetest.has_feature(arg) -> bool, missing_features
^ arg: string or table in format {foo=true, bar=true} ^ arg: string or table in format {foo=true, bar=true}
^ missing_features: {foo=true, bar=true} ^ missing_features: {foo=true, bar=true}
minetest.get_player_information(playername)
^ table containing information about player peer:
{
address = "127.0.0.1", -- ip address of client
ip_version = 4, -- IPv4 / IPv6
min_rtt = 0.01, -- minimum round trip time
max_rtt = 0.2, -- maximum round trip time
avg_rtt = 0.02, -- average round trip time
min_jitter = 0.01, -- minimum packet time jitter
max_jitter = 0.5, -- maximum packet time jitter
avg_jitter = 0.03, -- average packet time jitter
connection_uptime = 200, -- seconds since client connected
-- following information is available on debug build only!!!
-- DO NOT USE IN MODS
--ser_vers = 26, -- serialization version used by client
--prot_vers = 23, -- protocol version used by client
--major = 0, -- major version number
--minor = 4, -- minor version number
--patch = 10, -- patch version number
--vers_string = "0.4.9-git", -- full version string
--state = "Active" -- current client state
}
Logging: Logging:
minetest.debug(line) minetest.debug(line)

@ -47,6 +47,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "serialization.h" #include "serialization.h"
#include "util/serialize.h" #include "util/serialize.h"
#include "config.h" #include "config.h"
#include "cmake_config_githash.h"
#include "util/directiontables.h" #include "util/directiontables.h"
#include "util/pointedthing.h" #include "util/pointedthing.h"
#include "version.h" #include "version.h"
@ -252,7 +253,8 @@ Client::Client(
m_last_time_of_day_f(-1), m_last_time_of_day_f(-1),
m_time_of_day_update_timer(0), m_time_of_day_update_timer(0),
m_recommended_send_interval(0.1), m_recommended_send_interval(0.1),
m_removed_sounds_check_timer(0) m_removed_sounds_check_timer(0),
m_state(LC_Created)
{ {
m_packetcounter_timer = 0.0; m_packetcounter_timer = 0.0;
//m_delete_unused_sectors_timer = 0.0; //m_delete_unused_sectors_timer = 0.0;
@ -325,17 +327,6 @@ void Client::connect(Address address)
m_con.Connect(address); m_con.Connect(address);
} }
bool Client::connectedAndInitialized()
{
if(m_con.Connected() == false)
return false;
if(m_server_ser_ver == SER_FMT_VER_INVALID)
return false;
return true;
}
void Client::step(float dtime) void Client::step(float dtime)
{ {
DSTACK(__FUNCTION_NAME); DSTACK(__FUNCTION_NAME);
@ -373,9 +364,6 @@ void Client::step(float dtime)
} }
} }
// Get connection status
bool connected = connectedAndInitialized();
#if 0 #if 0
{ {
/* /*
@ -467,7 +455,7 @@ void Client::step(float dtime)
} }
#endif #endif
if(connected == false) if(m_state == LC_Created)
{ {
float &counter = m_connection_reinit_timer; float &counter = m_connection_reinit_timer;
counter -= dtime; counter -= dtime;
@ -632,7 +620,7 @@ void Client::step(float dtime)
{ {
counter = 0.0; counter = 0.0;
// connectedAndInitialized() is true, peer exists. // connectedAndInitialized() is true, peer exists.
float avg_rtt = m_con.GetPeerAvgRTT(PEER_ID_SERVER); float avg_rtt = getRTT();
infostream<<"Client: avg_rtt="<<avg_rtt<<std::endl; infostream<<"Client: avg_rtt="<<avg_rtt<<std::endl;
} }
} }
@ -643,7 +631,7 @@ void Client::step(float dtime)
{ {
float &counter = m_playerpos_send_timer; float &counter = m_playerpos_send_timer;
counter += dtime; counter += dtime;
if(counter >= m_recommended_send_interval) if((m_state == LC_Ready) && (counter >= m_recommended_send_interval))
{ {
counter = 0.0; counter = 0.0;
sendPlayerPos(); sendPlayerPos();
@ -1051,6 +1039,8 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
// Send as reliable // Send as reliable
m_con.Send(PEER_ID_SERVER, 1, reply, true); m_con.Send(PEER_ID_SERVER, 1, reply, true);
m_state = LC_Init;
return; return;
} }
@ -1937,7 +1927,7 @@ void Client::Send(u16 channelnum, SharedBuffer<u8> data, bool reliable)
void Client::interact(u8 action, const PointedThing& pointed) void Client::interact(u8 action, const PointedThing& pointed)
{ {
if(connectedAndInitialized() == false){ if(m_state != LC_Ready){
infostream<<"Client::interact() " infostream<<"Client::interact() "
"cancelled (not connected)" "cancelled (not connected)"
<<std::endl; <<std::endl;
@ -2152,6 +2142,27 @@ void Client::sendRespawn()
Send(0, data, true); Send(0, data, true);
} }
void Client::sendReady()
{
DSTACK(__FUNCTION_NAME);
std::ostringstream os(std::ios_base::binary);
writeU16(os, TOSERVER_CLIENT_READY);
writeU8(os,VERSION_MAJOR);
writeU8(os,VERSION_MINOR);
writeU8(os,VERSION_PATCH_ORIG);
writeU8(os,0);
writeU16(os,strlen(CMAKE_VERSION_GITHASH));
os.write(CMAKE_VERSION_GITHASH,strlen(CMAKE_VERSION_GITHASH));
// Make data buffer
std::string s = os.str();
SharedBuffer<u8> data((u8*)s.c_str(), s.size());
// Send as reliable
Send(0, data, true);
}
void Client::sendPlayerPos() void Client::sendPlayerPos()
{ {
LocalPlayer *myplayer = m_env.getLocalPlayer(); LocalPlayer *myplayer = m_env.getLocalPlayer();
@ -2650,16 +2661,14 @@ void Client::afterContentReceived(IrrlichtDevice *device, gui::IGUIFont* font)
infostream<<"- Starting mesh update thread"<<std::endl; infostream<<"- Starting mesh update thread"<<std::endl;
m_mesh_update_thread.Start(); m_mesh_update_thread.Start();
m_state = LC_Ready;
sendReady();
infostream<<"Client::afterContentReceived() done"<<std::endl; infostream<<"Client::afterContentReceived() done"<<std::endl;
} }
float Client::getRTT(void) float Client::getRTT(void)
{ {
try{ return m_con.getPeerStat(PEER_ID_SERVER,con::AVG_RTT);
return m_con.GetPeerAvgRTT(PEER_ID_SERVER);
} catch(con::PeerNotFoundException &e){
return 1337;
}
} }
// IGameDef interface // IGameDef interface

@ -57,6 +57,12 @@ struct QueuedMeshUpdate
~QueuedMeshUpdate(); ~QueuedMeshUpdate();
}; };
enum LocalClientState {
LC_Created,
LC_Init,
LC_Ready
};
/* /*
A thread-safe queue of mesh update tasks A thread-safe queue of mesh update tasks
*/ */
@ -319,14 +325,7 @@ public:
calling this, as it is sent in the initialization. calling this, as it is sent in the initialization.
*/ */
void connect(Address address); void connect(Address address);
/*
returns true when
m_con.Connected() == true
AND m_server_ser_ver != SER_FMT_VER_INVALID
throws con::PeerNotFoundException if connection has been deleted,
eg. timed out.
*/
bool connectedAndInitialized();
/* /*
Stuff that references the environment is valid only as Stuff that references the environment is valid only as
long as this is not called. (eg. Players) long as this is not called. (eg. Players)
@ -354,6 +353,7 @@ public:
void sendDamage(u8 damage); void sendDamage(u8 damage);
void sendBreath(u16 breath); void sendBreath(u16 breath);
void sendRespawn(); void sendRespawn();
void sendReady();
ClientEnvironment& getEnv() ClientEnvironment& getEnv()
{ return m_env; } { return m_env; }
@ -454,6 +454,8 @@ public:
// Send a notification that no conventional media transfer is needed // Send a notification that no conventional media transfer is needed
void received_media(); void received_media();
LocalClientState getState() { return m_state; }
private: private:
// Virtual methods from con::PeerHandler // Virtual methods from con::PeerHandler
@ -537,6 +539,9 @@ private:
// Storage for mesh data for creating multiple instances of the same mesh // Storage for mesh data for creating multiple instances of the same mesh
std::map<std::string, std::string> m_mesh_data; std::map<std::string, std::string> m_mesh_data;
// own state
LocalClientState m_state;
}; };
#endif // !CLIENT_HEADER #endif // !CLIENT_HEADER

@ -17,6 +17,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#include <sstream>
#include "clientiface.h" #include "clientiface.h"
#include "player.h" #include "player.h"
#include "settings.h" #include "settings.h"
@ -397,10 +399,11 @@ void RemoteClient::SetBlocksNotSent(std::map<v3s16, MapBlock*> &blocks)
void RemoteClient::notifyEvent(ClientStateEvent event) void RemoteClient::notifyEvent(ClientStateEvent event)
{ {
std::ostringstream myerror;
switch (m_state) switch (m_state)
{ {
case Invalid: case Invalid:
assert("State update for client in invalid state" != 0); //intentionally do nothing
break; break;
case Created: case Created:
@ -420,7 +423,8 @@ void RemoteClient::notifyEvent(ClientStateEvent event)
/* GotInit2 SetDefinitionsSent SetMediaSent */ /* GotInit2 SetDefinitionsSent SetMediaSent */
default: default:
assert("Invalid client state transition!" == 0); myerror << "Created: Invalid client state transition! " << event;
throw ClientStateError(myerror.str());
} }
break; break;
@ -446,7 +450,8 @@ void RemoteClient::notifyEvent(ClientStateEvent event)
/* Init SetDefinitionsSent SetMediaSent */ /* Init SetDefinitionsSent SetMediaSent */
default: default:
assert("Invalid client state transition!" == 0); myerror << "InitSent: Invalid client state transition! " << event;
throw ClientStateError(myerror.str());
} }
break; break;
@ -467,14 +472,15 @@ void RemoteClient::notifyEvent(ClientStateEvent event)
/* Init GotInit2 SetMediaSent */ /* Init GotInit2 SetMediaSent */
default: default:
assert("Invalid client state transition!" == 0); myerror << "InitDone: Invalid client state transition! " << event;
throw ClientStateError(myerror.str());
} }
break; break;
case DefinitionsSent: case DefinitionsSent:
switch(event) switch(event)
{ {
case SetMediaSent: case SetClientReady:
m_state = Active; m_state = Active;
break; break;
@ -488,7 +494,8 @@ void RemoteClient::notifyEvent(ClientStateEvent event)
/* Init GotInit2 SetDefinitionsSent */ /* Init GotInit2 SetDefinitionsSent */
default: default:
assert("Invalid client state transition!" == 0); myerror << "DefinitionsSent: Invalid client state transition! " << event;
throw ClientStateError(myerror.str());
} }
break; break;
@ -505,7 +512,8 @@ void RemoteClient::notifyEvent(ClientStateEvent event)
/* Init GotInit2 SetDefinitionsSent SetMediaSent SetDenied */ /* Init GotInit2 SetDefinitionsSent SetMediaSent SetDenied */
default: default:
assert("Invalid client state transition!" == 0); myerror << "Active: Invalid client state transition! " << event;
throw ClientStateError(myerror.str());
break; break;
} }
break; break;
@ -516,6 +524,11 @@ void RemoteClient::notifyEvent(ClientStateEvent event)
} }
} }
u32 RemoteClient::uptime()
{
return getTime(PRECISION_SECONDS) - m_connection_time;
}
ClientInterface::ClientInterface(con::Connection* con) ClientInterface::ClientInterface(con::Connection* con)
: :
m_con(con), m_con(con),
@ -749,7 +762,7 @@ void ClientInterface::event(u16 peer_id, ClientStateEvent event)
n->second->notifyEvent(event); n->second->notifyEvent(event);
} }
if ((event == SetMediaSent) || (event == Disconnect) || (event == SetDenied)) if ((event == SetClientReady) || (event == Disconnect) || (event == SetDenied))
{ {
UpdatePlayerList(); UpdatePlayerList();
} }
@ -763,9 +776,24 @@ u16 ClientInterface::getProtocolVersion(u16 peer_id)
std::map<u16, RemoteClient*>::iterator n; std::map<u16, RemoteClient*>::iterator n;
n = m_clients.find(peer_id); n = m_clients.find(peer_id);
// No client to deliver event // No client to get version
if (n == m_clients.end()) if (n == m_clients.end())
return 0; return 0;
return n->second->net_proto_version; return n->second->net_proto_version;
} }
void ClientInterface::setClientVersion(u16 peer_id, u8 major, u8 minor, u8 patch, std::string full)
{
JMutexAutoLock conlock(m_clients_mutex);
// Error check
std::map<u16, RemoteClient*>::iterator n;
n = m_clients.find(peer_id);
// No client to set versions
if (n == m_clients.end())
return;
n->second->setVersionInfo(major,minor,patch,full);
}

@ -34,6 +34,109 @@ class MapBlock;
class ServerEnvironment; class ServerEnvironment;
class EmergeManager; class EmergeManager;
/*
* State Transitions
Start
(peer connect)
|
v
/-----------------\
| |
| Created |
| |
\-----------------/
|
|
+-----------------------------+ invalid playername, password
|IN: | or denied by mod
| TOSERVER_INIT |------------------------------
+-----------------------------+ |
| |
| Auth ok |
| |
+-----------------------------+ |
|OUT: | |
| TOCLIENT_INIT | |
+-----------------------------+ |
| |
v |
/-----------------\ |
| | |
| InitSent | |
| | |
\-----------------/ +------------------
| | |
+-----------------------------+ +-----------------------------+ |
|IN: | |OUT: | |
| TOSERVER_INIT2 | | TOCLIENT_ACCESS_DENIED | |
+-----------------------------+ +-----------------------------+ |
| | |
v v |
/-----------------\ /-----------------\ |
| | | | |
| InitDone | | Denied | |
| | | | |
\-----------------/ \-----------------/ |
| |
+-----------------------------+ |
|OUT: | |
| TOCLIENT_MOVEMENT | |
| TOCLIENT_ITEMDEF | |
| TOCLIENT_NODEDEF | |
| TOCLIENT_ANNOUNCE_MEDIA | |
| TOCLIENT_DETACHED_INVENTORY | |
| TOCLIENT_TIME_OF_DAY | |
+-----------------------------+ |
| |
| |
| ----------------------------------- |
v | | |
/-----------------\ v |
| | +-----------------------------+ |
| DefinitionsSent | |IN: | |
| | | TOSERVER_REQUEST_MEDIA | |
\-----------------/ | TOSERVER_RECEIVED_MEDIA | |
| +-----------------------------+ |
| ^ | |
| ----------------------------------- |
| |
+-----------------------------+ |
|IN: | |
| TOSERVER_CLIENT_READY | |
+-----------------------------+ |
| async |
v mod action |
+-----------------------------+ (ban,kick) |
|OUT: | |
| TOCLIENT_MOVE_PLAYER | |
| TOCLIENT_PRIVILEGES | |
| TOCLIENT_INVENTORY_FORMSPEC | |
| UpdateCrafting | |
| TOCLIENT_INVENTORY | |
| TOCLIENT_HP (opt) | |
| TOCLIENT_BREATH | |
| TOCLIENT_DEATHSCREEN | |
+-----------------------------+ |
| |
v |
/-----------------\ |
| |------------------------------------------------------
| Active |
| |----------------------------------
\-----------------/ timeout |
| +-----------------------------+
| |OUT: |
| | TOCLIENT_DISCONNECT |
| +-----------------------------+
| |
| v
+-----------------------------+ /-----------------\
|IN: | | |
| TOSERVER_DISCONNECT |------------------->| Disconnecting |
+-----------------------------+ | |
\-----------------/
*/
namespace con { namespace con {
class Connection; class Connection;
} }
@ -50,13 +153,24 @@ enum ClientState
Active Active
}; };
static const char** statenames = (const char*[]) {
"Invalid",
"Disconnecting",
"Denied",
"Created",
"InitSent",
"InitDone",
"DefinitionsSent",
"Active"
};
enum ClientStateEvent enum ClientStateEvent
{ {
Init, Init,
GotInit2, GotInit2,
SetDenied, SetDenied,
SetDefinitionsSent, SetDefinitionsSent,
SetMediaSent, SetClientReady,
Disconnect Disconnect
}; };
@ -107,7 +221,12 @@ public:
m_excess_gotblocks(0), m_excess_gotblocks(0),
m_nothing_to_send_counter(0), m_nothing_to_send_counter(0),
m_nothing_to_send_pause_timer(0.0), m_nothing_to_send_pause_timer(0.0),
m_name("") m_name(""),
m_version_major(0),
m_version_minor(0),
m_version_patch(0),
m_full_version("unknown"),
m_connection_time(getTime(PRECISION_SECONDS))
{ {
} }
~RemoteClient() ~RemoteClient()
@ -178,6 +297,23 @@ public:
void confirmSerializationVersion() void confirmSerializationVersion()
{ serialization_version = m_pending_serialization_version; } { serialization_version = m_pending_serialization_version; }
/* get uptime */
u32 uptime();
/* set version information */
void setVersionInfo(u8 major, u8 minor, u8 patch, std::string full) {
m_version_major = major;
m_version_minor = minor;
m_version_patch = patch;
m_full_version = full;
}
/* read version information */
u8 getMajor() { return m_version_major; }
u8 getMinor() { return m_version_minor; }
u8 getPatch() { return m_version_patch; }
std::string getVersion() { return m_full_version; }
private: private:
// Version is stored in here after INIT before INIT2 // Version is stored in here after INIT before INIT2
u8 m_pending_serialization_version; u8 m_pending_serialization_version;
@ -221,7 +357,25 @@ private:
// CPU usage optimization // CPU usage optimization
u32 m_nothing_to_send_counter; u32 m_nothing_to_send_counter;
float m_nothing_to_send_pause_timer; float m_nothing_to_send_pause_timer;
/*
name of player using this client
*/
std::string m_name; std::string m_name;
/*
client information
*/
u8 m_version_major;
u8 m_version_minor;
u8 m_version_patch;
std::string m_full_version;
/*
time this client was created
*/
const u32 m_connection_time;
}; };
class ClientInterface { class ClientInterface {
@ -268,6 +422,9 @@ public:
/* get protocol version of client */ /* get protocol version of client */
u16 getProtocolVersion(u16 peer_id); u16 getProtocolVersion(u16 peer_id);
/* set client version */
void setClientVersion(u16 peer_id, u8 major, u8 minor, u8 patch, std::string full);
/* event to update client state */ /* event to update client state */
void event(u16 peer_id, ClientStateEvent event); void event(u16 peer_id, ClientStateEvent event);
@ -275,6 +432,11 @@ public:
void setEnv(ServerEnvironment* env) void setEnv(ServerEnvironment* env)
{ assert(m_env == 0); m_env = env; } { assert(m_env == 0); m_env = env; }
static std::string state2Name(ClientState state) {
assert(state < sizeof(statenames));
return statenames[state];
}
protected: protected:
//TODO find way to avoid this functions //TODO find way to avoid this functions
void Lock() void Lock()

@ -100,9 +100,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
version, heat and humidity transfer in MapBock version, heat and humidity transfer in MapBock
automatic_face_movement_dir and automatic_face_movement_dir_offset automatic_face_movement_dir and automatic_face_movement_dir_offset
added to object properties added to object properties
PROTOCOL_VERSION 22:
add swap_node
PROTOCOL_VERSION 23:
TOSERVER_CLIENT_READY
*/ */
#define LATEST_PROTOCOL_VERSION 22 #define LATEST_PROTOCOL_VERSION 23
// Server's supported network protocol range // Server's supported network protocol range
#define SERVER_PROTOCOL_VERSION_MIN 13 #define SERVER_PROTOCOL_VERSION_MIN 13
@ -755,6 +759,16 @@ enum ToServerCommand
u16 command u16 command
u16 breath u16 breath
*/ */
TOSERVER_CLIENT_READY = 0x43,
/*
u8 major
u8 minor
u8 patch
u8 reserved
u16 len
u8[len] full_version_string
*/
}; };
#endif #endif

@ -2875,11 +2875,11 @@ Address Connection::GetPeerAddress(u16 peer_id)
return peer_address; return peer_address;
} }
float Connection::GetPeerAvgRTT(u16 peer_id) float Connection::getPeerStat(u16 peer_id, rtt_stat_type type)
{ {
PeerHelper peer = getPeerNoEx(peer_id); PeerHelper peer = getPeerNoEx(peer_id);
if (!peer) return -1; if (!peer) return -1;
return peer->getStat(AVG_RTT); return peer->getStat(type);
} }
u16 Connection::createPeer(Address& sender, MTProtocols protocol, int fd) u16 Connection::createPeer(Address& sender, MTProtocols protocol, int fd)

@ -1004,7 +1004,7 @@ public:
void Send(u16 peer_id, u8 channelnum, SharedBuffer<u8> data, bool reliable); void Send(u16 peer_id, u8 channelnum, SharedBuffer<u8> data, bool reliable);
u16 GetPeerID(){ return m_peer_id; } u16 GetPeerID(){ return m_peer_id; }
Address GetPeerAddress(u16 peer_id); Address GetPeerAddress(u16 peer_id);
float GetPeerAvgRTT(u16 peer_id); float getPeerStat(u16 peer_id, rtt_stat_type type);
const u32 GetProtocolID() const { return m_protocol_id; }; const u32 GetProtocolID() const { return m_protocol_id; };
const std::string getDesc(); const std::string getDesc();
void DisconnectPeer(u16 peer_id); void DisconnectPeer(u16 peer_id);

@ -116,6 +116,11 @@ public:
FatalSystemException(const std::string &s): BaseException(s) {} FatalSystemException(const std::string &s): BaseException(s) {}
}; };
class ClientStateError : public BaseException {
public:
ClientStateError(std::string s): BaseException(s) {}
};
/* /*
Some "old-style" interrupts: Some "old-style" interrupts:
*/ */

@ -1266,7 +1266,7 @@ void the_game(bool &kill, bool random_input, InputHandler *input,
server->step(dtime); server->step(dtime);
// End condition // End condition
if(client.connectedAndInitialized()){ if(client.getState() == LC_Init){
could_connect = true; could_connect = true;
break; break;
} }
@ -1373,7 +1373,7 @@ void the_game(bool &kill, bool random_input, InputHandler *input,
errorstream<<wide_to_narrow(error_message)<<std::endl; errorstream<<wide_to_narrow(error_message)<<std::endl;
break; break;
} }
if(!client.connectedAndInitialized()){ if(client.getState() < LC_Init){
error_message = L"Client disconnected"; error_message = L"Client disconnected";
errorstream<<wide_to_narrow(error_message)<<std::endl; errorstream<<wide_to_narrow(error_message)<<std::endl;
break; break;

@ -363,7 +363,7 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, std::string texture, s
void Hud::drawHotbar(v2s32 centerlowerpos, s32 halfheartcount, u16 playeritem, s32 breath) { void Hud::drawHotbar(v2s32 centerlowerpos, s32 halfheartcount, u16 playeritem, s32 breath) {
InventoryList *mainlist = inventory->getList("main"); InventoryList *mainlist = inventory->getList("main");
if (mainlist == NULL) { if (mainlist == NULL) {
errorstream << "draw_hotbar(): mainlist == NULL" << std::endl; //silently ignore this we may not be initialized completely
return; return;
} }

@ -99,7 +99,7 @@ int ModApiServer::l_get_player_ip(lua_State *L)
} }
try try
{ {
Address addr = getServer(L)->getPeerAddress(getEnv(L)->getPlayer(name)->peer_id); Address addr = getServer(L)->getPeerAddress(player->peer_id);
std::string ip_str = addr.serializeString(); std::string ip_str = addr.serializeString();
lua_pushstring(L, ip_str.c_str()); lua_pushstring(L, ip_str.c_str());
return 1; return 1;
@ -112,6 +112,135 @@ int ModApiServer::l_get_player_ip(lua_State *L)
} }
} }
// get_player_information()
int ModApiServer::l_get_player_information(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
const char * name = luaL_checkstring(L, 1);
Player *player = getEnv(L)->getPlayer(name);
if(player == NULL)
{
lua_pushnil(L); // no such player
return 1;
}
Address addr;
try
{
addr = getServer(L)->getPeerAddress(player->peer_id);
}
catch(con::PeerNotFoundException) // unlikely
{
dstream << __FUNCTION_NAME << ": peer was not found" << std::endl;
lua_pushnil(L); // error
return 1;
}
float min_rtt,max_rtt,avg_rtt,min_jitter,max_jitter,avg_jitter;
ClientState state;
u32 uptime;
u16 prot_vers;
u8 ser_vers,major,minor,patch;
std::string vers_string;
#define ERET(code) \
if (!(code)) { \
dstream << __FUNCTION_NAME << ": peer was not found" << std::endl; \
lua_pushnil(L); /* error */ \
return 1; \
}
ERET(getServer(L)->getClientConInfo(player->peer_id,con::MIN_RTT,&min_rtt))
ERET(getServer(L)->getClientConInfo(player->peer_id,con::MAX_RTT,&max_rtt))
ERET(getServer(L)->getClientConInfo(player->peer_id,con::AVG_RTT,&avg_rtt))
ERET(getServer(L)->getClientConInfo(player->peer_id,con::MIN_JITTER,&min_jitter))
ERET(getServer(L)->getClientConInfo(player->peer_id,con::MAX_JITTER,&max_jitter))
ERET(getServer(L)->getClientConInfo(player->peer_id,con::AVG_JITTER,&avg_jitter))
ERET(getServer(L)->getClientInfo(player->peer_id,
&state, &uptime, &ser_vers, &prot_vers,
&major, &minor, &patch, &vers_string))
lua_newtable(L);
int table = lua_gettop(L);
lua_pushstring(L,"address");
lua_pushstring(L, addr.serializeString().c_str());
lua_settable(L, table);
lua_pushstring(L,"ip_version");
if (addr.getFamily() == AF_INET) {
lua_pushnumber(L, 4);
} else if (addr.getFamily() == AF_INET6) {
lua_pushnumber(L, 6);
} else {
lua_pushnumber(L, 0);
}
lua_settable(L, table);
lua_pushstring(L,"min_rtt");
lua_pushnumber(L, min_rtt);
lua_settable(L, table);
lua_pushstring(L,"max_rtt");
lua_pushnumber(L, max_rtt);
lua_settable(L, table);
lua_pushstring(L,"avg_rtt");
lua_pushnumber(L, avg_rtt);
lua_settable(L, table);
lua_pushstring(L,"min_jitter");
lua_pushnumber(L, min_jitter);
lua_settable(L, table);
lua_pushstring(L,"max_jitter");
lua_pushnumber(L, max_jitter);
lua_settable(L, table);
lua_pushstring(L,"avg_jitter");
lua_pushnumber(L, avg_jitter);
lua_settable(L, table);
lua_pushstring(L,"connection_uptime");
lua_pushnumber(L, uptime);
lua_settable(L, table);
#ifndef NDEBUG
lua_pushstring(L,"serialization_version");
lua_pushnumber(L, ser_vers);
lua_settable(L, table);
lua_pushstring(L,"protocol_version");
lua_pushnumber(L, prot_vers);
lua_settable(L, table);
lua_pushstring(L,"major");
lua_pushnumber(L, major);
lua_settable(L, table);
lua_pushstring(L,"minor");
lua_pushnumber(L, minor);
lua_settable(L, table);
lua_pushstring(L,"patch");
lua_pushnumber(L, patch);
lua_settable(L, table);
lua_pushstring(L,"version_string");
lua_pushstring(L, vers_string.c_str());
lua_settable(L, table);
lua_pushstring(L,"state");
lua_pushstring(L,ClientInterface::state2Name(state).c_str());
lua_settable(L, table);
#endif
#undef ERET
return 1;
}
// get_ban_list() // get_ban_list()
int ModApiServer::l_get_ban_list(lua_State *L) int ModApiServer::l_get_ban_list(lua_State *L)
{ {
@ -343,6 +472,7 @@ void ModApiServer::Initialize(lua_State *L, int top)
API_FCT(sound_play); API_FCT(sound_play);
API_FCT(sound_stop); API_FCT(sound_stop);
API_FCT(get_player_information);
API_FCT(get_player_privs); API_FCT(get_player_privs);
API_FCT(get_player_ip); API_FCT(get_player_ip);
API_FCT(get_ban_list); API_FCT(get_ban_list);

@ -67,6 +67,9 @@ private:
// get_player_ip() // get_player_ip()
static int l_get_player_ip(lua_State *L); static int l_get_player_ip(lua_State *L);
// get_player_information()
static int l_get_player_information(lua_State *L);
// get_ban_list() // get_ban_list()
static int l_get_ban_list(lua_State *L); static int l_get_ban_list(lua_State *L);

@ -1191,6 +1191,111 @@ void Server::Receive()
m_env->removePlayer(peer_id);*/ m_env->removePlayer(peer_id);*/
} }
catch(ClientStateError &e)
{
errorstream << "ProcessData: peer=" << peer_id << e.what() << std::endl;
DenyAccess(peer_id, L"Your client sent something server didn't expect."
L"Try reconnecting or updating your client");
}
}
PlayerSAO* Server::StageTwoClientInit(u16 peer_id)
{
std::string playername = "";
PlayerSAO *playersao = NULL;
m_clients.Lock();
RemoteClient* client = m_clients.lockedGetClientNoEx(peer_id,InitDone);
if (client != NULL) {
playername = client->getName();
playersao = emergePlayer(playername.c_str(), peer_id);
}
m_clients.Unlock();
RemotePlayer *player =
static_cast<RemotePlayer*>(m_env->getPlayer(playername.c_str()));
// If failed, cancel
if((playersao == NULL) || (player == NULL))
{
if(player && player->peer_id != 0){
errorstream<<"Server: "<<playername<<": Failed to emerge player"
<<" (player allocated to an another client)"<<std::endl;
DenyAccess(peer_id, L"Another client is connected with this "
L"name. If your client closed unexpectedly, try again in "
L"a minute.");
} else {
errorstream<<"Server: "<<playername<<": Failed to emerge player"
<<std::endl;
DenyAccess(peer_id, L"Could not allocate player.");
}
return NULL;
}
/*
Send complete position information
*/
SendMovePlayer(peer_id);
// Send privileges
SendPlayerPrivileges(peer_id);
// Send inventory formspec
SendPlayerInventoryFormspec(peer_id);
// Send inventory
UpdateCrafting(peer_id);
SendInventory(peer_id);
// Send HP
if(g_settings->getBool("enable_damage"))
SendPlayerHP(peer_id);
// Send Breath
SendPlayerBreath(peer_id);
// Show death screen if necessary
if(player->hp == 0)
SendDeathscreen(peer_id, false, v3f(0,0,0));
// Note things in chat if not in simple singleplayer mode
if(!m_simple_singleplayer_mode)
{
// Send information about server to player in chat
SendChatMessage(peer_id, getStatusString());
// Send information about joining in chat
{
std::wstring name = L"unknown";
Player *player = m_env->getPlayer(peer_id);
if(player != NULL)
name = narrow_to_wide(player->getName());
std::wstring message;
message += L"*** ";
message += name;
message += L" joined the game.";
SendChatMessage(PEER_ID_INEXISTENT,message);
}
}
actionstream<<player->getName() <<" joins game. " << std::endl;
/*
Print out action
*/
{
std::vector<std::string> names = m_clients.getPlayerNames();
actionstream<<player->getName() <<" joins game. List of players: ";
for (std::vector<std::string>::iterator i = names.begin();
i != names.end(); i++)
{
actionstream << *i << " ";
}
actionstream<<std::endl;
}
return playersao;
} }
void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
@ -1543,6 +1648,21 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
m_clients.event(peer_id, GotInit2); m_clients.event(peer_id, GotInit2);
u16 protocol_version = m_clients.getProtocolVersion(peer_id); u16 protocol_version = m_clients.getProtocolVersion(peer_id);
///// begin compatibility code
PlayerSAO* playersao = NULL;
if (protocol_version <= 22) {
playersao = StageTwoClientInit(peer_id);
if (playersao == NULL) {
errorstream
<< "TOSERVER_INIT2 stage 2 client init failed for peer "
<< peer_id << std::endl;
return;
}
}
///// end compatibility code
/* /*
Send some initialization data Send some initialization data
*/ */
@ -1572,6 +1692,13 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
float time_speed = g_settings->getFloat("time_speed"); float time_speed = g_settings->getFloat("time_speed");
SendTimeOfDay(peer_id, time, time_speed); SendTimeOfDay(peer_id, time, time_speed);
///// begin compatibility code
if (protocol_version <= 22) {
m_clients.event(peer_id, SetClientReady);
m_script->on_joinplayer(playersao);
}
///// end compatibility code
// Warnings about protocol version can be issued here // Warnings about protocol version can be issued here
if(getClient(peer_id)->net_proto_version < LATEST_PROTOCOL_VERSION) if(getClient(peer_id)->net_proto_version < LATEST_PROTOCOL_VERSION)
{ {
@ -1583,6 +1710,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
} }
u8 peer_ser_ver = getClient(peer_id,InitDone)->serialization_version; u8 peer_ser_ver = getClient(peer_id,InitDone)->serialization_version;
u16 peer_proto_ver = getClient(peer_id,InitDone)->net_proto_version;
if(peer_ser_ver == SER_FMT_VER_INVALID) if(peer_ser_ver == SER_FMT_VER_INVALID)
{ {
@ -1615,105 +1743,34 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
return; return;
} }
else if(command == TOSERVER_RECEIVED_MEDIA) { else if(command == TOSERVER_RECEIVED_MEDIA) {
std::string playername = ""; return;
PlayerSAO *playersao = NULL; }
m_clients.Lock(); else if(command == TOSERVER_CLIENT_READY) {
RemoteClient* client = m_clients.lockedGetClientNoEx(peer_id,DefinitionsSent); // clients <= protocol version 22 did not send ready message,
if (client != NULL) { // they're already initialized
playername = client->getName(); assert(peer_proto_ver > 22);
playersao = emergePlayer(playername.c_str(), peer_id);
}
m_clients.Unlock();
RemotePlayer *player = PlayerSAO* playersao = StageTwoClientInit(peer_id);
static_cast<RemotePlayer*>(m_env->getPlayer(playername.c_str()));
// If failed, cancel if (playersao == NULL) {
if((playersao == NULL) || (player == NULL)) errorstream
{ << "TOSERVER_CLIENT_READY stage 2 client init failed for peer "
if(player && player->peer_id != 0){ << peer_id << std::endl;
errorstream<<"Server: "<<playername<<": Failed to emerge player"
<<" (player allocated to an another client)"<<std::endl;
DenyAccess(peer_id, L"Another client is connected with this "
L"name. If your client closed unexpectedly, try again in "
L"a minute.");
} else {
errorstream<<"Server: "<<playername<<": Failed to emerge player"
<<std::endl;
DenyAccess(peer_id, L"Could not allocate player.");
}
return; return;
} }
/*
Send complete position information
*/
SendMovePlayer(peer_id);
// Send privileges if(datasize < 2+8)
SendPlayerPrivileges(peer_id); return;
// Send inventory formspec m_clients.setClientVersion(
SendPlayerInventoryFormspec(peer_id); peer_id,
data[2], data[3], data[4],
std::string((char*) &data[8],(u16) data[6]));
// Send inventory m_clients.event(peer_id, SetClientReady);
UpdateCrafting(peer_id);
SendInventory(peer_id);
// Send HP
if(g_settings->getBool("enable_damage"))
SendPlayerHP(peer_id);
// Send Breath
SendPlayerBreath(peer_id);
// Show death screen if necessary
if(player->hp == 0)
SendDeathscreen(peer_id, false, v3f(0,0,0));
// Note things in chat if not in simple singleplayer mode
if(!m_simple_singleplayer_mode)
{
// Send information about server to player in chat
SendChatMessage(peer_id, getStatusString());
// Send information about joining in chat
{
std::wstring name = L"unknown";
Player *player = m_env->getPlayer(peer_id);
if(player != NULL)
name = narrow_to_wide(player->getName());
std::wstring message;
message += L"*** ";
message += name;
message += L" joined the game.";
SendChatMessage(PEER_ID_INEXISTENT,message);
}
}
actionstream<<player->getName()<<" ["<<addr_s<<"] "<<"joins game. " << std::endl;
/*
Print out action
*/
{
std::vector<std::string> names = m_clients.getPlayerNames();
actionstream<<player->getName()<<" ["<<addr_s<<"] "
<<"joins game. List of players: ";
for (std::vector<std::string>::iterator i = names.begin();
i != names.end(); i++)
{
actionstream << *i << " ";
}
actionstream<<std::endl;
}
m_clients.event(peer_id,SetMediaSent);
m_script->on_joinplayer(playersao); m_script->on_joinplayer(playersao);
return;
} }
else if(command == TOSERVER_GOTBLOCKS) else if(command == TOSERVER_GOTBLOCKS)
{ {
@ -2809,6 +2866,46 @@ void Server::deletingPeer(con::Peer *peer, bool timeout)
m_peer_change_queue.push_back(c); m_peer_change_queue.push_back(c);
} }
bool Server::getClientConInfo(u16 peer_id, con::rtt_stat_type type, float* retval)
{
*retval = m_con.getPeerStat(peer_id,type);
if (*retval == -1) return false;
return true;
}
bool Server::getClientInfo(
u16 peer_id,
ClientState* state,
u32* uptime,
u8* ser_vers,
u16* prot_vers,
u8* major,
u8* minor,
u8* patch,
std::string* vers_string
)
{
*state = m_clients.getClientState(peer_id);
m_clients.Lock();
RemoteClient* client = m_clients.lockedGetClientNoEx(peer_id,Invalid);
if (client == NULL)
return false;
*uptime = client->uptime();
*ser_vers = client->serialization_version;
*prot_vers = client->net_proto_version;
*major = client->getMajor();
*minor = client->getMinor();
*patch = client->getPatch();
*vers_string = client->getPatch();
m_clients.Unlock();
return true;
}
void Server::handlePeerChanges() void Server::handlePeerChanges()
{ {
while(m_peer_change_queue.size() > 0) while(m_peer_change_queue.size() > 0)

@ -185,6 +185,7 @@ public:
// This is run by ServerThread and does the actual processing // This is run by ServerThread and does the actual processing
void AsyncRunStep(bool initial_step=false); void AsyncRunStep(bool initial_step=false);
void Receive(); void Receive();
PlayerSAO* StageTwoClientInit(u16 peer_id);
void ProcessData(u8 *data, u32 datasize, u16 peer_id); void ProcessData(u8 *data, u32 datasize, u16 peer_id);
// Environment must be locked when called // Environment must be locked when called
@ -331,6 +332,10 @@ public:
void deletingPeer(con::Peer *peer, bool timeout); void deletingPeer(con::Peer *peer, bool timeout);
void DenyAccess(u16 peer_id, const std::wstring &reason); void DenyAccess(u16 peer_id, const std::wstring &reason);
bool getClientConInfo(u16 peer_id, con::rtt_stat_type type,float* retval);
bool getClientInfo(u16 peer_id,ClientState* state, u32* uptime,
u8* ser_vers, u16* prot_vers, u8* major, u8* minor, u8* patch,
std::string* vers_string);
private: private: