Cleanup client init states by bumping protocol version
authorsapier <Sapier at GMX dot net>
Thu, 13 Feb 2014 19:17:42 +0000 (20:17 +0100)
committersapier <Sapier at GMX dot net>
Tue, 8 Apr 2014 19:12:20 +0000 (21:12 +0200)
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

15 files changed:
doc/lua_api.txt
src/client.cpp
src/client.h
src/clientiface.cpp
src/clientiface.h
src/clientserver.h
src/connection.cpp
src/connection.h
src/exceptions.h
src/game.cpp
src/hud.cpp
src/script/lua_api/l_server.cpp
src/script/lua_api/l_server.h
src/server.cpp
src/server.h

index 7138007522101306d86cc57a187ae157b9c314f2..bd060f9f030e77ceaed0874d52ec8d688b43fdc3 100644 (file)
@@ -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)
index 654052ac0a40076aba3c4cdba3facc255e9dbfd3..5b3ebed66e0322a12ee5abe910a48ab1b73f19f6 100644 (file)
@@ -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
index f2e1b86d7bbd133a731aa76bc2ea9216853da586..a5fda98d7c37769d9c65614caac218179dab7c3e 100644 (file)
@@ -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
index 5394cd0029b503ede00ce44180ff53bc0e409310..626e5da74270bbd8b13cc9338c2dc8550da56e00 100644 (file)
@@ -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);
+}
index a2315b3bd96d0d88aad95bfcdf0672e58012fcc7..13bb45a844888095287a79631afafa881628cf1a 100644 (file)
@@ -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()
index d1e250ea84dc54c9c2e96af29cdb360efff83689..5c541863288613afd21854a333d2e82b576126fb 100644 (file)
@@ -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
index 8a23f67c3e48b8845267056b83fc420f8ac38754..290e2cb21f58cce76b554ddf5aa6bb72fc96d6e1 100644 (file)
@@ -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)
index 9d646f4991740a7975d66ad569de17f09f7c4667..0f936eb31524401245b0dcbe8f2e889ec3847b4e 100644 (file)
@@ -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);
index 970c68e4027450149d476b829e754dea8dedd01f..6d6ad333a442bcb5690fbce59e1edf2a9bb25ec2 100644 (file)
@@ -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:
 */
index 699314d30d072704b28c460221e48be75893c477..7d881fa88377c5746b5027dfc0754169ed7029d1 100644 (file)
@@ -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;
index 80112a6ec63b8bcd7bc0a6f5a2ae39661cb1ba02..f87fdfc14b2df3d0f077f31a2eb4a919f98f387a 100644 (file)
@@ -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;
        }
        
index bbf5a707d0d48d08d7654c6ed3c25399328c0f45..531d044ef6f7787ba374429696112f638567d0a7 100644 (file)
@@ -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);
index 0d0aa45c8d0d6ee1697dc1e2bc501f7c804b7b6e..4101f2856a7cdcccb7ab93c06ecccd941b3e6bb6 100644 (file)
@@ -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);
 
index ba7ac1eceedc1ac0b9d32e957b4cf5d825adf993..ebb76b08723960038b612b9808843ec83c22e88a 100644 (file)
@@ -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);
-
-               // 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()<<" ["<<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 << " ";
-                       }
+               if(datasize < 2+8)
+                       return;
 
-                       actionstream<<std::endl;
-               }
+               m_clients.setClientVersion(
+                               peer_id,
+                               data[2], data[3], data[4],
+                               std::string((char*) &data[8],(u16) data[6]));
 
-               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)
index 7813eabbd6d5828a9e4116b8b28dfe993fb1b5ec..c0928e5743d579a8f4a06203a4718e52b9badf85 100644 (file)
@@ -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: