]> git.lizzy.rs Git - minetest.git/blobdiff - src/server.cpp
added dedicated server build without irrlicht
[minetest.git] / src / server.cpp
index a5f55ab5df89d191b1be96e4a060b93128463448..a2dfc8269790cbbd8b43aba9114453a73a2bd937 100644 (file)
@@ -1,3 +1,22 @@
+/*
+Minetest-c55
+Copyright (C) 2010 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
 /*
 (c) 2010 Perttu Ahola <celeron55@gmail.com>
 */
@@ -10,6 +29,7 @@
 #include "jmutexautolock.h"
 #include "main.h"
 #include "constants.h"
+#include "voxel.h"
 
 void * ServerThread::Thread()
 {
@@ -74,6 +94,8 @@ void * EmergeThread::Thread()
                v3s16 &p = q->pos;
                
                //derr_server<<"EmergeThread::Thread(): running"<<std::endl;
+
+               //TimeTaker timer("block emerge", g_device);
                
                /*
                        Try to emerge it from somewhere.
@@ -165,12 +187,27 @@ void * EmergeThread::Thread()
                                dout_server<<std::endl;
                        }
 
+                       /*
+                               Update water pressure
+                       */
+
+                       m_server->UpdateBlockWaterPressure(block, modified_blocks);
+
+                       for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator();
+                                       i.atEnd() == false; i++)
+                       {
+                               MapBlock *block = i.getNode()->getValue();
+                               m_server->UpdateBlockWaterPressure(block, modified_blocks);
+                               //v3s16 p = i.getNode()->getKey();
+                               //m_server->UpdateBlockWaterPressure(p, modified_blocks);
+                       }
+
                        /*
                                Collect a list of blocks that have been modified in
                                addition to the fetched one.
                        */
 
-                       // Add all the "changed blocks"
+                       // Add all the "changed blocks" to modified_blocks
                        for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator();
                                        i.atEnd() == false; i++)
                        {
@@ -178,7 +215,10 @@ void * EmergeThread::Thread()
                                modified_blocks.insert(block->getPos(), block);
                        }
                        
-                       //TimeTaker timer("** updateLighting", g_device);
+                       /*dstream<<"lighting "<<lighting_invalidated_blocks.size()
+                                       <<" blocks"<<std::endl;
+                       TimeTaker timer("** updateLighting", g_device);*/
+                       
                        // Update lighting without locking the environment mutex,
                        // add modified blocks to changed blocks
                        map.updateLighting(lighting_invalidated_blocks, modified_blocks);
@@ -221,12 +261,6 @@ void * EmergeThread::Thread()
                                // Remove block from sent history
                                client->SetBlocksNotSent(modified_blocks);
                        }
-                       
-                       if(q->peer_ids.find(client->peer_id) != NULL)
-                       {
-                               // Decrement emerge queue count of client
-                               client->BlockEmerged();
-                       }
                }
                
        }
@@ -246,28 +280,23 @@ void * EmergeThread::Thread()
        return NULL;
 }
 
-void RemoteClient::SendBlocks(Server *server, float dtime)
+void RemoteClient::GetNextBlocks(Server *server, float dtime,
+               core::array<PrioritySortedBlockTransfer> &dest)
 {
        DSTACK(__FUNCTION_NAME);
-       /*
-               Find what blocks to send to the client next, and send them.
-
-               Throttling is based on limiting the amount of blocks "flying"
-               at a given time.
-       */
-
-       // Can't send anything without knowing version
-       if(serialization_version == SER_FMT_VER_INVALID)
+       
+       // Increment timers
        {
-               dstream<<"RemoteClient::SendBlocks(): Not sending, no version."
-                               <<std::endl;
-               return;
+               JMutexAutoLock lock(m_blocks_sent_mutex);
+               m_nearest_unsent_reset_timer += dtime;
        }
 
+       // Won't send anything if already sending
        {
                JMutexAutoLock lock(m_blocks_sending_mutex);
                
-               if(m_blocks_sending.size() >= MAX_SIMULTANEOUS_BLOCK_SENDS)
+               if(m_blocks_sending.size() >= g_settings.getU16
+                               ("max_simultaneous_block_sends_per_client"))
                {
                        //dstream<<"Not sending any blocks, Queue full."<<std::endl;
                        return;
@@ -284,56 +313,8 @@ void RemoteClient::SendBlocks(Server *server, float dtime)
        v3s16 center = getNodeBlockPos(center_nodepos);
 
        /*
-               Find out what block the player is going to next and set
-               center to it.
-
-               Don't react to speeds under the initial value of highest_speed
+               Get the starting value of the block finder radius.
        */
-       /*f32 highest_speed = 0.1 * BS;
-       v3s16 dir(0,0,0);
-       if(abs(playerspeed.X) > highest_speed)
-       {
-               highest_speed = playerspeed.X;
-               if(playerspeed.X > 0)
-                       dir = v3s16(1,0,0);
-               else
-                       dir = v3s16(-1,0,0);
-       }
-       if(abs(playerspeed.Y) > highest_speed)
-       {
-               highest_speed = playerspeed.Y;
-               if(playerspeed.Y > 0)
-                       dir = v3s16(0,1,0);
-               else
-                       dir = v3s16(0,-1,0);
-       }
-       if(abs(playerspeed.Z) > highest_speed)
-       {
-               highest_speed = playerspeed.Z;
-               if(playerspeed.Z > 0)
-                       dir = v3s16(0,0,1);
-               else
-                       dir = v3s16(0,0,-1);
-       }
-
-       center += dir;*/
-       
-       /*
-               Calculate the starting value of the block finder radius.
-
-               The radius shall be the last used value minus the
-               maximum moved distance.
-       */
-       /*s16 d_start = m_last_block_find_d;
-       if(max_moved >= d_start)
-       {
-               d_start = 0;
-       }
-       else
-       {
-               d_start -= max_moved;
-       }*/
-       
        s16 last_nearest_unsent_d;
        s16 d_start;
        {
@@ -345,12 +326,13 @@ void RemoteClient::SendBlocks(Server *server, float dtime)
                        m_last_center = center;
                }
 
-               static float reset_counter = 0;
-               reset_counter += dtime;
-               if(reset_counter > 5.0)
+               /*dstream<<"m_nearest_unsent_reset_timer="
+                               <<m_nearest_unsent_reset_timer<<std::endl;*/
+               if(m_nearest_unsent_reset_timer > 5.0)
                {
-                       reset_counter = 0;
+                       m_nearest_unsent_reset_timer = 0;
                        m_nearest_unsent_d = 0;
+                       //dstream<<"Resetting m_nearest_unsent_d"<<std::endl;
                }
 
                last_nearest_unsent_d = m_nearest_unsent_d;
@@ -358,15 +340,19 @@ void RemoteClient::SendBlocks(Server *server, float dtime)
                d_start = m_nearest_unsent_d;
        }
 
-       u16 maximum_simultaneous_block_sends = MAX_SIMULTANEOUS_BLOCK_SENDS;
+       u16 maximum_simultaneous_block_sends_setting = g_settings.getU16
+                       ("max_simultaneous_block_sends_per_client");
+       u16 maximum_simultaneous_block_sends = 
+                       maximum_simultaneous_block_sends_setting;
 
+       /*
+               Check the time from last addNode/removeNode.
+               
+               Decrease send rate if player is building stuff.
+       */
        {
                SharedPtr<JMutexAutoLock> lock(m_time_from_building.getLock());
                m_time_from_building.m_value += dtime;
-               /*
-                       Check the time from last addNode/removeNode.
-                       Decrease send rate if player is building stuff.
-               */
                if(m_time_from_building.m_value
                                < FULL_BLOCK_SEND_ENABLE_MIN_TIME_FROM_BUILDING)
                {
@@ -374,24 +360,29 @@ void RemoteClient::SendBlocks(Server *server, float dtime)
                                = LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS;
                }
        }
+       
+       u32 num_blocks_selected;
+       {
+               JMutexAutoLock lock(m_blocks_sending_mutex);
+               num_blocks_selected = m_blocks_sending.size();
+       }
+       
+       /*
+               next time d will be continued from the d from which the nearest
+               unsent block was found this time.
+
+               This is because not necessarily any of the blocks found this
+               time are actually sent.
+       */
+       s32 new_nearest_unsent_d = -1;
 
        // Serialization version used
        //u8 ser_version = serialization_version;
 
        //bool has_incomplete_blocks = false;
        
-       /*
-               TODO: Get this from somewhere
-               TODO: Values more than 7 make placing and removing blocks very
-                     sluggish when the map is being generated. This is
-                         because d is looped every time from 0 to d_max if no
-                         blocks are found for sending.
-       */
-       //s16 d_max = 7;
-       s16 d_max = 8;
-
-       //TODO: Get this from somewhere (probably a bigger value)
-       s16 d_max_gen = 5;
+       s16 d_max = g_settings.getS16("max_block_send_distance");
+       s16 d_max_gen = g_settings.getS16("max_block_generate_distance");
        
        //dstream<<"Starting from "<<d_start<<std::endl;
 
@@ -411,12 +402,8 @@ void RemoteClient::SendBlocks(Server *server, float dtime)
                        if(m_nearest_unsent_d != last_nearest_unsent_d)
                        {
                                d = m_nearest_unsent_d;
+                               last_nearest_unsent_d = m_nearest_unsent_d;
                        }
-                       else
-                       {
-                               m_nearest_unsent_d = d;
-                       }
-                       last_nearest_unsent_d = m_nearest_unsent_d;
                }
 
                /*
@@ -434,19 +421,31 @@ void RemoteClient::SendBlocks(Server *server, float dtime)
                        /*
                                Send throttling
                                - Don't allow too many simultaneous transfers
+                               - EXCEPT when the blocks are very close
 
                                Also, don't send blocks that are already flying.
                        */
+                       
+                       u16 maximum_simultaneous_block_sends_now =
+                                       maximum_simultaneous_block_sends;
+                       
+                       if(d <= BLOCK_SEND_DISABLE_LIMITS_MAX_D)
+                       {
+                               maximum_simultaneous_block_sends_now =
+                                               maximum_simultaneous_block_sends_setting;
+                       }
+
                        {
                                JMutexAutoLock lock(m_blocks_sending_mutex);
                                
-                               if(m_blocks_sending.size()
-                                               >= maximum_simultaneous_block_sends)
+                               // Limit is dynamically lowered when building
+                               if(num_blocks_selected
+                                               >= maximum_simultaneous_block_sends_now)
                                {
                                        /*dstream<<"Not sending more blocks. Queue full. "
                                                        <<m_blocks_sending.size()
                                                        <<std::endl;*/
-                                       return;
+                                       goto queue_full;
                                }
 
                                if(m_blocks_sending.find(p) != NULL)
@@ -479,7 +478,7 @@ void RemoteClient::SendBlocks(Server *server, float dtime)
                                if(m_blocks_sent.find(p) != NULL)
                                        continue;
                        }
-                                       
+
                        /*
                                Check if map has this block
                        */
@@ -509,7 +508,7 @@ void RemoteClient::SendBlocks(Server *server, float dtime)
 
                        /*
                                If block has been marked to not exist on disk (dummy)
-                               and generating new ones is not wanted, skip block. TODO
+                               and generating new ones is not wanted, skip block.
                        */
                        if(generate == false && surely_not_found_on_disk == true)
                        {
@@ -517,21 +516,26 @@ void RemoteClient::SendBlocks(Server *server, float dtime)
                                continue;
                        }
 
+                       /*
+                               Record the lowest d from which a a block has been
+                               found being not sent and possibly to exist
+                       */
+                       if(new_nearest_unsent_d == -1 || d < new_nearest_unsent_d)
+                       {
+                               new_nearest_unsent_d = d;
+                       }
+                                       
                        /*
                                Add inexistent block to emerge queue.
                        */
                        if(block == NULL || surely_not_found_on_disk)
                        {
-                               // Block not found.
-                               SharedPtr<JMutexAutoLock> lock
-                                               (m_num_blocks_in_emerge_queue.getLock());
+                               /*SharedPtr<JMutexAutoLock> lock
+                                               (m_num_blocks_in_emerge_queue.getLock());*/
                                
                                //TODO: Get value from somewhere
-                               //TODO: Balance between clients
-                               //if(server->m_emerge_queue.size() < 1)
-
                                // Allow only one block in emerge queue
-                               if(m_num_blocks_in_emerge_queue.m_value == 0)
+                               if(server->m_emerge_queue.peerItemCount(peer_id) < 1)
                                {
                                        // Add it to the emerge queue and trigger the thread
                                        
@@ -539,10 +543,6 @@ void RemoteClient::SendBlocks(Server *server, float dtime)
                                        if(generate == false)
                                                flags |= TOSERVER_GETBLOCK_FLAG_OPTIONAL;
                                        
-                                       {
-                                               m_num_blocks_in_emerge_queue.m_value++;
-                                       }
-
                                        server->m_emerge_queue.addBlock(peer_id, p, flags);
                                        server->m_emergethread.trigger();
                                }
@@ -552,23 +552,23 @@ void RemoteClient::SendBlocks(Server *server, float dtime)
                        }
 
                        /*
-                               Send block
+                               Add block to queue
                        */
-                       
-                       /*dstream<<"RemoteClient::SendBlocks(): d="<<d<<", p="
-                               <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
-                               <<" sending queue size: "<<m_blocks_sending.size()<<std::endl;*/
 
-                       server->SendBlockNoLock(peer_id, block, serialization_version);
-                       
-                       /*
-                               Add to history
-                       */
-                       SentBlock(p);
+                       PrioritySortedBlockTransfer q((float)d, p, peer_id);
+
+                       dest.push_back(q);
+
+                       num_blocks_selected += 1;
                }
        }
+queue_full:
 
-       // Don't add anything here. The loop breaks by returning.
+       if(new_nearest_unsent_d != -1)
+       {
+               JMutexAutoLock lock(m_blocks_sent_mutex);
+               m_nearest_unsent_d = new_nearest_unsent_d;
+       }
 }
 
 void RemoteClient::SendObjectData(
@@ -659,6 +659,10 @@ void RemoteClient::SendObjectData(
                in memory):
                - Set blocks changed
                - Add blocks to emerge queue if they are not found
+
+               SUGGESTION: These could be ignored from the backside of the player
+
+               TODO: Keep track of total size of packet and stop when it is too big
        */
 
        Player *player = server->m_env.getPlayer(peer_id);
@@ -669,9 +673,14 @@ void RemoteClient::SendObjectData(
        v3s16 center_nodepos = floatToInt(playerpos);
        v3s16 center = getNodeBlockPos(center_nodepos);
 
-       s16 d_max = ACTIVE_OBJECT_D_BLOCKS;
+       //s16 d_max = ACTIVE_OBJECT_D_BLOCKS;
+       s16 d_max = g_settings.getS16("active_object_range");
+       
+       // Number of blocks whose objects were written to bos
+       u16 blockcount = 0;
 
-       core::map<v3s16, MapBlock*> blocks;
+       //core::map<v3s16, MapBlock*> blocks;
+       std::ostringstream bos(std::ios_base::binary);
 
        for(s16 d = 0; d <= d_max; d++)
        {
@@ -691,13 +700,18 @@ void RemoteClient::SendObjectData(
                                if(m_blocks_sent.find(p) == NULL)
                                        continue;
                        }
-
+                       
+                       // Try stepping block and add it to a send queue
                        try
                        {
 
                        // Get block
                        MapBlock *block = server->m_env.getMap().getBlockNoCreate(p);
 
+                       // Skip block if there are no objects
+                       if(block->getObjectCount() == 0)
+                               continue;
+                       
                        // Step block if not in stepped_blocks and add to stepped_blocks
                        if(stepped_blocks.find(p) == NULL)
                        {
@@ -705,9 +719,30 @@ void RemoteClient::SendObjectData(
                                stepped_blocks.insert(p, true);
                                block->setChangedFlag();
                        }
-                       
-                       // Add block to queue
-                       blocks.insert(p, block);
+
+                       /*
+                               Write objects
+                       */
+
+                       // Write blockpos
+                       writeV3S16(buf, p);
+                       bos.write((char*)buf, 6);
+
+                       // Write objects
+                       block->serializeObjects(bos, serialization_version);
+
+                       blockcount++;
+
+                       /*
+                               Stop collecting objects if data is already too big
+                       */
+                       // Sum of player and object data sizes
+                       s32 sum = (s32)os.tellp() + 2 + (s32)bos.tellp();
+                       // break out if data too big
+                       if(sum > MAX_OBJECTDATA_SIZE)
+                       {
+                               goto skip_subsequent;
+                       }
                        
                        } //try
                        catch(InvalidPositionException &e)
@@ -717,9 +752,9 @@ void RemoteClient::SendObjectData(
                                // Fetch the block only if it is on disk.
                                
                                // Grab and increment counter
-                               SharedPtr<JMutexAutoLock> lock
+                               /*SharedPtr<JMutexAutoLock> lock
                                                (m_num_blocks_in_emerge_queue.getLock());
-                               m_num_blocks_in_emerge_queue.m_value++;
+                               m_num_blocks_in_emerge_queue.m_value++;*/
                                
                                // Add to queue as an anonymous fetch from disk
                                u8 flags = TOSERVER_GETBLOCK_FLAG_OPTIONAL;
@@ -729,33 +764,21 @@ void RemoteClient::SendObjectData(
                }
        }
 
-       /*
-               Write objects
-       */
-
-       u16 blockcount = blocks.size();
+skip_subsequent:
 
        // Write block count
        writeU16(buf, blockcount);
        os.write((char*)buf, 2);
-       
-       for(core::map<v3s16, MapBlock*>::Iterator
-                       i = blocks.getIterator();
-                       i.atEnd() == false; i++)
-       {
-               v3s16 p = i.getNode()->getKey();
-               // Write blockpos
-               writeV3S16(buf, p);
-               os.write((char*)buf, 6);
-               // Write objects
-               MapBlock *block = i.getNode()->getValue();
-               block->serializeObjects(os, serialization_version);
-       }
-       
+
+       // Write block objects
+       os<<bos.str();
+
        /*
                Send data
        */
        
+       //dstream<<"Server: Sending object data to "<<peer_id<<std::endl;
+
        // Make data buffer
        std::string s = os.str();
        SharedBuffer<u8> data((u8*)s.c_str(), s.size());
@@ -824,12 +847,12 @@ void RemoteClient::SetBlocksNotSent(core::map<v3s16, MapBlock*> &blocks)
        }
 }
 
-void RemoteClient::BlockEmerged()
+/*void RemoteClient::BlockEmerged()
 {
        SharedPtr<JMutexAutoLock> lock(m_num_blocks_in_emerge_queue.getLock());
        assert(m_num_blocks_in_emerge_queue.m_value > 0);
        m_num_blocks_in_emerge_queue.m_value--;
-}
+}*/
 
 /*void RemoteClient::RunSendingTimeouts(float dtime, float timeout)
 {
@@ -897,15 +920,20 @@ u32 PIChecksum(core::list<PlayerInfo> &l)
 
 Server::Server(
                std::string mapsavedir,
-               bool creative_mode,
-               MapgenParams mapgen_params
+               HMParams hm_params,
+               MapParams map_params
        ):
-       m_env(new ServerMap(mapsavedir, mapgen_params), dout_server),
+       m_env(new ServerMap(mapsavedir, hm_params, map_params), dout_server),
        m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this),
        m_thread(this),
-       m_emergethread(this),
-       m_creative_mode(creative_mode)
+       m_emergethread(this)
 {
+       m_flowwater_timer = 0.0;
+       m_print_info_timer = 0.0;
+       m_objectdata_timer = 0.0;
+       m_emergethread_trigger_timer = 0.0;
+       m_savemap_timer = 0.0;
+       
        m_env_mutex.Init();
        m_con_mutex.Init();
        m_step_dtime_mutex.Init();
@@ -943,7 +971,7 @@ void Server::start(unsigned short port)
        m_thread.stop();
        
        // Initialize connection
-       m_con.setTimeoutMs(50);
+       m_con.setTimeoutMs(30);
        m_con.Serve(port);
 
        // Start thread
@@ -980,15 +1008,24 @@ void Server::step(float dtime)
 void Server::AsyncRunStep()
 {
        DSTACK(__FUNCTION_NAME);
+       
        float dtime;
        {
                JMutexAutoLock lock1(m_step_dtime_mutex);
                dtime = m_step_dtime;
-               if(dtime < 0.001)
-                       return;
-               m_step_dtime = 0.0;
        }
        
+       // Send blocks to clients
+       SendBlocks(dtime);
+       
+       if(dtime < 0.001)
+               return;
+       
+       {
+               JMutexAutoLock lock1(m_step_dtime_mutex);
+               m_step_dtime -= dtime;
+       }
+
        //dstream<<"Server steps "<<dtime<<std::endl;
        
        //dstream<<"Server::AsyncRunStep(): dtime="<<dtime<<std::endl;
@@ -1009,10 +1046,83 @@ void Server::AsyncRunStep()
        /*
                Do background stuff
        */
+
+       /*
+               Flow water
+       */
+       {
+               float interval;
+               
+               if(g_settings.getBool("endless_water") == false)
+                       interval = 1.0;
+               else
+                       interval = 0.25;
+
+               float &counter = m_flowwater_timer;
+               counter += dtime;
+               if(counter >= 0.25 && m_flow_active_nodes.size() > 0)
+               {
+               
+               counter = 0.0;
+
+               core::map<v3s16, MapBlock*> modified_blocks;
+
+               {
+
+                       JMutexAutoLock envlock(m_env_mutex);
+                       
+                       MapVoxelManipulator v(&m_env.getMap());
+                       v.m_disable_water_climb =
+                                       g_settings.getBool("disable_water_climb");
+                       
+                       if(g_settings.getBool("endless_water") == false)
+                               v.flowWater(m_flow_active_nodes, 0, false, 250);
+                       else
+                               v.flowWater(m_flow_active_nodes, 0, false, 50);
+
+                       v.blitBack(modified_blocks);
+
+                       ServerMap &map = ((ServerMap&)m_env.getMap());
+                       
+                       // Update lighting
+                       core::map<v3s16, MapBlock*> lighting_modified_blocks;
+                       map.updateLighting(modified_blocks, lighting_modified_blocks);
+                       
+                       // Add blocks modified by lighting to modified_blocks
+                       for(core::map<v3s16, MapBlock*>::Iterator
+                                       i = lighting_modified_blocks.getIterator();
+                                       i.atEnd() == false; i++)
+                       {
+                               MapBlock *block = i.getNode()->getValue();
+                               modified_blocks.insert(block->getPos(), block);
+                       }
+               } // envlock
+
+               /*
+                       Set the modified blocks unsent for all the clients
+               */
+               
+               JMutexAutoLock lock2(m_con_mutex);
+
+               for(core::map<u16, RemoteClient*>::Iterator
+                               i = m_clients.getIterator();
+                               i.atEnd() == false; i++)
+               {
+                       RemoteClient *client = i.getNode()->getValue();
+                       
+                       if(modified_blocks.size() > 0)
+                       {
+                               // Remove block from sent history
+                               client->SetBlocksNotSent(modified_blocks);
+                       }
+               }
+
+               } // interval counter
+       }
        
        // Periodically print some info
        {
-               static float counter = 0.0;
+               float &counter = m_print_info_timer;
                counter += dtime;
                if(counter >= 30.0)
                {
@@ -1031,32 +1141,116 @@ void Server::AsyncRunStep()
                }
        }
 
-       // Run time- and client- related stuff
-       // NOTE: If you intend to add something here, check that it
-       // doesn't fit in RemoteClient::SendBlocks for exampel.
-       /*{
-               // Clients are behind connection lock
-               JMutexAutoLock lock(m_con_mutex);
+       /*
+               Update digging
+
+               NOTE: Some of this could be moved to RemoteClient
+       */
+
+       {
+               JMutexAutoLock envlock(m_env_mutex);
+               JMutexAutoLock conlock(m_con_mutex);
 
                for(core::map<u16, RemoteClient*>::Iterator
                        i = m_clients.getIterator();
                        i.atEnd() == false; i++)
                {
                        RemoteClient *client = i.getNode()->getValue();
-                       //con::Peer *peer = m_con.GetPeer(client->peer_id);
-                       //client->RunSendingTimeouts(dtime, peer->resend_timeout);
+                       Player *player = m_env.getPlayer(client->peer_id);
+
+                       JMutexAutoLock digmutex(client->m_dig_mutex);
+
+                       if(client->m_dig_tool_item == -1)
+                               continue;
+
+                       client->m_dig_time_remaining -= dtime;
+
+                       if(client->m_dig_time_remaining > 0)
+                               continue;
+
+                       v3s16 p_under = client->m_dig_position;
+                       
+                       // Mandatory parameter; actually used for nothing
+                       core::map<v3s16, MapBlock*> modified_blocks;
+
+                       u8 material;
+
+                       try
+                       {
+                               // Get material at position
+                               material = m_env.getMap().getNode(p_under).d;
+                               // If it's not diggable, do nothing
+                               if(content_diggable(material) == false)
+                               {
+                                       derr_server<<"Server: Not finishing digging: Node not diggable"
+                                                       <<std::endl;
+                                       client->m_dig_tool_item = -1;
+                                       break;
+                               }
+                       }
+                       catch(InvalidPositionException &e)
+                       {
+                               derr_server<<"Server: Not finishing digging: Node not found"
+                                               <<std::endl;
+                               client->m_dig_tool_item = -1;
+                               break;
+                       }
+                       
+                       // Create packet
+                       u32 replysize = 8;
+                       SharedBuffer<u8> reply(replysize);
+                       writeU16(&reply[0], TOCLIENT_REMOVENODE);
+                       writeS16(&reply[2], p_under.X);
+                       writeS16(&reply[4], p_under.Y);
+                       writeS16(&reply[6], p_under.Z);
+                       // Send as reliable
+                       m_con.SendToAll(0, reply, true);
+                       
+                       if(g_settings.getBool("creative_mode") == false)
+                       {
+                               // Add to inventory and send inventory
+                               InventoryItem *item = new MaterialItem(material, 1);
+                               player->inventory.addItem(item);
+                               SendInventory(player->peer_id);
+                       }
+
+                       /*
+                               Remove the node
+                               (this takes some time so it is done after the quick stuff)
+                       */
+                       m_env.getMap().removeNodeAndUpdate(p_under, modified_blocks);
+                       
+                       /*
+                               Update water
+                       */
+                       
+                       // Update water pressure around modification
+                       // This also adds it to m_flow_active_nodes if appropriate
+
+                       MapVoxelManipulator v(&m_env.getMap());
+                       v.m_disable_water_climb =
+                                       g_settings.getBool("disable_water_climb");
+                       
+                       VoxelArea area(p_under-v3s16(1,1,1), p_under+v3s16(1,1,1));
+
+                       try
+                       {
+                               v.updateAreaWaterPressure(area, m_flow_active_nodes);
+                       }
+                       catch(ProcessingLimitException &e)
+                       {
+                               dstream<<"Processing limit reached (1)"<<std::endl;
+                       }
+                       
+                       v.blitBack(modified_blocks);
                }
-       }*/
+       }
 
-       // Send blocks to clients
-       SendBlocks(dtime);
-       
        // Send object positions
        {
-               static float counter = 0.0;
+               float &counter = m_objectdata_timer;
                counter += dtime;
-               //TODO: Get value from somewhere
-               if(counter >= 0.1)
+               if(counter >= g_settings.getFloat("objectdata_interval"))
                {
                        JMutexAutoLock lock1(m_env_mutex);
                        JMutexAutoLock lock2(m_con_mutex);
@@ -1065,10 +1259,23 @@ void Server::AsyncRunStep()
                        counter = 0.0;
                }
        }
+       
+       // Trigger emergethread (it gets somehow gets to a
+       // non-triggered but bysy state sometimes)
+       {
+               float &counter = m_emergethread_trigger_timer;
+               counter += dtime;
+               if(counter >= 2.0)
+               {
+                       counter = 0.0;
+                       
+                       m_emergethread.trigger();
+               }
+       }
 
+       // Save map
        {
-               // Save map
-               static float counter = 0.0;
+               float &counter = m_savemap_timer;
                counter += dtime;
                if(counter >= SERVER_MAP_SAVE_INTERVAL)
                {
@@ -1366,7 +1573,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                // Left click
                if(button == 0)
                {
-                       if(m_creative_mode == false)
+                       if(g_settings.getBool("creative_mode") == false)
                        {
                        
                                // Skip if inventory has no free space
@@ -1387,19 +1594,23 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                        block->removeObject(id);
                }
        }
-       else if(command == TOSERVER_CLICK_GROUND)
+       else if(command == TOSERVER_GROUND_ACTION)
        {
                if(datasize < 17)
                        return;
                /*
                        length: 17
                        [0] u16 command
-                       [2] u8 button (0=left, 1=right)
+                       [2] u8 action
                        [3] v3s16 nodepos_undersurface
                        [9] v3s16 nodepos_abovesurface
                        [15] u16 item
+                       actions:
+                       0: start digging
+                       1: place block
+                       2: stop digging (all parameters ignored)
                */
-               u8 button = readU8(&data[2]);
+               u8 action = readU8(&data[2]);
                v3s16 p_under;
                p_under.X = readS16(&data[3]);
                p_under.Y = readS16(&data[5]);
@@ -1413,60 +1624,67 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                //TODO: Check that target is reasonably close
                
                /*
-                       Left button digs ground
+                       0: start digging
                */
-               if(button == 0)
+               if(action == 0)
                {
 
-                       core::map<v3s16, MapBlock*> modified_blocks;
-
-                       u8 material;
+                       u8 content;
 
                        try
                        {
-                               // Get material at position
-                               material = m_env.getMap().getNode(p_under).d;
-                               // If it's air, do nothing
-                               if(material == MATERIAL_AIR)
+                               // Get content at position
+                               content = m_env.getMap().getNode(p_under).d;
+                               // If it's not diggable, do nothing
+                               if(content_diggable(content) == false)
                                {
                                        return;
                                }
-                               // Otherwise remove it
-                               m_env.getMap().removeNodeAndUpdate(p_under, modified_blocks);
                        }
                        catch(InvalidPositionException &e)
                        {
-                               derr_server<<"Server: Ignoring REMOVENODE: Node not found"
+                               derr_server<<"Server: Not starting digging: Node not found"
                                                <<std::endl;
                                return;
                        }
                        
+                       /*
+                               Set stuff in RemoteClient
+                       */
+                       RemoteClient *client = getClient(peer->id);
+                       JMutexAutoLock(client->m_dig_mutex);
+                       client->m_dig_tool_item = 0;
+                       client->m_dig_position = p_under;
+                       float dig_time = 0.5;
+                       if(content == CONTENT_STONE)
+                       {
+                               dig_time = 1.5;
+                       }
+                       else if(content == CONTENT_TORCH)
+                       {
+                               dig_time = 0.0;
+                       }
+                       client->m_dig_time_remaining = dig_time;
+                       
                        // Reset build time counter
                        getClient(peer->id)->m_time_from_building.set(0.0);
                        
-                       // Create packet
-                       u32 replysize = 8;
-                       SharedBuffer<u8> reply(replysize);
-                       writeU16(&reply[0], TOCLIENT_REMOVENODE);
-                       writeS16(&reply[2], p_under.X);
-                       writeS16(&reply[4], p_under.Y);
-                       writeS16(&reply[6], p_under.Z);
-                       // Send as reliable
-                       m_con.SendToAll(0, reply, true);
-                       
-                       if(m_creative_mode == false)
-                       {
-                               // Add to inventory and send inventory
-                               InventoryItem *item = new MaterialItem(material, 1);
-                               player->inventory.addItem(item);
-                               SendInventory(player->peer_id);
-                       }
+               } // action == 0
 
-               } // button == 0
                /*
-                       Right button places blocks and stuff
+                       2: stop digging
                */
-               else if(button == 1)
+               else if(action == 2)
+               {
+                       RemoteClient *client = getClient(peer->id);
+                       JMutexAutoLock digmutex(client->m_dig_mutex);
+                       client->m_dig_tool_item = -1;
+               }
+
+               /*
+                       1: place block
+               */
+               else if(action == 1)
                {
 
                        // Get item
@@ -1481,19 +1699,11 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                        */
                        if(std::string("MaterialItem") == item->getName())
                        {
-                               MaterialItem *mitem = (MaterialItem*)item;
-                               
-                               MapNode n;
-                               n.d = mitem->getMaterial();
-
                                try{
-                                       // Don't add a node if there isn't air
+                                       // Don't add a node if this is not a free space
                                        MapNode n2 = m_env.getMap().getNode(p_over);
-                                       if(n2.d != MATERIAL_AIR)
+                                       if(content_buildable_to(n2.d) == false)
                                                return;
-
-                                       core::map<v3s16, MapBlock*> modified_blocks;
-                                       m_env.getMap().addNodeAndUpdate(p_over, n, modified_blocks);
                                }
                                catch(InvalidPositionException &e)
                                {
@@ -1505,17 +1715,14 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                                // Reset build time counter
                                getClient(peer->id)->m_time_from_building.set(0.0);
                                
-                               if(m_creative_mode == false)
-                               {
-                                       // Remove from inventory and send inventory
-                                       if(mitem->getCount() == 1)
-                                               player->inventory.deleteItem(item_i);
-                                       else
-                                               mitem->remove(1);
-                                       // Send inventory
-                                       SendInventory(peer_id);
-                               }
-                               
+                               // Create node data
+                               MaterialItem *mitem = (MaterialItem*)item;
+                               MapNode n;
+                               n.d = mitem->getMaterial();
+                               if(content_directional(n.d))
+                                       n.dir = packDir(p_under - p_over);
+
+#if 1
                                // Create packet
                                u32 replysize = 8 + MapNode::serializedLength(peer_ser_ver);
                                SharedBuffer<u8> reply(replysize);
@@ -1526,6 +1733,95 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                                n.serialize(&reply[8], peer_ser_ver);
                                // Send as reliable
                                m_con.SendToAll(0, reply, true);
+                               
+                               /*
+                                       Handle inventory
+                               */
+                               if(g_settings.getBool("creative_mode") == false)
+                               {
+                                       // Remove from inventory and send inventory
+                                       if(mitem->getCount() == 1)
+                                               player->inventory.deleteItem(item_i);
+                                       else
+                                               mitem->remove(1);
+                                       // Send inventory
+                                       SendInventory(peer_id);
+                               }
+                               
+                               /*
+                                       Add node.
+
+                                       This takes some time so it is done after the quick stuff
+                               */
+                               core::map<v3s16, MapBlock*> modified_blocks;
+                               m_env.getMap().addNodeAndUpdate(p_over, n, modified_blocks);
+#endif
+#if 0
+                               /*
+                                       Handle inventory
+                               */
+                               if(g_settings.getBool("creative_mode") == false)
+                               {
+                                       // Remove from inventory and send inventory
+                                       if(mitem->getCount() == 1)
+                                               player->inventory.deleteItem(item_i);
+                                       else
+                                               mitem->remove(1);
+                                       // Send inventory
+                                       SendInventory(peer_id);
+                               }
+
+                               /*
+                                       Add node.
+
+                                       This takes some time so it is done after the quick stuff
+                               */
+                               core::map<v3s16, MapBlock*> modified_blocks;
+                               m_env.getMap().addNodeAndUpdate(p_over, n, modified_blocks);
+
+                               /*
+                                       Set the modified blocks unsent for all the clients
+                               */
+                               
+                               //JMutexAutoLock lock2(m_con_mutex);
+
+                               for(core::map<u16, RemoteClient*>::Iterator
+                                               i = m_clients.getIterator();
+                                               i.atEnd() == false; i++)
+                               {
+                                       RemoteClient *client = i.getNode()->getValue();
+                                       
+                                       if(modified_blocks.size() > 0)
+                                       {
+                                               // Remove block from sent history
+                                               client->SetBlocksNotSent(modified_blocks);
+                                       }
+                               }
+#endif
+                               
+                               /*
+                                       Update water
+                               */
+                               
+                               // Update water pressure around modification
+                               // This also adds it to m_flow_active_nodes if appropriate
+
+                               MapVoxelManipulator v(&m_env.getMap());
+                               v.m_disable_water_climb =
+                                               g_settings.getBool("disable_water_climb");
+                               
+                               VoxelArea area(p_over-v3s16(1,1,1), p_over+v3s16(1,1,1));
+
+                               try
+                               {
+                                       v.updateAreaWaterPressure(area, m_flow_active_nodes);
+                               }
+                               catch(ProcessingLimitException &e)
+                               {
+                                       dstream<<"Processing limit reached (1)"<<std::endl;
+                               }
+                               
+                               v.blitBack(modified_blocks);
                        }
                        /*
                                Handle block object items
@@ -1575,7 +1871,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 
                                //dout_server<<"Placed object"<<std::endl;
 
-                               if(m_creative_mode == false)
+                               if(g_settings.getBool("creative_mode") == false)
                                {
                                        // Remove from inventory and send inventory
                                        player->inventory.deleteItem(item_i);
@@ -1584,16 +1880,17 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                                }
                        }
 
-               } // button == 1
+               } // action == 1
                /*
-                       Catch invalid buttons
+                       Catch invalid actions
                */
                else
                {
-                       derr_server<<"WARNING: Server: Invalid button "
-                                       <<button<<std::endl;
+                       derr_server<<"WARNING: Server: Invalid action "
+                                       <<action<<std::endl;
                }
        }
+#if 0
        else if(command == TOSERVER_RELEASE)
        {
                if(datasize < 3)
@@ -1603,8 +1900,9 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                        [0] u16 command
                        [2] u8 button
                */
-               //TODO
+               dstream<<"TOSERVER_RELEASE ignored"<<std::endl;
        }
+#endif
        else if(command == TOSERVER_SIGNTEXT)
        {
                /*
@@ -1704,6 +2002,9 @@ void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver)
        writeS16(&reply[4], p.Y);
        writeS16(&reply[6], p.Z);
        memcpy(&reply[8], *blockdata, blockdata.getSize());
+
+       /*dstream<<"Sending block ("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+                       <<":  \tpacket size: "<<replysize<<std::endl;*/
        
        /*
                Send packet
@@ -1711,107 +2012,6 @@ void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver)
        m_con.Send(peer_id, 1, reply, true);
 }
 
-/*void Server::SendBlock(u16 peer_id, MapBlock *block, u8 ver)
-{
-       JMutexAutoLock conlock(m_con_mutex);
-       
-       SendBlockNoLock(peer_id, block, ver);
-}*/
-
-#if 0
-void Server::SendSectorMeta(u16 peer_id, core::list<v2s16> ps, u8 ver)
-{
-       DSTACK(__FUNCTION_NAME);
-       dstream<<"Server sending sector meta of "
-                       <<ps.getSize()<<" sectors"<<std::endl;
-
-       core::list<v2s16>::Iterator i = ps.begin();
-       core::list<v2s16> sendlist;
-       for(;;)
-       {
-               if(sendlist.size() == 255 || i == ps.end())
-               {
-                       if(sendlist.size() == 0)
-                               break;
-                       /*
-                               [0] u16 command
-                               [2] u8 sector count
-                               [3...] v2s16 pos + sector metadata
-                       */
-                       std::ostringstream os(std::ios_base::binary);
-                       u8 buf[4];
-
-                       writeU16(buf, TOCLIENT_SECTORMETA);
-                       os.write((char*)buf, 2);
-
-                       writeU8(buf, sendlist.size());
-                       os.write((char*)buf, 1);
-
-                       for(core::list<v2s16>::Iterator
-                                       j = sendlist.begin();
-                                       j != sendlist.end(); j++)
-                       {
-                               // Write position
-                               writeV2S16(buf, *j);
-                               os.write((char*)buf, 4);
-                               
-                               /*
-                                       Write ClientMapSector metadata
-                               */
-
-                               /*
-                                       [0] u8 serialization version
-                                       [1] s16 corners[0]
-                                       [3] s16 corners[1]
-                                       [5] s16 corners[2]
-                                       [7] s16 corners[3]
-                                       size = 9
-                                       
-                                       In which corners are in these positions
-                                       v2s16(0,0),
-                                       v2s16(1,0),
-                                       v2s16(1,1),
-                                       v2s16(0,1),
-                               */
-
-                               // Write version
-                               writeU8(buf, ver);
-                               os.write((char*)buf, 1);
-
-                               // Write corners
-                               // TODO: Get real values
-                               s16 corners[4];
-                               ((ServerMap&)m_env.getMap()).getSectorCorners(*j, corners);
-
-                               writeS16(buf, corners[0]);
-                               os.write((char*)buf, 2);
-                               writeS16(buf, corners[1]);
-                               os.write((char*)buf, 2);
-                               writeS16(buf, corners[2]);
-                               os.write((char*)buf, 2);
-                               writeS16(buf, corners[3]);
-                               os.write((char*)buf, 2);
-                       }
-
-                       SharedBuffer<u8> data((u8*)os.str().c_str(), os.str().size());
-
-                       /*dstream<<"Server::SendSectorMeta(): sending packet"
-                                       " with "<<sendlist.size()<<" sectors"<<std::endl;*/
-
-                       m_con.Send(peer_id, 1, data, true);
-
-                       if(i == ps.end())
-                               break;
-
-                       sendlist.clear();
-               }
-
-               sendlist.push_back(*i);
-               i++;
-       }
-}
-#endif
-
 core::list<PlayerInfo> Server::getPlayerInfo()
 {
        DSTACK(__FUNCTION_NAME);
@@ -1882,27 +2082,32 @@ void Server::peerAdded(con::Peer *peer)
                // The player shouldn't already exist
                assert(player == NULL);
 
-               player = new RemotePlayer();
+               player = new ServerRemotePlayer();
                player->peer_id = peer->id;
 
                /*
                        Set player position
                */
-
+               
+               // We're going to throw the player to this position
+               //v2s16 nodepos(29990,29990);
+               //v2s16 nodepos(9990,9990);
+               v2s16 nodepos(0,0);
+               v2s16 sectorpos = getNodeSectorPos(nodepos);
                // Get zero sector (it could have been unloaded to disk)
-               m_env.getMap().emergeSector(v2s16(0,0));
+               m_env.getMap().emergeSector(sectorpos);
                // Get ground height at origin
-               f32 groundheight = m_env.getMap().getGroundHeight(v2s16(0,0), true);
-               // The zero sector should have been generated
+               f32 groundheight = m_env.getMap().getGroundHeight(nodepos, true);
+               // The sector should have been generated -> groundheight exists
                assert(groundheight > GROUNDHEIGHT_VALID_MINVALUE);
                // Don't go underwater
                if(groundheight < WATER_LEVEL)
                        groundheight = WATER_LEVEL;
 
                player->setPosition(intToFloat(v3s16(
-                               0,
+                               nodepos.X,
                                groundheight + 1,
-                               0
+                               nodepos.Y
                )));
 
                /*
@@ -1915,12 +2120,16 @@ void Server::peerAdded(con::Peer *peer)
                        Add stuff to inventory
                */
                
-               if(m_creative_mode)
+               if(g_settings.getBool("creative_mode"))
                {
                        // Give all materials
-                       assert(USEFUL_MATERIAL_COUNT <= PLAYER_INVENTORY_SIZE);
-                       for(u16 i=0; i<USEFUL_MATERIAL_COUNT; i++)
+                       assert(USEFUL_CONTENT_COUNT <= PLAYER_INVENTORY_SIZE);
+                       for(u16 i=0; i<USEFUL_CONTENT_COUNT; i++)
                        {
+                               // Skip some materials
+                               if(i == CONTENT_OCEAN)
+                                       continue;
+
                                InventoryItem *item = new MaterialItem(i, 1);
                                player->inventory.addItem(item);
                        }
@@ -1930,12 +2139,12 @@ void Server::peerAdded(con::Peer *peer)
                                bool r = player->inventory.addItem(item);
                                assert(r == true);
                        }
-                       // Rat
+                       /*// Rat
                        {
                                InventoryItem *item = new MapBlockObjectItem("Rat");
                                bool r = player->inventory.addItem(item);
                                assert(r == true);
-                       }
+                       }*/
                }
                else
                {
@@ -2077,10 +2286,12 @@ void Server::SendInventory(u16 peer_id)
 void Server::SendBlocks(float dtime)
 {
        DSTACK(__FUNCTION_NAME);
-       //dstream<<"Server::SendBlocks(): BEGIN"<<std::endl;
 
        JMutexAutoLock envlock(m_env_mutex);
-       JMutexAutoLock conlock(m_con_mutex);
+
+       core::array<PrioritySortedBlockTransfer> queue;
+
+       s32 total_sending = 0;
 
        for(core::map<u16, RemoteClient*>::Iterator
                i = m_clients.getIterator();
@@ -2088,19 +2299,52 @@ void Server::SendBlocks(float dtime)
        {
                RemoteClient *client = i.getNode()->getValue();
                assert(client->peer_id == i.getNode()->getKey());
+
+               total_sending += client->SendingCount();
                
                if(client->serialization_version == SER_FMT_VER_INVALID)
                        continue;
-
-               //dstream<<"Server::SendBlocks(): sending blocks for client "<<client->peer_id<<std::endl;
                
-               //u16 peer_id = client->peer_id;
-               client->SendBlocks(this, dtime);
+               client->GetNextBlocks(this, dtime, queue);
        }
 
-       //dstream<<"Server::SendBlocks(): END"<<std::endl;
+       // Sort.
+       // Lowest priority number comes first.
+       // Lowest is most important.
+       queue.sort();
+
+       JMutexAutoLock conlock(m_con_mutex);
+
+       for(u32 i=0; i<queue.size(); i++)
+       {
+               //TODO: Calculate limit dynamically
+               if(total_sending >= g_settings.getS32
+                               ("max_simultaneous_block_sends_server_total"))
+                       break;
+               
+               PrioritySortedBlockTransfer q = queue[i];
+
+               MapBlock *block = NULL;
+               try
+               {
+                       block = m_env.getMap().getBlockNoCreate(q.pos);
+               }
+               catch(InvalidPositionException &e)
+               {
+                       continue;
+               }
+
+               RemoteClient *client = getClient(q.peer_id);
+
+               SendBlockNoLock(q.peer_id, block, client->serialization_version);
+
+               client->SentBlock(q.pos);
+
+               total_sending++;
+       }
 }
 
+
 RemoteClient* Server::getClient(u16 peer_id)
 {
        DSTACK(__FUNCTION_NAME);
@@ -2112,4 +2356,27 @@ RemoteClient* Server::getClient(u16 peer_id)
        return n->getValue();
 }
 
+void Server::UpdateBlockWaterPressure(MapBlock *block,
+                       core::map<v3s16, MapBlock*> &modified_blocks)
+{
+       MapVoxelManipulator v(&m_env.getMap());
+       v.m_disable_water_climb =
+                       g_settings.getBool("disable_water_climb");
+       
+       VoxelArea area(block->getPosRelative(),
+                       block->getPosRelative() + v3s16(1,1,1)*(MAP_BLOCKSIZE-1));
+
+       try
+       {
+               v.updateAreaWaterPressure(area, m_flow_active_nodes);
+       }
+       catch(ProcessingLimitException &e)
+       {
+               dstream<<"Processing limit reached (1)"<<std::endl;
+       }
+       
+       v.blitBack(modified_blocks);
+}
+       
+