forked from Mirrorlandia_minetest/minetest
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:
parent
556bdc260a
commit
142e2d3b74
@ -1200,6 +1200,29 @@ minetest.features
|
||||
minetest.has_feature(arg) -> bool, missing_features
|
||||
^ arg: string or table in format {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:
|
||||
minetest.debug(line)
|
||||
|
@ -47,6 +47,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "serialization.h"
|
||||
#include "util/serialize.h"
|
||||
#include "config.h"
|
||||
#include "cmake_config_githash.h"
|
||||
#include "util/directiontables.h"
|
||||
#include "util/pointedthing.h"
|
||||
#include "version.h"
|
||||
@ -252,7 +253,8 @@ Client::Client(
|
||||
m_last_time_of_day_f(-1),
|
||||
m_time_of_day_update_timer(0),
|
||||
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_delete_unused_sectors_timer = 0.0;
|
||||
@ -325,17 +327,6 @@ void Client::connect(Address 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)
|
||||
{
|
||||
DSTACK(__FUNCTION_NAME);
|
||||
@ -372,9 +363,6 @@ void Client::step(float dtime)
|
||||
m_packetcounter.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Get connection status
|
||||
bool connected = connectedAndInitialized();
|
||||
|
||||
#if 0
|
||||
{
|
||||
@ -467,7 +455,7 @@ void Client::step(float dtime)
|
||||
}
|
||||
#endif
|
||||
|
||||
if(connected == false)
|
||||
if(m_state == LC_Created)
|
||||
{
|
||||
float &counter = m_connection_reinit_timer;
|
||||
counter -= dtime;
|
||||
@ -632,7 +620,7 @@ void Client::step(float dtime)
|
||||
{
|
||||
counter = 0.0;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@ -643,7 +631,7 @@ void Client::step(float dtime)
|
||||
{
|
||||
float &counter = m_playerpos_send_timer;
|
||||
counter += dtime;
|
||||
if(counter >= m_recommended_send_interval)
|
||||
if((m_state == LC_Ready) && (counter >= m_recommended_send_interval))
|
||||
{
|
||||
counter = 0.0;
|
||||
sendPlayerPos();
|
||||
@ -1051,6 +1039,8 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
|
||||
// Send as reliable
|
||||
m_con.Send(PEER_ID_SERVER, 1, reply, true);
|
||||
|
||||
m_state = LC_Init;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1937,7 +1927,7 @@ void Client::Send(u16 channelnum, SharedBuffer<u8> data, bool reliable)
|
||||
|
||||
void Client::interact(u8 action, const PointedThing& pointed)
|
||||
{
|
||||
if(connectedAndInitialized() == false){
|
||||
if(m_state != LC_Ready){
|
||||
infostream<<"Client::interact() "
|
||||
"cancelled (not connected)"
|
||||
<<std::endl;
|
||||
@ -2152,6 +2142,27 @@ void Client::sendRespawn()
|
||||
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()
|
||||
{
|
||||
LocalPlayer *myplayer = m_env.getLocalPlayer();
|
||||
@ -2650,16 +2661,14 @@ void Client::afterContentReceived(IrrlichtDevice *device, gui::IGUIFont* font)
|
||||
infostream<<"- Starting mesh update thread"<<std::endl;
|
||||
m_mesh_update_thread.Start();
|
||||
|
||||
m_state = LC_Ready;
|
||||
sendReady();
|
||||
infostream<<"Client::afterContentReceived() done"<<std::endl;
|
||||
}
|
||||
|
||||
float Client::getRTT(void)
|
||||
{
|
||||
try{
|
||||
return m_con.GetPeerAvgRTT(PEER_ID_SERVER);
|
||||
} catch(con::PeerNotFoundException &e){
|
||||
return 1337;
|
||||
}
|
||||
return m_con.getPeerStat(PEER_ID_SERVER,con::AVG_RTT);
|
||||
}
|
||||
|
||||
// IGameDef interface
|
||||
|
21
src/client.h
21
src/client.h
@ -57,6 +57,12 @@ struct QueuedMeshUpdate
|
||||
~QueuedMeshUpdate();
|
||||
};
|
||||
|
||||
enum LocalClientState {
|
||||
LC_Created,
|
||||
LC_Init,
|
||||
LC_Ready
|
||||
};
|
||||
|
||||
/*
|
||||
A thread-safe queue of mesh update tasks
|
||||
*/
|
||||
@ -319,14 +325,7 @@ public:
|
||||
calling this, as it is sent in the initialization.
|
||||
*/
|
||||
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
|
||||
long as this is not called. (eg. Players)
|
||||
@ -354,6 +353,7 @@ public:
|
||||
void sendDamage(u8 damage);
|
||||
void sendBreath(u16 breath);
|
||||
void sendRespawn();
|
||||
void sendReady();
|
||||
|
||||
ClientEnvironment& getEnv()
|
||||
{ return m_env; }
|
||||
@ -454,6 +454,8 @@ public:
|
||||
// Send a notification that no conventional media transfer is needed
|
||||
void received_media();
|
||||
|
||||
LocalClientState getState() { return m_state; }
|
||||
|
||||
private:
|
||||
|
||||
// Virtual methods from con::PeerHandler
|
||||
@ -537,6 +539,9 @@ private:
|
||||
|
||||
// Storage for mesh data for creating multiple instances of the same mesh
|
||||
std::map<std::string, std::string> m_mesh_data;
|
||||
|
||||
// own state
|
||||
LocalClientState m_state;
|
||||
};
|
||||
|
||||
#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.
|
||||
*/
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "clientiface.h"
|
||||
#include "player.h"
|
||||
#include "settings.h"
|
||||
@ -397,10 +399,11 @@ void RemoteClient::SetBlocksNotSent(std::map<v3s16, MapBlock*> &blocks)
|
||||
|
||||
void RemoteClient::notifyEvent(ClientStateEvent event)
|
||||
{
|
||||
std::ostringstream myerror;
|
||||
switch (m_state)
|
||||
{
|
||||
case Invalid:
|
||||
assert("State update for client in invalid state" != 0);
|
||||
//intentionally do nothing
|
||||
break;
|
||||
|
||||
case Created:
|
||||
@ -420,7 +423,8 @@ void RemoteClient::notifyEvent(ClientStateEvent event)
|
||||
|
||||
/* GotInit2 SetDefinitionsSent SetMediaSent */
|
||||
default:
|
||||
assert("Invalid client state transition!" == 0);
|
||||
myerror << "Created: Invalid client state transition! " << event;
|
||||
throw ClientStateError(myerror.str());
|
||||
}
|
||||
break;
|
||||
|
||||
@ -446,7 +450,8 @@ void RemoteClient::notifyEvent(ClientStateEvent event)
|
||||
|
||||
/* Init SetDefinitionsSent SetMediaSent */
|
||||
default:
|
||||
assert("Invalid client state transition!" == 0);
|
||||
myerror << "InitSent: Invalid client state transition! " << event;
|
||||
throw ClientStateError(myerror.str());
|
||||
}
|
||||
break;
|
||||
|
||||
@ -467,14 +472,15 @@ void RemoteClient::notifyEvent(ClientStateEvent event)
|
||||
|
||||
/* Init GotInit2 SetMediaSent */
|
||||
default:
|
||||
assert("Invalid client state transition!" == 0);
|
||||
myerror << "InitDone: Invalid client state transition! " << event;
|
||||
throw ClientStateError(myerror.str());
|
||||
}
|
||||
break;
|
||||
|
||||
case DefinitionsSent:
|
||||
switch(event)
|
||||
{
|
||||
case SetMediaSent:
|
||||
case SetClientReady:
|
||||
m_state = Active;
|
||||
break;
|
||||
|
||||
@ -488,7 +494,8 @@ void RemoteClient::notifyEvent(ClientStateEvent event)
|
||||
|
||||
/* Init GotInit2 SetDefinitionsSent */
|
||||
default:
|
||||
assert("Invalid client state transition!" == 0);
|
||||
myerror << "DefinitionsSent: Invalid client state transition! " << event;
|
||||
throw ClientStateError(myerror.str());
|
||||
}
|
||||
break;
|
||||
|
||||
@ -505,7 +512,8 @@ void RemoteClient::notifyEvent(ClientStateEvent event)
|
||||
|
||||
/* Init GotInit2 SetDefinitionsSent SetMediaSent SetDenied */
|
||||
default:
|
||||
assert("Invalid client state transition!" == 0);
|
||||
myerror << "Active: Invalid client state transition! " << event;
|
||||
throw ClientStateError(myerror.str());
|
||||
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)
|
||||
:
|
||||
m_con(con),
|
||||
@ -749,7 +762,7 @@ void ClientInterface::event(u16 peer_id, ClientStateEvent event)
|
||||
n->second->notifyEvent(event);
|
||||
}
|
||||
|
||||
if ((event == SetMediaSent) || (event == Disconnect) || (event == SetDenied))
|
||||
if ((event == SetClientReady) || (event == Disconnect) || (event == SetDenied))
|
||||
{
|
||||
UpdatePlayerList();
|
||||
}
|
||||
@ -763,9 +776,24 @@ u16 ClientInterface::getProtocolVersion(u16 peer_id)
|
||||
std::map<u16, RemoteClient*>::iterator n;
|
||||
n = m_clients.find(peer_id);
|
||||
|
||||
// No client to deliver event
|
||||
// No client to get version
|
||||
if (n == m_clients.end())
|
||||
return 0;
|
||||
|
||||
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 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 {
|
||||
class Connection;
|
||||
}
|
||||
@ -50,13 +153,24 @@ enum ClientState
|
||||
Active
|
||||
};
|
||||
|
||||
static const char** statenames = (const char*[]) {
|
||||
"Invalid",
|
||||
"Disconnecting",
|
||||
"Denied",
|
||||
"Created",
|
||||
"InitSent",
|
||||
"InitDone",
|
||||
"DefinitionsSent",
|
||||
"Active"
|
||||
};
|
||||
|
||||
enum ClientStateEvent
|
||||
{
|
||||
Init,
|
||||
GotInit2,
|
||||
SetDenied,
|
||||
SetDefinitionsSent,
|
||||
SetMediaSent,
|
||||
SetClientReady,
|
||||
Disconnect
|
||||
};
|
||||
|
||||
@ -107,7 +221,12 @@ public:
|
||||
m_excess_gotblocks(0),
|
||||
m_nothing_to_send_counter(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()
|
||||
@ -178,6 +297,23 @@ public:
|
||||
void confirmSerializationVersion()
|
||||
{ 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:
|
||||
// Version is stored in here after INIT before INIT2
|
||||
u8 m_pending_serialization_version;
|
||||
@ -221,7 +357,25 @@ private:
|
||||
// CPU usage optimization
|
||||
u32 m_nothing_to_send_counter;
|
||||
float m_nothing_to_send_pause_timer;
|
||||
|
||||
/*
|
||||
name of player using this client
|
||||
*/
|
||||
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 {
|
||||
@ -268,6 +422,9 @@ public:
|
||||
/* get protocol version of client */
|
||||
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 */
|
||||
void event(u16 peer_id, ClientStateEvent event);
|
||||
|
||||
@ -275,6 +432,11 @@ public:
|
||||
void setEnv(ServerEnvironment* env)
|
||||
{ assert(m_env == 0); m_env = env; }
|
||||
|
||||
static std::string state2Name(ClientState state) {
|
||||
assert(state < sizeof(statenames));
|
||||
return statenames[state];
|
||||
}
|
||||
|
||||
protected:
|
||||
//TODO find way to avoid this functions
|
||||
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
|
||||
automatic_face_movement_dir and automatic_face_movement_dir_offset
|
||||
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
|
||||
#define SERVER_PROTOCOL_VERSION_MIN 13
|
||||
@ -129,7 +133,7 @@ enum ToClientCommand
|
||||
|
||||
[0] u16 TOSERVER_INIT
|
||||
[2] u8 deployed version
|
||||
[3] v3s16 player's position + v3f(0,BS/2,0) floatToInt'd
|
||||
[3] v3s16 player's position + v3f(0,BS/2,0) floatToInt'd
|
||||
[12] u64 map seed (new as of 2011-02-27)
|
||||
[20] f1000 recommended send interval (in seconds) (new as of 14)
|
||||
|
||||
@ -755,6 +759,16 @@ enum ToServerCommand
|
||||
u16 command
|
||||
u16 breath
|
||||
*/
|
||||
|
||||
TOSERVER_CLIENT_READY = 0x43,
|
||||
/*
|
||||
u8 major
|
||||
u8 minor
|
||||
u8 patch
|
||||
u8 reserved
|
||||
u16 len
|
||||
u8[len] full_version_string
|
||||
*/
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -2875,11 +2875,11 @@ Address Connection::GetPeerAddress(u16 peer_id)
|
||||
return peer_address;
|
||||
}
|
||||
|
||||
float Connection::GetPeerAvgRTT(u16 peer_id)
|
||||
float Connection::getPeerStat(u16 peer_id, rtt_stat_type type)
|
||||
{
|
||||
PeerHelper peer = getPeerNoEx(peer_id);
|
||||
if (!peer) return -1;
|
||||
return peer->getStat(AVG_RTT);
|
||||
return peer->getStat(type);
|
||||
}
|
||||
|
||||
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);
|
||||
u16 GetPeerID(){ return m_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 std::string getDesc();
|
||||
void DisconnectPeer(u16 peer_id);
|
||||
|
@ -116,6 +116,11 @@ public:
|
||||
FatalSystemException(const std::string &s): BaseException(s) {}
|
||||
};
|
||||
|
||||
class ClientStateError : public BaseException {
|
||||
public:
|
||||
ClientStateError(std::string s): BaseException(s) {}
|
||||
};
|
||||
|
||||
/*
|
||||
Some "old-style" interrupts:
|
||||
*/
|
||||
|
@ -1266,7 +1266,7 @@ void the_game(bool &kill, bool random_input, InputHandler *input,
|
||||
server->step(dtime);
|
||||
|
||||
// End condition
|
||||
if(client.connectedAndInitialized()){
|
||||
if(client.getState() == LC_Init){
|
||||
could_connect = true;
|
||||
break;
|
||||
}
|
||||
@ -1373,7 +1373,7 @@ void the_game(bool &kill, bool random_input, InputHandler *input,
|
||||
errorstream<<wide_to_narrow(error_message)<<std::endl;
|
||||
break;
|
||||
}
|
||||
if(!client.connectedAndInitialized()){
|
||||
if(client.getState() < LC_Init){
|
||||
error_message = L"Client disconnected";
|
||||
errorstream<<wide_to_narrow(error_message)<<std::endl;
|
||||
break;
|
||||
|
@ -143,7 +143,7 @@ void Hud::drawItem(v2s32 upperleftpos, s32 imgsize, s32 itemcount,
|
||||
steppos = v2s32(padding, -(padding + i * fullimglen));
|
||||
break;
|
||||
default:
|
||||
steppos = v2s32(padding + i * fullimglen, padding);
|
||||
steppos = v2s32(padding + i * fullimglen, padding);
|
||||
}
|
||||
|
||||
core::rect<s32> rect = imgrect + pos + steppos;
|
||||
@ -334,7 +334,7 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, std::string texture, s
|
||||
steppos = v2s32(0, -1);
|
||||
break;
|
||||
default:
|
||||
steppos = v2s32(1, 0);
|
||||
steppos = v2s32(1, 0);
|
||||
}
|
||||
steppos.X *= srcd.Width;
|
||||
steppos.Y *= srcd.Height;
|
||||
@ -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) {
|
||||
InventoryList *mainlist = inventory->getList("main");
|
||||
if (mainlist == NULL) {
|
||||
errorstream << "draw_hotbar(): mainlist == NULL" << std::endl;
|
||||
//silently ignore this we may not be initialized completely
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,7 @@ int ModApiServer::l_get_player_ip(lua_State *L)
|
||||
}
|
||||
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();
|
||||
lua_pushstring(L, ip_str.c_str());
|
||||
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()
|
||||
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_stop);
|
||||
|
||||
API_FCT(get_player_information);
|
||||
API_FCT(get_player_privs);
|
||||
API_FCT(get_player_ip);
|
||||
API_FCT(get_ban_list);
|
||||
|
@ -67,6 +67,9 @@ private:
|
||||
// get_player_ip()
|
||||
static int l_get_player_ip(lua_State *L);
|
||||
|
||||
// get_player_information()
|
||||
static int l_get_player_information(lua_State *L);
|
||||
|
||||
// get_ban_list()
|
||||
static int l_get_ban_list(lua_State *L);
|
||||
|
||||
|
277
src/server.cpp
277
src/server.cpp
@ -1191,6 +1191,111 @@ void Server::Receive()
|
||||
|
||||
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)
|
||||
@ -1543,6 +1648,21 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
|
||||
m_clients.event(peer_id, GotInit2);
|
||||
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
|
||||
*/
|
||||
@ -1572,6 +1692,13 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
|
||||
float time_speed = g_settings->getFloat("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
|
||||
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;
|
||||
u16 peer_proto_ver = getClient(peer_id,InitDone)->net_proto_version;
|
||||
|
||||
if(peer_ser_ver == SER_FMT_VER_INVALID)
|
||||
{
|
||||
@ -1615,105 +1743,34 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
|
||||
return;
|
||||
}
|
||||
else if(command == TOSERVER_RECEIVED_MEDIA) {
|
||||
std::string playername = "";
|
||||
PlayerSAO *playersao = NULL;
|
||||
m_clients.Lock();
|
||||
RemoteClient* client = m_clients.lockedGetClientNoEx(peer_id,DefinitionsSent);
|
||||
if (client != NULL) {
|
||||
playername = client->getName();
|
||||
playersao = emergePlayer(playername.c_str(), peer_id);
|
||||
}
|
||||
m_clients.Unlock();
|
||||
return;
|
||||
}
|
||||
else if(command == TOSERVER_CLIENT_READY) {
|
||||
// clients <= protocol version 22 did not send ready message,
|
||||
// they're already initialized
|
||||
assert(peer_proto_ver > 22);
|
||||
|
||||
RemotePlayer *player =
|
||||
static_cast<RemotePlayer*>(m_env->getPlayer(playername.c_str()));
|
||||
PlayerSAO* playersao = StageTwoClientInit(peer_id);
|
||||
|
||||
// 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.");
|
||||
}
|
||||
if (playersao == NULL) {
|
||||
errorstream
|
||||
<< "TOSERVER_CLIENT_READY stage 2 client init failed for peer "
|
||||
<< peer_id << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
Send complete position information
|
||||
*/
|
||||
SendMovePlayer(peer_id);
|
||||
|
||||
// Send privileges
|
||||
SendPlayerPrivileges(peer_id);
|
||||
if(datasize < 2+8)
|
||||
return;
|
||||
|
||||
// Send inventory formspec
|
||||
SendPlayerInventoryFormspec(peer_id);
|
||||
m_clients.setClientVersion(
|
||||
peer_id,
|
||||
data[2], data[3], data[4],
|
||||
std::string((char*) &data[8],(u16) data[6]));
|
||||
|
||||
// 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()<<" ["<<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_clients.event(peer_id, SetClientReady);
|
||||
m_script->on_joinplayer(playersao);
|
||||
return;
|
||||
|
||||
}
|
||||
else if(command == TOSERVER_GOTBLOCKS)
|
||||
{
|
||||
@ -2809,6 +2866,46 @@ void Server::deletingPeer(con::Peer *peer, bool timeout)
|
||||
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()
|
||||
{
|
||||
while(m_peer_change_queue.size() > 0)
|
||||
|
@ -185,6 +185,7 @@ public:
|
||||
// This is run by ServerThread and does the actual processing
|
||||
void AsyncRunStep(bool initial_step=false);
|
||||
void Receive();
|
||||
PlayerSAO* StageTwoClientInit(u16 peer_id);
|
||||
void ProcessData(u8 *data, u32 datasize, u16 peer_id);
|
||||
|
||||
// Environment must be locked when called
|
||||
@ -331,6 +332,10 @@ public:
|
||||
void deletingPeer(con::Peer *peer, bool timeout);
|
||||
|
||||
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:
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user