]> git.lizzy.rs Git - dragonfireclient.git/blobdiff - src/server.cpp
Merge pull request #48 from Oblomov/master
[dragonfireclient.git] / src / server.cpp
index 89359cd5ed2612d62f45687148448746beead6b6..8a19306a9a55b7c1aeeaf12274178f5248494acc 100644 (file)
@@ -1,6 +1,6 @@
 /*
 Minetest-c55
-Copyright (C) 2010 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2010-2011 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
@@ -28,9 +28,42 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "voxel.h"
 #include "materials.h"
 #include "mineral.h"
+#include "config.h"
+#include "servercommand.h"
+#include "filesys.h"
+#include "content_mapnode.h"
+#include "content_craft.h"
+#include "content_nodemeta.h"
+#include "mapblock.h"
+#include "serverobject.h"
 
 #define BLOCK_EMERGE_FLAG_FROMDISK (1<<0)
 
+class MapEditEventIgnorer
+{
+public:
+       MapEditEventIgnorer(bool *flag):
+               m_flag(flag)
+       {
+               if(*m_flag == false)
+                       *m_flag = true;
+               else
+                       m_flag = NULL;
+       }
+
+       ~MapEditEventIgnorer()
+       {
+               if(m_flag)
+               {
+                       assert(*m_flag);
+                       *m_flag = false;
+               }
+       }
+       
+private:
+       bool *m_flag;
+};
+
 void * ServerThread::Thread()
 {
        ThreadStarted();
@@ -72,10 +105,10 @@ void * EmergeThread::Thread()
 
        DSTACK(__FUNCTION_NAME);
 
-       //bool debug=false;
-       
        BEGIN_DEBUG_EXCEPTION_HANDLER
 
+       bool enable_mapgen_debug_info = g_settings.getBool("enable_mapgen_debug_info");
+       
        /*
                Get block info from queue, emerge them and send them
                to clients.
@@ -122,7 +155,7 @@ void * EmergeThread::Thread()
                        Also decrement the emerge queue count in clients.
                */
 
-               bool optional = true;
+               bool only_from_disk = true;
 
                {
                        core::map<u16, u8>::Iterator i;
@@ -133,55 +166,25 @@ void * EmergeThread::Thread()
                                // Check flags
                                u8 flags = i.getNode()->getValue();
                                if((flags & BLOCK_EMERGE_FLAG_FROMDISK) == false)
-                                       optional = false;
+                                       only_from_disk = false;
                                
                        }
                }
-
-               /*dstream<<"EmergeThread: p="
-                               <<"("<<p.X<<","<<p.Y<<","<<p.Z<<") "
-                               <<"optional="<<optional<<std::endl;*/
+               
+               if(enable_mapgen_debug_info)
+                       dstream<<"EmergeThread: p="
+                                       <<"("<<p.X<<","<<p.Y<<","<<p.Z<<") "
+                                       <<"only_from_disk="<<only_from_disk<<std::endl;
                
                ServerMap &map = ((ServerMap&)m_server->m_env.getMap());
                        
-               core::map<v3s16, MapBlock*> changed_blocks;
-               core::map<v3s16, MapBlock*> lighting_invalidated_blocks;
+               //core::map<v3s16, MapBlock*> changed_blocks;
+               //core::map<v3s16, MapBlock*> lighting_invalidated_blocks;
 
                MapBlock *block = NULL;
                bool got_block = true;
                core::map<v3s16, MapBlock*> modified_blocks;
                
-               bool only_from_disk = false;
-               
-               if(optional)
-                       only_from_disk = true;
-
-               v2s16 chunkpos = map.sector_to_chunk(p2d);
-
-               bool generate_chunk = false;
-               if(only_from_disk == false)
-               {
-                       JMutexAutoLock envlock(m_server->m_env_mutex);
-                       if(map.chunkNonVolatile(chunkpos) == false)
-                               generate_chunk = true;
-               }
-               if(generate_chunk)
-               {
-                       ChunkMakeData data;
-                       
-                       {
-                               JMutexAutoLock envlock(m_server->m_env_mutex);
-                               map.initChunkMake(data, chunkpos);
-                       }
-
-                       makeChunk(&data);
-
-                       {
-                               JMutexAutoLock envlock(m_server->m_env_mutex);
-                               map.finishChunkMake(data, changed_blocks);
-                       }
-               }
-       
                /*
                        Fetch block from map or generate a single block
                */
@@ -190,36 +193,66 @@ void * EmergeThread::Thread()
                        
                        // Load sector if it isn't loaded
                        if(map.getSectorNoGenerateNoEx(p2d) == NULL)
-                               map.loadSectorFull(p2d);
+                               //map.loadSectorFull(p2d);
+                               map.loadSectorMeta(p2d);
 
                        block = map.getBlockNoCreateNoEx(p);
-                       if(!block || block->isDummy())
+                       if(!block || block->isDummy() || !block->isGenerated())
                        {
-                               if(only_from_disk)
+                               if(enable_mapgen_debug_info)
+                                       dstream<<"EmergeThread: not in memory, loading"<<std::endl;
+
+                               // Get, load or create sector
+                               /*ServerMapSector *sector =
+                                               (ServerMapSector*)map.createSector(p2d);*/
+
+                               // Load/generate block
+
+                               /*block = map.emergeBlock(p, sector, changed_blocks,
+                                               lighting_invalidated_blocks);*/
+
+                               block = map.loadBlock(p);
+                               
+                               if(only_from_disk == false)
+                               {
+                                       if(block == NULL || block->isGenerated() == false)
+                                       {
+                                               if(enable_mapgen_debug_info)
+                                                       dstream<<"EmergeThread: generating"<<std::endl;
+                                               block = map.generateBlock(p, modified_blocks);
+                                       }
+                               }
+
+                               if(enable_mapgen_debug_info)
+                                       dstream<<"EmergeThread: ended up with: "
+                                                       <<analyze_block(block)<<std::endl;
+
+                               if(block == NULL)
                                {
                                        got_block = false;
                                }
                                else
                                {
-                                       // Get, load or create sector
-                                       ServerMapSector *sector =
-                                                       (ServerMapSector*)map.createSector(p2d);
-                                       // Generate block
-                                       block = map.generateBlock(p, block, sector, changed_blocks,
-                                                       lighting_invalidated_blocks);
-                                       if(block == NULL)
-                                               got_block = false;
+                                       /*
+                                               Ignore map edit events, they will not need to be
+                                               sent to anybody because the block hasn't been sent
+                                               to anybody
+                                       */
+                                       MapEditEventIgnorer ign(&m_server->m_ignore_map_edit_events);
+                                       
+                                       // Activate objects and stuff
+                                       m_server->m_env.activateBlock(block, 3600);
                                }
                        }
                        else
                        {
-                               if(block->getLightingExpired()){
+                               /*if(block->getLightingExpired()){
                                        lighting_invalidated_blocks[block->getPos()] = block;
-                               }
+                               }*/
                        }
 
                        // TODO: Some additional checking and lighting updating,
-                       // see emergeBlock
+                       //       see emergeBlock
                }
 
                {//envlock
@@ -231,7 +264,8 @@ void * EmergeThread::Thread()
                                Collect a list of blocks that have been modified in
                                addition to the fetched one.
                        */
-                       
+
+#if 0
                        if(lighting_invalidated_blocks.size() > 0)
                        {
                                /*dstream<<"lighting "<<lighting_invalidated_blocks.size()
@@ -252,11 +286,12 @@ void * EmergeThread::Thread()
                                MapBlock *block = i.getNode()->getValue();
                                modified_blocks.insert(block->getPos(), block);
                        }
+#endif
                }
                // If we got no block, there should be no invalidated blocks
                else
                {
-                       assert(lighting_invalidated_blocks.size() == 0);
+                       //assert(lighting_invalidated_blocks.size() == 0);
                }
 
                }//envlock
@@ -305,8 +340,18 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
 {
        DSTACK(__FUNCTION_NAME);
        
+       /*u32 timer_result;
+       TimeTaker timer("RemoteClient::GetNextBlocks", &timer_result);*/
+       
        // Increment timers
-       m_nearest_unsent_reset_timer += dtime;
+       m_nothing_to_send_pause_timer -= dtime;
+       
+       if(m_nothing_to_send_pause_timer >= 0)
+       {
+               // Keep this reset
+               m_nearest_unsent_reset_timer = 0;
+               return;
+       }
 
        // Won't send anything if already sending
        if(m_blocks_sending.size() >= g_settings.getU16
@@ -316,14 +361,21 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
                return;
        }
 
+       //TimeTaker timer("RemoteClient::GetNextBlocks");
+       
        Player *player = server->m_env.getPlayer(peer_id);
 
        assert(player != NULL);
 
        v3f playerpos = player->getPosition();
        v3f playerspeed = player->getSpeed();
+       v3f playerspeeddir(0,0,0);
+       if(playerspeed.getLength() > 1.0*BS)
+               playerspeeddir = playerspeed / playerspeed.getLength();
+       // Predict to next block
+       v3f playerpos_predicted = playerpos + playerspeeddir*MAP_BLOCKSIZE*BS;
 
-       v3s16 center_nodepos = floatToInt(playerpos, BS);
+       v3s16 center_nodepos = floatToInt(playerpos_predicted, BS);
 
        v3s16 center = getNodeBlockPos(center_nodepos);
        
@@ -334,11 +386,12 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
        camera_dir.rotateYZBy(player->getPitch());
        camera_dir.rotateXZBy(player->getYaw());
 
+       /*dstream<<"camera_dir=("<<camera_dir.X<<","<<camera_dir.Y<<","
+                       <<camera_dir.Z<<")"<<std::endl;*/
+
        /*
                Get the starting value of the block finder radius.
        */
-       s16 last_nearest_unsent_d;
-       s16 d_start;
                
        if(m_last_center != center)
        {
@@ -348,21 +401,28 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
 
        /*dstream<<"m_nearest_unsent_reset_timer="
                        <<m_nearest_unsent_reset_timer<<std::endl;*/
-       if(m_nearest_unsent_reset_timer > 5.0)
+                       
+       // This has to be incremented only when the nothing to send pause
+       // is not active
+       m_nearest_unsent_reset_timer += dtime;
+       
+       // Reset periodically to avoid possible bugs or other mishaps
+       if(m_nearest_unsent_reset_timer > 10.0)
        {
                m_nearest_unsent_reset_timer = 0;
                m_nearest_unsent_d = 0;
-               //dstream<<"Resetting m_nearest_unsent_d"<<std::endl;
+               /*dstream<<"Resetting m_nearest_unsent_d for "
+                               <<server->getPlayerName(peer_id)<<std::endl;*/
        }
 
-       last_nearest_unsent_d = m_nearest_unsent_d;
-       
-       d_start = m_nearest_unsent_d;
+       //s16 last_nearest_unsent_d = m_nearest_unsent_d;
+       s16 d_start = m_nearest_unsent_d;
+
+       //dstream<<"d_start="<<d_start<<std::endl;
 
-       u16 maximum_simultaneous_block_sends_setting = g_settings.getU16
+       u16 max_simul_sends_setting = g_settings.getU16
                        ("max_simultaneous_block_sends_per_client");
-       u16 maximum_simultaneous_block_sends = 
-                       maximum_simultaneous_block_sends_setting;
+       u16 max_simul_sends_usually = max_simul_sends_setting;
 
        /*
                Check the time from last addNode/removeNode.
@@ -373,10 +433,13 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
        if(m_time_from_building < g_settings.getFloat(
                                "full_block_send_enable_min_time_from_building"))
        {
-               maximum_simultaneous_block_sends
+               max_simul_sends_usually
                        = LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS;
        }
        
+       /*
+               Number of blocks sending + number of blocks selected for sending
+       */
        u32 num_blocks_selected = m_blocks_sending.size();
        
        /*
@@ -391,9 +454,22 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
        s16 d_max = g_settings.getS16("max_block_send_distance");
        s16 d_max_gen = g_settings.getS16("max_block_generate_distance");
        
+       // Don't loop very much at a time
+       if(d_max > d_start+1)
+               d_max = d_start+1;
+       /*if(d_max_gen > d_start+2)
+               d_max_gen = d_start+2;*/
+       
        //dstream<<"Starting from "<<d_start<<std::endl;
 
-       for(s16 d = d_start; d <= d_max; d++)
+       bool sending_something = false;
+
+       bool no_blocks_found_for_sending = true;
+
+       bool queue_is_full = false;
+       
+       s16 d;
+       for(d = d_start; d <= d_max; d++)
        {
                //dstream<<"RemoteClient::SendBlocks(): d="<<d<<std::endl;
                
@@ -403,11 +479,11 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
                        update our d to it.
                        Else update m_nearest_unsent_d
                */
-               if(m_nearest_unsent_d != last_nearest_unsent_d)
+               /*if(m_nearest_unsent_d != last_nearest_unsent_d)
                {
                        d = m_nearest_unsent_d;
                        last_nearest_unsent_d = m_nearest_unsent_d;
-               }
+               }*/
 
                /*
                        Get the border/face dot coordinates of a "d-radiused"
@@ -429,25 +505,21 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
                                Also, don't send blocks that are already flying.
                        */
                        
-                       u16 maximum_simultaneous_block_sends_now =
-                                       maximum_simultaneous_block_sends;
+                       // Start with the usual maximum
+                       u16 max_simul_dynamic = max_simul_sends_usually;
                        
+                       // If block is very close, allow full maximum
                        if(d <= BLOCK_SEND_DISABLE_LIMITS_MAX_D)
-                       {
-                               maximum_simultaneous_block_sends_now =
-                                               maximum_simultaneous_block_sends_setting;
-                       }
+                               max_simul_dynamic = max_simul_sends_setting;
 
-                       // Limit is dynamically lowered when building
-                       if(num_blocks_selected
-                                       >= maximum_simultaneous_block_sends_now)
+                       // Don't select too many blocks for sending
+                       if(num_blocks_selected >= max_simul_dynamic)
                        {
-                               /*dstream<<"Not sending more blocks. Queue full. "
-                                               <<m_blocks_sending.size()
-                                               <<std::endl;*/
-                               goto queue_full;
+                               queue_is_full = true;
+                               goto queue_full_break;
                        }
-
+                       
+                       // Don't send blocks that are currently being transferred
                        if(m_blocks_sending.find(p) != NULL)
                                continue;
                
@@ -475,40 +547,40 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
                                        continue;
                        }
 
-#if 0
+#if 1
                        /*
                                If block is far away, don't generate it unless it is
-                               near ground level
-
-                               NOTE: We can't know the ground level this way with the
-                               new generator.
+                               near ground level.
                        */
-                       if(d > 4)
+                       if(d >= 4)
                        {
-                               v2s16 p2d(p.X, p.Z);
-                               MapSector *sector = NULL;
-                               try
-                               {
-                                       sector = server->m_env.getMap().getSectorNoGenerate(p2d);
-                               }
-                               catch(InvalidPositionException &e)
-                               {
-                               }
-
-                               if(sector != NULL)
-                               {
-                                       // Get center ground height in nodes
-                                       f32 gh = sector->getGroundHeight(
-                                                       v2s16(MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2));
-                                       // Block center y in nodes
-                                       f32 y = (f32)(p.Y * MAP_BLOCKSIZE + MAP_BLOCKSIZE/2);
-                                       // If differs a lot, don't generate
-                                       if(fabs(gh - y) > MAP_BLOCKSIZE*2)
-                                               generate = false;
-                               }
+       #if 1
+                               // Block center y in nodes
+                               f32 y = (f32)(p.Y * MAP_BLOCKSIZE + MAP_BLOCKSIZE/2);
+                               // Don't generate if it's very high or very low
+                               if(y < -64 || y > 64)
+                                       generate = false;
+       #endif
+       #if 0
+                               v2s16 p2d_nodes_center(
+                                       MAP_BLOCKSIZE*p.X,
+                                       MAP_BLOCKSIZE*p.Z);
+                               
+                               // Get ground height in nodes
+                               s16 gh = server->m_env.getServerMap().findGroundLevel(
+                                               p2d_nodes_center);
+
+                               // If differs a lot, don't generate
+                               if(fabs(gh - y) > MAP_BLOCKSIZE*2)
+                                       generate = false;
+                                       // Actually, don't even send it
+                                       //continue;
+       #endif
                        }
 #endif
 
+                       //dstream<<"d="<<d<<std::endl;
+                       
                        /*
                                Don't generate or send if not in sight
                        */
@@ -523,7 +595,9 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
                        */
                        {
                                if(m_blocks_sent.find(p) != NULL)
+                               {
                                        continue;
+                               }
                        }
 
                        /*
@@ -535,11 +609,17 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
                        bool block_is_invalid = false;
                        if(block != NULL)
                        {
+                               // Reset usage timer, this block will be of use in the future.
+                               block->resetUsageTimer();
+
+                               // Block is dummy if data doesn't exist.
+                               // It means it has been not found from disk and not generated
                                if(block->isDummy())
                                {
                                        surely_not_found_on_disk = true;
                                }
-
+                               
+                               // Block is valid if lighting is up-to-date and data exists
                                if(block->isValid() == false)
                                {
                                        block_is_invalid = true;
@@ -549,12 +629,30 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
                                {
                                        block_is_invalid = true;
                                }*/
-                               
+
+#if 0
                                v2s16 p2d(p.X, p.Z);
                                ServerMap *map = (ServerMap*)(&server->m_env.getMap());
                                v2s16 chunkpos = map->sector_to_chunk(p2d);
                                if(map->chunkNonVolatile(chunkpos) == false)
                                        block_is_invalid = true;
+#endif
+                               if(block->isGenerated() == false)
+                                       block_is_invalid = true;
+#if 1
+                               /*
+                                       If block is not close, don't send it unless it is near
+                                       ground level.
+
+                                       Block is near ground level if night-time mesh
+                                       differs from day-time mesh.
+                               */
+                               if(d > 3)
+                               {
+                                       if(block->dayNightDiffed() == false)
+                                               continue;
+                               }
+#endif
                        }
 
                        /*
@@ -568,13 +666,16 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
                        }
 
                        /*
-                               Record the lowest d from which a block has been
+                               Record the lowest d from which a block has been
                                found being not sent and possibly to exist
                        */
-                       if(new_nearest_unsent_d == -1 || d < new_nearest_unsent_d)
+                       if(no_blocks_found_for_sending)
                        {
-                               new_nearest_unsent_d = d;
+                               if(generate == true)
+                                       new_nearest_unsent_d = d;
                        }
+
+                       no_blocks_found_for_sending = false;
                                        
                        /*
                                Add inexistent block to emerge queue.
@@ -584,6 +685,7 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
                                //TODO: Get value from somewhere
                                // Allow only one block in emerge queue
                                //if(server->m_emerge_queue.peerItemCount(peer_id) < 1)
+                               // Allow two blocks in queue per client
                                if(server->m_emerge_queue.peerItemCount(peer_id) < 2)
                                {
                                        //dstream<<"Adding block to emerge queue"<<std::endl;
@@ -611,14 +713,43 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
                        dest.push_back(q);
 
                        num_blocks_selected += 1;
+                       sending_something = true;
                }
        }
-queue_full:
+queue_full_break:
 
-       if(new_nearest_unsent_d != -1)
+       //dstream<<"Stopped at "<<d<<std::endl;
+       
+       if(no_blocks_found_for_sending)
        {
+               if(queue_is_full == false)
+                       new_nearest_unsent_d = d;
+       }
+
+       if(new_nearest_unsent_d != -1)
                m_nearest_unsent_d = new_nearest_unsent_d;
+
+       if(sending_something == false)
+       {
+               m_nothing_to_send_counter++;
+               if((s16)m_nothing_to_send_counter >=
+                               g_settings.getS16("max_block_send_distance"))
+               {
+                       // Pause time in seconds
+                       m_nothing_to_send_pause_timer = 1.0;
+                       /*dstream<<"nothing to send to "
+                                       <<server->getPlayerName(peer_id)
+                                       <<" (d="<<d<<")"<<std::endl;*/
+               }
+       }
+       else
+       {
+               m_nothing_to_send_counter = 0;
        }
+
+       /*timer_result = timer.stop(true);
+       if(timer_result != 0)
+               dstream<<"GetNextBlocks duration: "<<timer_result<<" (!=0)"<<std::endl;*/
 }
 
 void RemoteClient::SendObjectData(
@@ -761,7 +892,7 @@ void RemoteClient::SendObjectData(
                        */
                        if(stepped_blocks.find(p) == NULL)
                        {
-                               block->stepObjects(dtime, true, server->getDayNightRatio());
+                               block->stepObjects(dtime, true, server->m_env.getDayNightRatio());
                                stepped_blocks.insert(p, true);
                                block->setChangedFlag();
                        }
@@ -779,7 +910,9 @@ void RemoteClient::SendObjectData(
                        bos.write((char*)buf, 6);
 
                        // Write objects
-                       block->serializeObjects(bos, serialization_version);
+                       //block->serializeObjects(bos, serialization_version); // DEPRECATED
+                       // count=0
+                       writeU16(bos, 0);
 
                        blockcount++;
 
@@ -925,17 +1058,19 @@ u32 PIChecksum(core::list<PlayerInfo> &l)
 */
 
 Server::Server(
-               std::string mapsavedir
+               std::string mapsavedir,
+               std::string configpath
        ):
        m_env(new ServerMap(mapsavedir), this),
        m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this),
+       m_authmanager(mapsavedir+"/auth.txt"),
        m_thread(this),
        m_emergethread(this),
-       m_time_of_day(9000),
        m_time_counter(0),
        m_time_of_day_send_timer(0),
        m_uptime(0),
        m_mapsavedir(mapsavedir),
+       m_configpath(configpath),
        m_shutdown_requested(false),
        m_ignore_map_edit_events(false),
        m_ignore_map_edit_events_peer_id(0)
@@ -950,10 +1085,19 @@ Server::Server(
        m_con_mutex.Init();
        m_step_dtime_mutex.Init();
        m_step_dtime = 0.0;
-
+       
+       // Register us to receive map edit events
        m_env.getMap().addEventReceiver(this);
 
+       // If file exists, load environment metadata
+       if(fs::PathExists(m_mapsavedir+"/env_meta.txt"))
+       {
+               dstream<<"Server: Loading environment metadata"<<std::endl;
+               m_env.loadMeta(m_mapsavedir);
+       }
+
        // Load players
+       dstream<<"Server: Loading players"<<std::endl;
        m_env.deSerializePlayers(m_mapsavedir);
 }
 
@@ -995,6 +1139,12 @@ Server::~Server()
        */
        dstream<<"Server: Saving players"<<std::endl;
        m_env.serializePlayers(m_mapsavedir);
+
+       /*
+               Save environment metadata
+       */
+       dstream<<"Server: Saving environment metadata"<<std::endl;
+       m_env.saveMeta(m_mapsavedir);
        
        /*
                Stop threads
@@ -1077,12 +1227,16 @@ void Server::AsyncRunStep()
                dtime = m_step_dtime;
        }
        
-       // Send blocks to clients
-       SendBlocks(dtime);
+       {
+               ScopeProfiler sp(&g_profiler, "Server: selecting and sending "
+                               "blocks to clients");
+               // Send blocks to clients
+               SendBlocks(dtime);
+       }
        
        if(dtime < 0.001)
                return;
-       
+
        //dstream<<"Server steps "<<dtime<<std::endl;
        //dstream<<"Server::AsyncRunStep(): dtime="<<dtime<<std::endl;
        
@@ -1099,14 +1253,17 @@ void Server::AsyncRunStep()
        }
        
        /*
-               Update m_time_of_day
+               Update m_time_of_day and overall game time
        */
        {
+               JMutexAutoLock envlock(m_env_mutex);
+
                m_time_counter += dtime;
                f32 speed = g_settings.getFloat("time_speed") * 24000./(24.*3600);
                u32 units = (u32)(m_time_counter*speed);
                m_time_counter -= (f32)units / speed;
-               m_time_of_day.set((m_time_of_day.get() + units) % 24000);
+               
+               m_env.setTimeOfDay((m_env.getTimeOfDay() + units) % 24000);
                
                //dstream<<"Server: m_time_of_day = "<<m_time_of_day.get()<<std::endl;
 
@@ -1130,7 +1287,7 @@ void Server::AsyncRunStep()
                                //Player *player = m_env.getPlayer(client->peer_id);
                                
                                SharedBuffer<u8> data = makePacket_TOCLIENT_TIME_OF_DAY(
-                                               m_time_of_day.get());
+                                               m_env.getTimeOfDay());
                                // Send as reliable
                                m_con.Send(client->peer_id, 0, data, true);
                        }
@@ -1140,21 +1297,33 @@ void Server::AsyncRunStep()
        {
                // Process connection's timeouts
                JMutexAutoLock lock2(m_con_mutex);
+               ScopeProfiler sp(&g_profiler, "Server: connection timeout processing");
                m_con.RunTimeouts(dtime);
        }
        
        {
                // This has to be called so that the client list gets synced
                // with the peer list of the connection
+               ScopeProfiler sp(&g_profiler, "Server: peer change handling");
                handlePeerChanges();
        }
 
        {
-               // Step environment
-               // This also runs Map's timers
                JMutexAutoLock lock(m_env_mutex);
+               // Step environment
+               ScopeProfiler sp(&g_profiler, "Server: environment step");
                m_env.step(dtime);
        }
+               
+       const float map_timer_and_unload_dtime = 5.15;
+       if(m_map_timer_and_unload_interval.step(dtime, map_timer_and_unload_dtime))
+       {
+               JMutexAutoLock lock(m_env_mutex);
+               // Run Map's timers and unload unused data
+               ScopeProfiler sp(&g_profiler, "Server: map timer and unload");
+               m_env.getMap().timerUpdate(map_timer_and_unload_dtime,
+                               g_settings.getFloat("server_unload_unused_data_timeout"));
+       }
        
        /*
                Do background stuff
@@ -1169,7 +1338,9 @@ void Server::AsyncRunStep()
                m_liquid_transform_timer -= 1.00;
                
                JMutexAutoLock lock(m_env_mutex);
-               
+
+               ScopeProfiler sp(&g_profiler, "Server: liquid transform");
+
                core::map<v3s16, MapBlock*> modified_blocks;
                m_env.getMap().transformLiquids(modified_blocks);
 #if 0          
@@ -1242,10 +1413,11 @@ void Server::AsyncRunStep()
        */
        {
                //dstream<<"Server: Checking added and deleted active objects"<<std::endl;
-
                JMutexAutoLock envlock(m_env_mutex);
                JMutexAutoLock conlock(m_con_mutex);
-               
+
+               ScopeProfiler sp(&g_profiler, "Server: checking added and deleted objects");
+
                // Radius inside which objects are active
                s16 radius = 32;
 
@@ -1257,8 +1429,10 @@ void Server::AsyncRunStep()
                        Player *player = m_env.getPlayer(client->peer_id);
                        if(player==NULL)
                        {
-                               dstream<<"WARNING: "<<__FUNCTION_NAME<<": Client "<<client->peer_id
-                                               <<" has no associated player"<<std::endl;
+                               // This can happen if the client timeouts somehow
+                               /*dstream<<"WARNING: "<<__FUNCTION_NAME<<": Client "
+                                               <<client->peer_id
+                                               <<" has no associated player"<<std::endl;*/
                                continue;
                        }
                        v3s16 pos = floatToInt(player->getPosition(), BS);
@@ -1390,6 +1564,8 @@ void Server::AsyncRunStep()
                JMutexAutoLock envlock(m_env_mutex);
                JMutexAutoLock conlock(m_con_mutex);
 
+               ScopeProfiler sp(&g_profiler, "Server: sending object messages");
+
                // Key = object id
                // Value = data sent by object
                core::map<u16, core::list<ActiveObjectMessage>* > buffered_messages;
@@ -1502,33 +1678,113 @@ void Server::AsyncRunStep()
                Send queued-for-sending map edit events.
        */
        {
+               // Don't send too many at a time
+               //u32 count = 0;
+
+               // Single change sending is disabled if queue size is not small
+               bool disable_single_change_sending = false;
+               if(m_unsent_map_edit_queue.size() >= 4)
+                       disable_single_change_sending = true;
+
+               bool got_any_events = false;
+
+               // We'll log the amount of each
+               Profiler prof;
+
                while(m_unsent_map_edit_queue.size() != 0)
                {
+                       got_any_events = true;
+
                        MapEditEvent* event = m_unsent_map_edit_queue.pop_front();
+                       
+                       // Players far away from the change are stored here.
+                       // Instead of sending the changes, MapBlocks are set not sent
+                       // for them.
+                       core::list<u16> far_players;
 
                        if(event->type == MEET_ADDNODE)
                        {
-                               dstream<<"Server: MEET_ADDNODE"<<std::endl;
-                               sendAddNode(event->p, event->n, event->already_known_by_peer);
+                               //dstream<<"Server: MEET_ADDNODE"<<std::endl;
+                               prof.add("MEET_ADDNODE", 1);
+                               if(disable_single_change_sending)
+                                       sendAddNode(event->p, event->n, event->already_known_by_peer,
+                                                       &far_players, 5);
+                               else
+                                       sendAddNode(event->p, event->n, event->already_known_by_peer,
+                                                       &far_players, 30);
                        }
                        else if(event->type == MEET_REMOVENODE)
                        {
-                               dstream<<"Server: MEET_REMOVENODE"<<std::endl;
-                               sendRemoveNode(event->p, event->already_known_by_peer);
+                               //dstream<<"Server: MEET_REMOVENODE"<<std::endl;
+                               prof.add("MEET_REMOVENODE", 1);
+                               if(disable_single_change_sending)
+                                       sendRemoveNode(event->p, event->already_known_by_peer,
+                                                       &far_players, 5);
+                               else
+                                       sendRemoveNode(event->p, event->already_known_by_peer,
+                                                       &far_players, 30);
+                       }
+                       else if(event->type == MEET_BLOCK_NODE_METADATA_CHANGED)
+                       {
+                               dstream<<"Server: MEET_BLOCK_NODE_METADATA_CHANGED"<<std::endl;
+                               prof.add("MEET_BLOCK_NODE_METADATA_CHANGED", 1);
+                               setBlockNotSent(event->p);
                        }
                        else if(event->type == MEET_OTHER)
                        {
+                               prof.add("MEET_OTHER", 1);
                                dstream<<"WARNING: Server: MEET_OTHER not implemented"
                                                <<std::endl;
                        }
                        else
                        {
+                               prof.add("unknown", 1);
                                dstream<<"WARNING: Server: Unknown MapEditEvent "
                                                <<((u32)event->type)<<std::endl;
                        }
+                       
+                       /*
+                               Set blocks not sent to far players
+                       */
+                       if(far_players.size() > 0)
+                       {
+                               // Convert list format to that wanted by SetBlocksNotSent
+                               core::map<v3s16, MapBlock*> modified_blocks2;
+                               for(core::map<v3s16, bool>::Iterator
+                                               i = event->modified_blocks.getIterator();
+                                               i.atEnd()==false; i++)
+                               {
+                                       v3s16 p = i.getNode()->getKey();
+                                       modified_blocks2.insert(p,
+                                                       m_env.getMap().getBlockNoCreateNoEx(p));
+                               }
+                               // Set blocks not sent
+                               for(core::list<u16>::Iterator
+                                               i = far_players.begin();
+                                               i != far_players.end(); i++)
+                               {
+                                       u16 peer_id = *i;
+                                       RemoteClient *client = getClient(peer_id);
+                                       if(client==NULL)
+                                               continue;
+                                       client->SetBlocksNotSent(modified_blocks2);
+                               }
+                       }
 
                        delete event;
+
+                       /*// Don't send too many at a time
+                       count++;
+                       if(count >= 1 && m_unsent_map_edit_queue.size() < 100)
+                               break;*/
                }
+
+               if(got_any_events)
+               {
+                       dstream<<"Server: MapEditEvents:"<<std::endl;
+                       prof.print(dstream);
+               }
+               
        }
 
        /*
@@ -1542,40 +1798,15 @@ void Server::AsyncRunStep()
                {
                        JMutexAutoLock lock1(m_env_mutex);
                        JMutexAutoLock lock2(m_con_mutex);
+
+                       ScopeProfiler sp(&g_profiler, "Server: sending mbo positions");
+
                        SendObjectData(counter);
 
                        counter = 0.0;
                }
        }
        
-       /*
-               Step node metadata
-       */
-       {
-               //TimeTaker timer("Step node metadata");
-
-               JMutexAutoLock envlock(m_env_mutex);
-               JMutexAutoLock conlock(m_con_mutex);
-               
-               core::map<v3s16, MapBlock*> changed_blocks;
-               m_env.getMap().nodeMetadataStep(dtime, changed_blocks);
-
-               for(core::map<v3s16, MapBlock*>::Iterator
-                               i = changed_blocks.getIterator();
-                               i.atEnd() == false; i++)
-               {
-                       MapBlock *block = i.getNode()->getValue();
-
-                       for(core::map<u16, RemoteClient*>::Iterator
-                               i = m_clients.getIterator();
-                               i.atEnd()==false; i++)
-                       {
-                               RemoteClient *client = i.getNode()->getValue();
-                               client->SetBlockNotSent(block->getPos());
-                       }
-               }
-       }
-               
        /*
                Trigger emergethread (it somehow gets to a non-triggered but
                bysy state sometimes)
@@ -1591,7 +1822,7 @@ void Server::AsyncRunStep()
                }
        }
 
-       // Save map
+       // Save map, players and auth stuff
        {
                float &counter = m_savemap_timer;
                counter += dtime;
@@ -1599,25 +1830,37 @@ void Server::AsyncRunStep()
                {
                        counter = 0.0;
 
+                       ScopeProfiler sp(&g_profiler, "Server: saving stuff");
+
+                       // Auth stuff
+                       if(m_authmanager.isModified())
+                               m_authmanager.save();
+                       
+                       // Map
                        JMutexAutoLock lock(m_env_mutex);
 
-                       if(((ServerMap*)(&m_env.getMap()))->isSavingEnabled() == true)
-                       {
-                               // Save only changed parts
-                               m_env.getMap().save(true);
+                       /*// Unload unused data (delete from memory)
+                       m_env.getMap().unloadUnusedData(
+                                       g_settings.getFloat("server_unload_unused_sectors_timeout"));
+                                       */
+                       /*u32 deleted_count = m_env.getMap().unloadUnusedData(
+                                       g_settings.getFloat("server_unload_unused_sectors_timeout"));
+                                       */
 
-                               // Delete unused sectors
-                               u32 deleted_count = m_env.getMap().deleteUnusedSectors(
-                                               g_settings.getFloat("server_unload_unused_sectors_timeout"));
-                               if(deleted_count > 0)
-                               {
-                                       dout_server<<"Server: Unloaded "<<deleted_count
-                                                       <<" sectors from memory"<<std::endl;
-                               }
+                       // Save only changed parts
+                       m_env.getMap().save(true);
 
-                               // Save players
-                               m_env.serializePlayers(m_mapsavedir);
-                       }
+                       /*if(deleted_count > 0)
+                       {
+                               dout_server<<"Server: Unloaded "<<deleted_count
+                                               <<" blocks from memory"<<std::endl;
+                       }*/
+
+                       // Save players
+                       m_env.serializePlayers(m_mapsavedir);
+                       
+                       // Save environment metadata
+                       m_env.saveMeta(m_mapsavedir);
                }
        }
 }
@@ -1697,8 +1940,9 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                // [0] u16 TOSERVER_INIT
                // [2] u8 SER_FMT_VER_HIGHEST
                // [3] u8[20] player_name
+               // [23] u8[28] password <--- can be sent without this, from old versions
 
-               if(datasize < 3)
+               if(datasize < 2+1+PLAYERNAME_SIZE)
                        return;
 
                derr_server<<DTIME<<"Server: Got TOSERVER_INIT from "
@@ -1722,6 +1966,27 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                        derr_server<<DTIME<<"Server: Cannot negotiate "
                                        "serialization version with peer "
                                        <<peer_id<<std::endl;
+                       SendAccessDenied(m_con, peer_id,
+                                       L"Your client is too old (map format)");
+                       return;
+               }
+               
+               /*
+                       Read and check network protocol version
+               */
+
+               u16 net_proto_version = 0;
+               if(datasize >= 2+1+PLAYERNAME_SIZE+PASSWORD_SIZE+2)
+               {
+                       net_proto_version = readU16(&data[2+1+PLAYERNAME_SIZE+PASSWORD_SIZE]);
+               }
+
+               getClient(peer->id)->net_proto_version = net_proto_version;
+
+               if(net_proto_version == 0)
+               {
+                       SendAccessDenied(m_con, peer_id,
+                                       L"Your client is too old. Please upgrade.");
                        return;
                }
 
@@ -1730,47 +1995,112 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                */
                
                // Get player name
-               const u32 playername_size = 20;
-               char playername[playername_size];
-               for(u32 i=0; i<playername_size-1; i++)
+               char playername[PLAYERNAME_SIZE];
+               for(u32 i=0; i<PLAYERNAME_SIZE-1; i++)
                {
                        playername[i] = data[3+i];
                }
-               playername[playername_size-1] = 0;
+               playername[PLAYERNAME_SIZE-1] = 0;
                
-               // Get player
-               Player *player = emergePlayer(playername, "", peer_id);
-               //Player *player = m_env.getPlayer(peer_id);
-
-               /*{
-                       // DEBUG: Test serialization
-                       std::ostringstream test_os;
-                       player->serialize(test_os);
-                       dstream<<"Player serialization test: \""<<test_os.str()
-                                       <<"\""<<std::endl;
-                       std::istringstream test_is(test_os.str());
-                       player->deSerialize(test_is);
-               }*/
-
-               // If failed, cancel
-               if(player == NULL)
+               if(playername[0]=='\0')
                {
-                       derr_server<<DTIME<<"Server: peer_id="<<peer_id
-                                       <<": failed to emerge player"<<std::endl;
+                       derr_server<<DTIME<<"Server: Player has empty name"<<std::endl;
+                       SendAccessDenied(m_con, peer_id,
+                                       L"Empty name");
                        return;
                }
 
-               /*
-               // If a client is already connected to the player, cancel
-               if(player->peer_id != 0)
+               if(string_allowed(playername, PLAYERNAME_ALLOWED_CHARS)==false)
                {
-                       derr_server<<DTIME<<"Server: peer_id="<<peer_id
-                                       <<" tried to connect to "
-                                       "an already connected player (peer_id="
-                                       <<player->peer_id<<")"<<std::endl;
+                       derr_server<<DTIME<<"Server: Player has invalid name"<<std::endl;
+                       SendAccessDenied(m_con, peer_id,
+                                       L"Name contains unallowed characters");
                        return;
                }
-               // Set client of player
+
+               // Get password
+               char password[PASSWORD_SIZE];
+               if(datasize < 2+1+PLAYERNAME_SIZE+PASSWORD_SIZE)
+               {
+                       // old version - assume blank password
+                       password[0] = 0;
+               }
+               else
+               {
+                               for(u32 i=0; i<PASSWORD_SIZE-1; i++)
+                               {
+                                       password[i] = data[23+i];
+                               }
+                               password[PASSWORD_SIZE-1] = 0;
+               }
+               
+               std::string checkpwd;
+               if(m_authmanager.exists(playername))
+               {
+                       checkpwd = m_authmanager.getPassword(playername);
+               }
+               else
+               {
+                       checkpwd = g_settings.get("default_password");
+               }
+               
+               /*dstream<<"Server: Client gave password '"<<password
+                               <<"', the correct one is '"<<checkpwd<<"'"<<std::endl;*/
+               
+               if(password != checkpwd && m_authmanager.exists(playername))
+               {
+                       derr_server<<DTIME<<"Server: peer_id="<<peer_id
+                                       <<": supplied invalid password for "
+                                       <<playername<<std::endl;
+                       SendAccessDenied(m_con, peer_id, L"Invalid password");
+                       return;
+               }
+               
+               // Add player to auth manager
+               if(m_authmanager.exists(playername) == false)
+               {
+                       derr_server<<DTIME<<"Server: adding player "<<playername
+                                       <<" to auth manager"<<std::endl;
+                       m_authmanager.add(playername);
+                       m_authmanager.setPassword(playername, checkpwd);
+                       m_authmanager.setPrivs(playername,
+                                       stringToPrivs(g_settings.get("default_privs")));
+                       m_authmanager.save();
+               }
+
+               // Get player
+               Player *player = emergePlayer(playername, password, peer_id);
+
+
+               /*{
+                       // DEBUG: Test serialization
+                       std::ostringstream test_os;
+                       player->serialize(test_os);
+                       dstream<<"Player serialization test: \""<<test_os.str()
+                                       <<"\""<<std::endl;
+                       std::istringstream test_is(test_os.str());
+                       player->deSerialize(test_is);
+               }*/
+
+               // If failed, cancel
+               if(player == NULL)
+               {
+                       derr_server<<DTIME<<"Server: peer_id="<<peer_id
+                                       <<": failed to emerge player"<<std::endl;
+                       return;
+               }
+
+               /*
+               // If a client is already connected to the player, cancel
+               if(player->peer_id != 0)
+               {
+                       derr_server<<DTIME<<"Server: peer_id="<<peer_id
+                                       <<" tried to connect to "
+                                       "an already connected player (peer_id="
+                                       <<player->peer_id<<")"<<std::endl;
+                       return;
+               }
+               // Set client of player
                player->peer_id = peer_id;
                */
 
@@ -1794,8 +2124,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                        writeU16(&reply[0], TOCLIENT_INIT);
                        writeU8(&reply[2], deployed);
                        writeV3S16(&reply[2+1], floatToInt(player->getPosition()+v3f(0,BS/2,0), BS));
-                       //writeU64(&reply[2+1+6], m_env.getServerMap().getSeed());
-                       writeU64(&reply[2+1+6], 0); // no seed
+                       writeU64(&reply[2+1+6], m_env.getServerMap().getSeed());
                        
                        // Send as reliable
                        m_con.Send(peer_id, 0, reply, true);
@@ -1838,7 +2167,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                // Send time of day
                {
                        SharedBuffer<u8> data = makePacket_TOCLIENT_TIME_OF_DAY(
-                                       m_time_of_day.get());
+                                       m_env.getTimeOfDay());
                        m_con.Send(peer->id, 0, data, true);
                }
                
@@ -1858,6 +2187,12 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                        message += L" joined game";
                        BroadcastChatMessage(message);
                }
+               
+               // Warnings about protocol version can be issued here
+               /*if(getClient(peer->id)->net_proto_version == 0)
+               {
+                       SendChatMessage(peer_id, L"# Server: NOTE: YOUR CLIENT IS OLD AND DOES NOT WORK PROPERLY WITH THIS SERVER");
+               }*/
 
                return;
        }
@@ -1958,6 +2293,9 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                if(datasize < 13)
                        return;
 
+               if((getPlayerPrivs(player) & PRIV_BUILD) == 0)
+                       return;
+
                /*
                        [0] u16 command
                        [2] u8 button (0=left, 1=right)
@@ -2039,6 +2377,9 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                if(datasize < 7)
                        return;
 
+               if((getPlayerPrivs(player) & PRIV_BUILD) == 0)
+                       return;
+
                /*
                        length: 7
                        [0] u16 command
@@ -2059,71 +2400,92 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                        return;
                }
 
+               // Skip if object has been removed
+               if(obj->m_removed)
+                       return;
+               
                //TODO: Check that object is reasonably close
                
                // Left click, pick object up (usually)
                if(button == 0)
                {
-                       InventoryList *ilist = player->inventory.getList("main");
-                       if(g_settings.getBool("creative_mode") == false && ilist != NULL)
-                       {
+                       /*
+                               Try creating inventory item
+                       */
+                       InventoryItem *item = obj->createPickedUpItem();
                        
-                               // Skip if inventory has no free space
-                               if(ilist->getUsedSlots() == ilist->getSize())
+                       if(item)
+                       {
+                               InventoryList *ilist = player->inventory.getList("main");
+                               if(ilist != NULL)
                                {
-                                       dout_server<<"Player inventory has no free space"<<std::endl;
-                                       return;
-                               }
+                                       if(g_settings.getBool("creative_mode") == false)
+                                       {
+                                               // Skip if inventory has no free space
+                                               if(ilist->getUsedSlots() == ilist->getSize())
+                                               {
+                                                       dout_server<<"Player inventory has no free space"<<std::endl;
+                                                       return;
+                                               }
 
-                               // Skip if object has been removed
-                               if(obj->m_removed)
-                                       return;
-                               
-                               /*
-                                       Create the inventory item
-                               */
-                               InventoryItem *item = obj->createPickedUpItem();
-                               
-                               if(item)
-                               {
-                                       // Add to inventory and send inventory
-                                       ilist->addItem(item);
-                                       UpdateCrafting(player->peer_id);
-                                       SendInventory(player->peer_id);
+                                               // Add to inventory and send inventory
+                                               ilist->addItem(item);
+                                               UpdateCrafting(player->peer_id);
+                                               SendInventory(player->peer_id);
+                                       }
 
                                        // Remove object from environment
                                        obj->m_removed = true;
                                }
-                               else
-                               {
-                                       /*
-                                               Item cannot be picked up. Punch it instead.
-                                       */
+                       }
+                       else
+                       {
+                               /*
+                                       Item cannot be picked up. Punch it instead.
+                               */
 
-                                       ToolItem *titem = NULL;
-                                       std::string toolname = "";
+                               ToolItem *titem = NULL;
+                               std::string toolname = "";
 
-                                       InventoryList *mlist = player->inventory.getList("main");
-                                       if(mlist != NULL)
+                               InventoryList *mlist = player->inventory.getList("main");
+                               if(mlist != NULL)
+                               {
+                                       InventoryItem *item = mlist->getItem(item_i);
+                                       if(item && (std::string)item->getName() == "ToolItem")
                                        {
-                                               InventoryItem *item = mlist->getItem(item_i);
-                                               if(item && (std::string)item->getName() == "ToolItem")
-                                               {
-                                                       titem = (ToolItem*)item;
-                                                       toolname = titem->getToolName();
-                                               }
-                                       }
-                                       
-                                       u16 wear = obj->punch(toolname);
-                                       
-                                       if(titem)
-                                       {
-                                               bool weared_out = titem->addWear(wear);
-                                               if(weared_out)
-                                                       mlist->deleteItem(item_i);
-                                               SendInventory(player->peer_id);
+                                               titem = (ToolItem*)item;
+                                               toolname = titem->getToolName();
                                        }
                                }
+
+                               v3f playerpos = player->getPosition();
+                               v3f objpos = obj->getBasePosition();
+                               v3f dir = (objpos - playerpos).normalize();
+                               
+                               u16 wear = obj->punch(toolname, dir);
+                               
+                               if(titem)
+                               {
+                                       bool weared_out = titem->addWear(wear);
+                                       if(weared_out)
+                                               mlist->deleteItem(item_i);
+                                       SendInventory(player->peer_id);
+                               }
+                       }
+               }
+               // Right click, do something with object
+               if(button == 1)
+               {
+                       // Track hp changes super-crappily
+                       u16 oldhp = player->hp;
+                       
+                       // Do stuff
+                       obj->rightClick(player);
+                       
+                       // Send back stuff
+                       if(player->hp != oldhp)
+                       {
+                               SendPlayerHP(player);
                        }
                }
        }
@@ -2188,7 +2550,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                        // Mandatory parameter; actually used for nothing
                        core::map<v3s16, MapBlock*> modified_blocks;
 
-                       u8 material;
+                       content_t material = CONTENT_IGNORE;
                        u8 mineral = MINERAL_NONE;
 
                        bool cannot_remove_node = false;
@@ -2199,7 +2561,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                                // Get mineral
                                mineral = n.getMineral();
                                // Get material at position
-                               material = n.d;
+                               material = n.getContent();
                                // If not yet cancelled
                                if(cannot_remove_node == false)
                                {
@@ -2236,6 +2598,15 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                                cannot_remove_node = true;
                        }
 
+                       // Make sure the player is allowed to do it
+                       if((getPlayerPrivs(player) & PRIV_BUILD) == 0)
+                       {
+                               dstream<<"Player "<<player->getName()<<" cannot remove node"
+                                               <<" because privileges are "<<getPlayerPrivs(player)
+                                               <<std::endl;
+                               cannot_remove_node = true;
+                       }
+
                        /*
                                If node can't be removed, set block to be re-sent to
                                client and quit.
@@ -2258,12 +2629,12 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                        }
                        
                        /*
-                               Send the removal to all other clients.
+                               Send the removal to all close-by players.
                                - If other player is close, send REMOVENODE
                                - Otherwise set blocks not sent
                        */
                        core::list<u16> far_players;
-                       sendRemoveNode(p_under, peer_id, &far_players, 100);
+                       sendRemoveNode(p_under, peer_id, &far_players, 30);
                        
                        /*
                                Update and send inventory
@@ -2338,10 +2709,11 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                                Remove the node
                                (this takes some time so it is done after the quick stuff)
                        */
-                       m_ignore_map_edit_events = true;
-                       m_env.getMap().removeNodeAndUpdate(p_under, modified_blocks);
-                       m_ignore_map_edit_events = false;
-                       
+                       {
+                               MapEditEventIgnorer ign(&m_ignore_map_edit_events);
+
+                               m_env.getMap().removeNodeAndUpdate(p_under, modified_blocks);
+                       }
                        /*
                                Set blocks not sent to far players
                        */
@@ -2382,7 +2754,15 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                                try{
                                        // Don't add a node if this is not a free space
                                        MapNode n2 = m_env.getMap().getNode(p_over);
-                                       if(content_buildable_to(n2.d) == false)
+                                       bool no_enough_privs =
+                                                       ((getPlayerPrivs(player) & PRIV_BUILD)==0);
+                                       if(no_enough_privs)
+                                               dstream<<"Player "<<player->getName()<<" cannot add node"
+                                                       <<" because privileges are "<<getPlayerPrivs(player)
+                                                       <<std::endl;
+
+                                       if(content_features(n2).buildable_to == false
+                                               || no_enough_privs)
                                        {
                                                // Client probably has wrong data.
                                                // Set block not sent, so that client will get
@@ -2412,15 +2792,37 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                                // Create node data
                                MaterialItem *mitem = (MaterialItem*)item;
                                MapNode n;
-                               n.d = mitem->getMaterial();
-                               if(content_features(n.d).wall_mounted)
-                                       n.dir = packDir(p_under - p_over);
-                               
+                               n.setContent(mitem->getMaterial());
+
+                               // Calculate direction for wall mounted stuff
+                               if(content_features(n).wall_mounted)
+                                       n.param2 = packDir(p_under - p_over);
+
+                               // Calculate the direction for furnaces and chests and stuff
+                               if(content_features(n).param_type == CPT_FACEDIR_SIMPLE)
+                               {
+                                       v3f playerpos = player->getPosition();
+                                       v3f blockpos = intToFloat(p_over, BS) - playerpos;
+                                       blockpos = blockpos.normalize();
+                                       n.param1 = 0;
+                                       if (fabs(blockpos.X) > fabs(blockpos.Z)) {
+                                               if (blockpos.X < 0)
+                                                       n.param1 = 3;
+                                               else
+                                                       n.param1 = 1;
+                                       } else {
+                                               if (blockpos.Z < 0)
+                                                       n.param1 = 2;
+                                               else
+                                                       n.param1 = 0;
+                                       }
+                               }
+
                                /*
-                                       Send to all players
+                                       Send to all close-by players
                                */
                                core::list<u16> far_players;
-                               sendAddNode(p_over, n, 0, &far_players, 100);
+                               sendAddNode(p_over, n, 0, &far_players, 30);
                                
                                /*
                                        Handle inventory
@@ -2444,10 +2846,11 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                                        This takes some time so it is done after the quick stuff
                                */
                                core::map<v3s16, MapBlock*> modified_blocks;
-                               m_ignore_map_edit_events = true;
-                               m_env.getMap().addNodeAndUpdate(p_over, n, modified_blocks);
-                               m_ignore_map_edit_events = false;
-                               
+                               {
+                                       MapEditEventIgnorer ign(&m_ignore_map_edit_events);
+
+                                       m_env.getMap().addNodeAndUpdate(p_over, n, modified_blocks);
+                               }
                                /*
                                        Set blocks not sent to far players
                                */
@@ -2579,6 +2982,8 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 #endif
        else if(command == TOSERVER_SIGNTEXT)
        {
+               if((getPlayerPrivs(player) & PRIV_BUILD) == 0)
+                       return;
                /*
                        u16 command
                        v3s16 blockpos
@@ -2636,6 +3041,8 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
        }
        else if(command == TOSERVER_SIGNNODETEXT)
        {
+               if((getPlayerPrivs(player) & PRIV_BUILD) == 0)
+                       return;
                /*
                        u16 command
                        v3s16 p
@@ -2810,6 +3217,10 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                // Whether to send to other players
                bool send_to_others = false;
                
+               // Local player gets all privileges regardless of
+               // what's set on their account.
+               u64 privs = getPlayerPrivs(player);
+
                // Parse commands
                std::wstring commandprefix = L"/#";
                if(message.substr(0, commandprefix.size()) == commandprefix)
@@ -2817,73 +3228,41 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                        line += L"Server: ";
 
                        message = message.substr(commandprefix.size());
-                       // Get player name as narrow string
-                       std::string name_s = player->getName();
-                       // Convert message to narrow string
-                       std::string message_s = wide_to_narrow(message);
-                       // Operator is the single name defined in config.
-                       std::string operator_name = g_settings.get("name");
-                       bool is_operator = (operator_name != "" &&
-                                       wide_to_narrow(name) == operator_name);
-                       bool valid_command = false;
-                       if(message_s == "help")
-                       {
-                               line += L"-!- Available commands: ";
-                               line += L"status ";
-                               if(is_operator)
-                               {
-                                       line += L"shutdown setting ";
-                               }
-                               else
-                               {
-                               }
-                               send_to_sender = true;
-                               valid_command = true;
-                       }
-                       else if(message_s == "status")
-                       {
-                               line = getStatusString();
-                               send_to_sender = true;
-                               valid_command = true;
-                       }
-                       else if(is_operator)
+                       
+                       WStrfnd f1(message);
+                       f1.next(L" "); // Skip over /#whatever
+                       std::wstring paramstring = f1.next(L"");
+
+                       ServerCommandContext *ctx = new ServerCommandContext(
+                               str_split(message, L' '),
+                               paramstring,
+                               this,
+                               &m_env,
+                               player,
+                               privs);
+
+                       line += processServerCommand(ctx);
+                       send_to_sender = ctx->flags & 1;
+                       send_to_others = ctx->flags & 2;
+                       delete ctx;
+
+               }
+               else
+               {
+                       if(privs & PRIV_SHOUT)
                        {
-                               if(message_s == "shutdown")
-                               {
-                                       dstream<<DTIME<<" Server: Operator requested shutdown."
-                                                       <<std::endl;
-                                       m_shutdown_requested.set(true);
-                                       
-                                       line += L"*** Server shutting down (operator request)";
-                                       send_to_sender = true;
-                                       valid_command = true;
-                               }
-                               else if(message_s.substr(0,8) == "setting ")
-                               {
-                                       std::string confline = message_s.substr(8);
-                                       g_settings.parseConfigLine(confline);
-                                       line += L"-!- Setting changed.";
-                                       send_to_sender = true;
-                                       valid_command = true;
-                               }
+                               line += L"<";
+                               line += name;
+                               line += L"> ";
+                               line += message;
+                               send_to_others = true;
                        }
-                       
-                       if(valid_command == false)
+                       else
                        {
-                               line += L"-!- Invalid command: " + message;
+                               line += L"Server: You are not allowed to shout";
                                send_to_sender = true;
                        }
                }
-               else
-               {
-                       line += L"<";
-                       /*if(is_operator)
-                               line += L"@";*/
-                       line += name;
-                       line += L"> ";
-                       line += message;
-                       send_to_others = true;
-               }
                
                if(line != L"")
                {
@@ -2943,6 +3322,66 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 
                SendPlayerHP(player);
        }
+       else if(command == TOSERVER_PASSWORD)
+       {
+               /*
+                       [0] u16 TOSERVER_PASSWORD
+                       [2] u8[28] old password
+                       [30] u8[28] new password
+               */
+
+               if(datasize != 2+PASSWORD_SIZE*2)
+                       return;
+               /*char password[PASSWORD_SIZE];
+               for(u32 i=0; i<PASSWORD_SIZE-1; i++)
+                       password[i] = data[2+i];
+               password[PASSWORD_SIZE-1] = 0;*/
+               std::string oldpwd;
+               for(u32 i=0; i<PASSWORD_SIZE-1; i++)
+               {
+                       char c = data[2+i];
+                       if(c == 0)
+                               break;
+                       oldpwd += c;
+               }
+               std::string newpwd;
+               for(u32 i=0; i<PASSWORD_SIZE-1; i++)
+               {
+                       char c = data[2+PASSWORD_SIZE+i];
+                       if(c == 0)
+                               break;
+                       newpwd += c;
+               }
+
+               dstream<<"Server: Client requests a password change from "
+                               <<"'"<<oldpwd<<"' to '"<<newpwd<<"'"<<std::endl;
+
+               std::string playername = player->getName();
+
+               if(m_authmanager.exists(playername) == false)
+               {
+                       dstream<<"Server: playername not found in authmanager"<<std::endl;
+                       // Wrong old password supplied!!
+                       SendChatMessage(peer_id, L"playername not found in authmanager");
+                       return;
+               }
+
+               std::string checkpwd = m_authmanager.getPassword(playername);
+
+               if(oldpwd != checkpwd)
+               {
+                       dstream<<"Server: invalid old password"<<std::endl;
+                       // Wrong old password supplied!!
+                       SendChatMessage(peer_id, L"Invalid old password supplied. Password NOT changed.");
+                       return;
+               }
+
+               m_authmanager.setPassword(playername, newpwd);
+               
+               dstream<<"Server: password change successful for "<<playername
+                               <<std::endl;
+               SendChatMessage(peer_id, L"Password change successful");
+       }
        else
        {
                derr_server<<"WARNING: Server::ProcessData(): Ignoring "
@@ -2960,7 +3399,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 
 void Server::onMapEditEvent(MapEditEvent *event)
 {
-       dstream<<"Server::onMapEditEvent()"<<std::endl;
+       //dstream<<"Server::onMapEditEvent()"<<std::endl;
        if(m_ignore_map_edit_events)
                return;
        MapEditEvent *e = event->clone();
@@ -3123,6 +3562,22 @@ void Server::SendHP(con::Connection &con, u16 peer_id, u8 hp)
        con.Send(peer_id, 0, data, true);
 }
 
+void Server::SendAccessDenied(con::Connection &con, u16 peer_id,
+               const std::wstring &reason)
+{
+       DSTACK(__FUNCTION_NAME);
+       std::ostringstream os(std::ios_base::binary);
+
+       writeU16(os, TOCLIENT_ACCESS_DENIED);
+       os<<serializeWideString(reason);
+
+       // Make data buffer
+       std::string s = os.str();
+       SharedBuffer<u8> data((u8*)s.c_str(), s.size());
+       // Send as reliable
+       con.Send(peer_id, 0, data, true);
+}
+
 /*
        Non-static send methods
 */
@@ -3389,9 +3844,44 @@ void Server::sendAddNode(v3s16 p, MapNode n, u16 ignore_id,
        }
 }
 
+void Server::setBlockNotSent(v3s16 p)
+{
+       for(core::map<u16, RemoteClient*>::Iterator
+               i = m_clients.getIterator();
+               i.atEnd()==false; i++)
+       {
+               RemoteClient *client = i.getNode()->getValue();
+               client->SetBlockNotSent(p);
+       }
+}
+
 void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver)
 {
        DSTACK(__FUNCTION_NAME);
+
+       v3s16 p = block->getPos();
+       
+#if 0
+       // Analyze it a bit
+       bool completely_air = true;
+       for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
+       for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
+       for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
+       {
+               if(block->getNodeNoEx(v3s16(x0,y0,z0)).d != CONTENT_AIR)
+               {
+                       completely_air = false;
+                       x0 = y0 = z0 = MAP_BLOCKSIZE; // Break out
+               }
+       }
+
+       // Print result
+       dstream<<"Server: Sending block ("<<p.X<<","<<p.Y<<","<<p.Z<<"): ";
+       if(completely_air)
+               dstream<<"[completely air] ";
+       dstream<<std::endl;
+#endif
+
        /*
                Create a packet with the block in the right format
        */
@@ -3403,7 +3893,6 @@ void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver)
 
        u32 replysize = 8 + blockdata.getSize();
        SharedBuffer<u8> reply(replysize);
-       v3s16 p = block->getPos();
        writeU16(&reply[0], TOCLIENT_BLOCKDATA);
        writeS16(&reply[2], p.X);
        writeS16(&reply[4], p.Y);
@@ -3431,20 +3920,24 @@ void Server::SendBlocks(float dtime)
        core::array<PrioritySortedBlockTransfer> queue;
 
        s32 total_sending = 0;
-
-       for(core::map<u16, RemoteClient*>::Iterator
-               i = m_clients.getIterator();
-               i.atEnd() == false; i++)
+       
        {
-               RemoteClient *client = i.getNode()->getValue();
-               assert(client->peer_id == i.getNode()->getKey());
+               ScopeProfiler sp(&g_profiler, "Server: selecting blocks for sending");
 
-               total_sending += client->SendingCount();
-               
-               if(client->serialization_version == SER_FMT_VER_INVALID)
-                       continue;
-               
-               client->GetNextBlocks(this, dtime, queue);
+               for(core::map<u16, RemoteClient*>::Iterator
+                       i = m_clients.getIterator();
+                       i.atEnd() == false; i++)
+               {
+                       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;
+                       
+                       client->GetNextBlocks(this, dtime, queue);
+               }
        }
 
        // Sort.
@@ -3515,317 +4008,10 @@ void Server::UpdateCrafting(u16 peer_id)
                                items[i] = clist->getItem(i);
                        }
                        
-                       bool found = false;
-
-                       // Wood
-                       if(!found)
-                       {
-                               ItemSpec specs[9];
-                               specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_TREE);
-                               if(checkItemCombination(items, specs))
-                               {
-                                       rlist->addItem(new MaterialItem(CONTENT_WOOD, 4));
-                                       found = true;
-                               }
-                       }
-
-                       // Stick
-                       if(!found)
-                       {
-                               ItemSpec specs[9];
-                               specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
-                               if(checkItemCombination(items, specs))
-                               {
-                                       rlist->addItem(new CraftItem("Stick", 4));
-                                       found = true;
-                               }
-                       }
-
-                       // Sign
-                       if(!found)
-                       {
-                               ItemSpec specs[9];
-                               specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
-                               specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
-                               specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
-                               specs[3] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
-                               specs[4] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
-                               specs[5] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
-                               specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
-                               if(checkItemCombination(items, specs))
-                               {
-                                       //rlist->addItem(new MapBlockObjectItem("Sign"));
-                                       rlist->addItem(new MaterialItem(CONTENT_SIGN_WALL, 1));
-                                       found = true;
-                               }
-                       }
-
-                       // Torch
-                       if(!found)
-                       {
-                               ItemSpec specs[9];
-                               specs[0] = ItemSpec(ITEM_CRAFT, "lump_of_coal");
-                               specs[3] = ItemSpec(ITEM_CRAFT, "Stick");
-                               if(checkItemCombination(items, specs))
-                               {
-                                       rlist->addItem(new MaterialItem(CONTENT_TORCH, 4));
-                                       found = true;
-                               }
-                       }
-
-                       // Wooden pick
-                       if(!found)
-                       {
-                               ItemSpec specs[9];
-                               specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
-                               specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
-                               specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
-                               specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
-                               specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
-                               if(checkItemCombination(items, specs))
-                               {
-                                       rlist->addItem(new ToolItem("WPick", 0));
-                                       found = true;
-                               }
-                       }
-
-                       // Stone pick
-                       if(!found)
-                       {
-                               ItemSpec specs[9];
-                               specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
-                               specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
-                               specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
-                               specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
-                               specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
-                               if(checkItemCombination(items, specs))
-                               {
-                                       rlist->addItem(new ToolItem("STPick", 0));
-                                       found = true;
-                               }
-                       }
-
-                       // Steel pick
-                       if(!found)
-                       {
-                               ItemSpec specs[9];
-                               specs[0] = ItemSpec(ITEM_CRAFT, "steel_ingot");
-                               specs[1] = ItemSpec(ITEM_CRAFT, "steel_ingot");
-                               specs[2] = ItemSpec(ITEM_CRAFT, "steel_ingot");
-                               specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
-                               specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
-                               if(checkItemCombination(items, specs))
-                               {
-                                       rlist->addItem(new ToolItem("SteelPick", 0));
-                                       found = true;
-                               }
-                       }
-
-                       // Mese pick
-                       if(!found)
-                       {
-                               ItemSpec specs[9];
-                               specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_MESE);
-                               specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_MESE);
-                               specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_MESE);
-                               specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
-                               specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
-                               if(checkItemCombination(items, specs))
-                               {
-                                       rlist->addItem(new ToolItem("MesePick", 0));
-                                       found = true;
-                               }
-                       }
-
-                       // Wooden shovel
-                       if(!found)
-                       {
-                               ItemSpec specs[9];
-                               specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
-                               specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
-                               specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
-                               if(checkItemCombination(items, specs))
-                               {
-                                       rlist->addItem(new ToolItem("WShovel", 0));
-                                       found = true;
-                               }
-                       }
-
-                       // Stone shovel
-                       if(!found)
-                       {
-                               ItemSpec specs[9];
-                               specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
-                               specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
-                               specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
-                               if(checkItemCombination(items, specs))
-                               {
-                                       rlist->addItem(new ToolItem("STShovel", 0));
-                                       found = true;
-                               }
-                       }
-
-                       // Steel shovel
-                       if(!found)
-                       {
-                               ItemSpec specs[9];
-                               specs[1] = ItemSpec(ITEM_CRAFT, "steel_ingot");
-                               specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
-                               specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
-                               if(checkItemCombination(items, specs))
-                               {
-                                       rlist->addItem(new ToolItem("SteelShovel", 0));
-                                       found = true;
-                               }
-                       }
-
-                       // Wooden axe
-                       if(!found)
-                       {
-                               ItemSpec specs[9];
-                               specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
-                               specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
-                               specs[3] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
-                               specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
-                               specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
-                               if(checkItemCombination(items, specs))
-                               {
-                                       rlist->addItem(new ToolItem("WAxe", 0));
-                                       found = true;
-                               }
-                       }
-
-                       // Stone axe
-                       if(!found)
-                       {
-                               ItemSpec specs[9];
-                               specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
-                               specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
-                               specs[3] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
-                               specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
-                               specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
-                               if(checkItemCombination(items, specs))
-                               {
-                                       rlist->addItem(new ToolItem("STAxe", 0));
-                                       found = true;
-                               }
-                       }
-
-                       // Steel axe
-                       if(!found)
-                       {
-                               ItemSpec specs[9];
-                               specs[0] = ItemSpec(ITEM_CRAFT, "steel_ingot");
-                               specs[1] = ItemSpec(ITEM_CRAFT, "steel_ingot");
-                               specs[3] = ItemSpec(ITEM_CRAFT, "steel_ingot");
-                               specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
-                               specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
-                               if(checkItemCombination(items, specs))
-                               {
-                                       rlist->addItem(new ToolItem("SteelAxe", 0));
-                                       found = true;
-                               }
-                       }
-
-                       // Wooden sword
-                       if(!found)
-                       {
-                               ItemSpec specs[9];
-                               specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
-                               specs[4] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
-                               specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
-                               if(checkItemCombination(items, specs))
-                               {
-                                       rlist->addItem(new ToolItem("WSword", 0));
-                                       found = true;
-                               }
-                       }
-
-                       // Stone sword
-                       if(!found)
-                       {
-                               ItemSpec specs[9];
-                               specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
-                               specs[4] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
-                               specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
-                               if(checkItemCombination(items, specs))
-                               {
-                                       rlist->addItem(new ToolItem("STSword", 0));
-                                       found = true;
-                               }
-                       }
-
-                       // Steel sword
-                       if(!found)
-                       {
-                               ItemSpec specs[9];
-                               specs[1] = ItemSpec(ITEM_CRAFT, "steel_ingot");
-                               specs[4] = ItemSpec(ITEM_CRAFT, "steel_ingot");
-                               specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
-                               if(checkItemCombination(items, specs))
-                               {
-                                       rlist->addItem(new ToolItem("SteelSword", 0));
-                                       found = true;
-                               }
-                       }
-
-                       // Chest
-                       if(!found)
-                       {
-                               ItemSpec specs[9];
-                               specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
-                               specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
-                               specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
-                               specs[3] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
-                               specs[5] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
-                               specs[6] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
-                               specs[7] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
-                               specs[8] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
-                               if(checkItemCombination(items, specs))
-                               {
-                                       rlist->addItem(new MaterialItem(CONTENT_CHEST, 1));
-                                       found = true;
-                               }
-                       }
-
-                       // Furnace
-                       if(!found)
-                       {
-                               ItemSpec specs[9];
-                               specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
-                               specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
-                               specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
-                               specs[3] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
-                               specs[5] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
-                               specs[6] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
-                               specs[7] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
-                               specs[8] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
-                               if(checkItemCombination(items, specs))
-                               {
-                                       rlist->addItem(new MaterialItem(CONTENT_FURNACE, 1));
-                                       found = true;
-                               }
-                       }
-
-                       // Steel block
-                       if(!found)
-                       {
-                               ItemSpec specs[9];
-                               specs[0] = ItemSpec(ITEM_CRAFT, "steel_ingot");
-                               specs[1] = ItemSpec(ITEM_CRAFT, "steel_ingot");
-                               specs[2] = ItemSpec(ITEM_CRAFT, "steel_ingot");
-                               specs[3] = ItemSpec(ITEM_CRAFT, "steel_ingot");
-                               specs[4] = ItemSpec(ITEM_CRAFT, "steel_ingot");
-                               specs[5] = ItemSpec(ITEM_CRAFT, "steel_ingot");
-                               specs[6] = ItemSpec(ITEM_CRAFT, "steel_ingot");
-                               specs[7] = ItemSpec(ITEM_CRAFT, "steel_ingot");
-                               specs[8] = ItemSpec(ITEM_CRAFT, "steel_ingot");
-                               if(checkItemCombination(items, specs))
-                               {
-                                       rlist->addItem(new MaterialItem(CONTENT_STEEL, 1));
-                                       found = true;
-                               }
-                       }
+                       // Get result of crafting grid
+                       InventoryItem *result = craft_get_result(items);
+                       if(result)
+                               rlist->addItem(result);
                }
        
        } // if creative_mode == false
@@ -3846,8 +4032,10 @@ std::wstring Server::getStatusString()
 {
        std::wostringstream os(std::ios_base::binary);
        os<<L"# Server: ";
+       // Version
+       os<<L"version="<<narrow_to_wide(VERSION_STRING);
        // Uptime
-       os<<L"uptime="<<m_uptime.get();
+       os<<L"uptime="<<m_uptime.get();
        // Information about clients
        os<<L", clients={";
        for(core::map<u16, RemoteClient*>::Iterator
@@ -3870,104 +4058,25 @@ std::wstring Server::getStatusString()
        }
        os<<L"}";
        if(((ServerMap*)(&m_env.getMap()))->isSavingEnabled() == false)
-               os<<" WARNING: Map saving is disabled."<<std::endl;
+               os<<std::endl<<L"# Server: "<<" WARNING: Map saving is disabled.";
+       if(g_settings.get("motd") != "")
+               os<<std::endl<<L"# Server: "<<narrow_to_wide(g_settings.get("motd"));
        return os.str();
 }
 
-
-void setCreativeInventory(Player *player)
+v3f findSpawnPos(ServerMap &map)
 {
-       player->resetInventory();
-       
-       // Give some good tools
-       {
-               InventoryItem *item = new ToolItem("MesePick", 0);
-               void* r = player->inventory.addItem("main", item);
-               assert(r == NULL);
-       }
-       {
-               InventoryItem *item = new ToolItem("SteelPick", 0);
-               void* r = player->inventory.addItem("main", item);
-               assert(r == NULL);
-       }
-       {
-               InventoryItem *item = new ToolItem("SteelAxe", 0);
-               void* r = player->inventory.addItem("main", item);
-               assert(r == NULL);
-       }
-       {
-               InventoryItem *item = new ToolItem("SteelShovel", 0);
-               void* r = player->inventory.addItem("main", item);
-               assert(r == NULL);
-       }
+       //return v3f(50,50,50)*BS;
 
-       /*
-               Give materials
-       */
-       
-       // CONTENT_IGNORE-terminated list
-       u8 material_items[] = {
-               CONTENT_TORCH,
-               CONTENT_COBBLE,
-               CONTENT_MUD,
-               CONTENT_STONE,
-               CONTENT_SAND,
-               CONTENT_TREE,
-               CONTENT_LEAVES,
-               CONTENT_MESE,
-               CONTENT_WATERSOURCE,
-               CONTENT_CLOUD,
-               CONTENT_CHEST,
-               CONTENT_FURNACE,
-               CONTENT_SIGN_WALL,
-               CONTENT_IGNORE
-       };
+       v2s16 nodepos;
+       s16 groundheight = 0;
        
-       u8 *mip = material_items;
-       for(u16 i=0; i<PLAYER_INVENTORY_SIZE; i++)
-       {
-               if(*mip == CONTENT_IGNORE)
-                       break;
-
-               InventoryItem *item = new MaterialItem(*mip, 1);
-               player->inventory.addItem("main", item);
-
-               mip++;
-       }
-
 #if 0
-       assert(USEFUL_CONTENT_COUNT <= PLAYER_INVENTORY_SIZE);
-       
-       // add torch first
-       InventoryItem *item = new MaterialItem(CONTENT_TORCH, 1);
-       player->inventory.addItem("main", item);
-       
-       // Then others
-       for(u16 i=0; i<USEFUL_CONTENT_COUNT; i++)
-       {
-               // Skip some materials
-               if(i == CONTENT_WATER || i == CONTENT_TORCH
-                       || i == CONTENT_COALSTONE)
-                       continue;
-
-               InventoryItem *item = new MaterialItem(i, 1);
-               player->inventory.addItem("main", item);
-       }
+       nodepos = v2s16(0,0);
+       groundheight = 20;
 #endif
 
-       /*// Sign
-       {
-               InventoryItem *item = new MapBlockObjectItem("Sign Example text");
-               void* r = player->inventory.addItem("main", item);
-               assert(r == NULL);
-       }*/
-}
-
-v3f findSpawnPos(ServerMap &map)
-{
-       v2s16 nodepos;
-       s16 groundheight = 0;
-       
+#if 1
        // Try to find a good place a few times
        for(s32 i=0; i<1000; i++)
        {
@@ -3997,6 +4106,7 @@ v3f findSpawnPos(ServerMap &map)
                //dstream<<"Searched through "<<i<<" places."<<std::endl;
                break;
        }
+#endif
        
        // If no suitable place was not found, go above water at least.
        if(groundheight < WATER_LEVEL)
@@ -4004,13 +4114,12 @@ v3f findSpawnPos(ServerMap &map)
 
        return intToFloat(v3s16(
                        nodepos.X,
-                       groundheight + 2,
+                       groundheight + 3,
                        nodepos.Y
                        ), BS);
 }
 
-Player *Server::emergePlayer(const char *name, const char *password,
-               u16 peer_id)
+Player *Server::emergePlayer(const char *name, const char *password, u16 peer_id)
 {
        /*
                Try to get an existing player
@@ -4031,7 +4140,12 @@ Player *Server::emergePlayer(const char *name, const char *password,
                // Reset inventory to creative if in creative mode
                if(g_settings.getBool("creative_mode"))
                {
-                       setCreativeInventory(player);
+                       // Warning: double code below
+                       // Backup actual inventory
+                       player->inventory_backup = new Inventory();
+                       *(player->inventory_backup) = player->inventory;
+                       // Set creative inventory
+                       craft_set_creative_inventory(player);
                }
 
                return player;
@@ -4056,6 +4170,10 @@ Player *Server::emergePlayer(const char *name, const char *password,
                //player->peer_id = PEER_ID_INEXISTENT;
                player->peer_id = peer_id;
                player->updateName(name);
+               m_authmanager.add(name);
+               m_authmanager.setPassword(name, password);
+               m_authmanager.setPrivs(name,
+                               stringToPrivs(g_settings.get("default_privs")));
 
                /*
                        Set player position
@@ -4080,78 +4198,16 @@ Player *Server::emergePlayer(const char *name, const char *password,
                
                if(g_settings.getBool("creative_mode"))
                {
-                       setCreativeInventory(player);
+                       // Warning: double code above
+                       // Backup actual inventory
+                       player->inventory_backup = new Inventory();
+                       *(player->inventory_backup) = player->inventory;
+                       // Set creative inventory
+                       craft_set_creative_inventory(player);
                }
                else if(g_settings.getBool("give_initial_stuff"))
                {
-                       {
-                               InventoryItem *item = new ToolItem("SteelPick", 0);
-                               void* r = player->inventory.addItem("main", item);
-                               assert(r == NULL);
-                       }
-                       {
-                               InventoryItem *item = new MaterialItem(CONTENT_TORCH, 99);
-                               void* r = player->inventory.addItem("main", item);
-                               assert(r == NULL);
-                       }
-                       {
-                               InventoryItem *item = new ToolItem("SteelAxe", 0);
-                               void* r = player->inventory.addItem("main", item);
-                               assert(r == NULL);
-                       }
-                       {
-                               InventoryItem *item = new ToolItem("SteelShovel", 0);
-                               void* r = player->inventory.addItem("main", item);
-                               assert(r == NULL);
-                       }
-                       {
-                               InventoryItem *item = new MaterialItem(CONTENT_COBBLE, 99);
-                               void* r = player->inventory.addItem("main", item);
-                               assert(r == NULL);
-                       }
-                       /*{
-                               InventoryItem *item = new MaterialItem(CONTENT_MESE, 6);
-                               void* r = player->inventory.addItem("main", item);
-                               assert(r == NULL);
-                       }
-                       {
-                               InventoryItem *item = new MaterialItem(CONTENT_COALSTONE, 6);
-                               void* r = player->inventory.addItem("main", item);
-                               assert(r == NULL);
-                       }
-                       {
-                               InventoryItem *item = new MaterialItem(CONTENT_WOOD, 6);
-                               void* r = player->inventory.addItem("main", item);
-                               assert(r == NULL);
-                       }
-                       {
-                               InventoryItem *item = new CraftItem("Stick", 4);
-                               void* r = player->inventory.addItem("main", item);
-                               assert(r == NULL);
-                       }
-                       {
-                               InventoryItem *item = new ToolItem("WPick", 32000);
-                               void* r = player->inventory.addItem("main", item);
-                               assert(r == NULL);
-                       }
-                       {
-                               InventoryItem *item = new ToolItem("STPick", 32000);
-                               void* r = player->inventory.addItem("main", item);
-                               assert(r == NULL);
-                       }*/
-                       /*// and some signs
-                       for(u16 i=0; i<4; i++)
-                       {
-                               InventoryItem *item = new MapBlockObjectItem("Sign Example text");
-                               bool r = player->inventory.addItem("main", item);
-                               assert(r == true);
-                       }*/
-                       /*// Give some other stuff
-                       {
-                               InventoryItem *item = new MaterialItem(CONTENT_TREE, 999);
-                               bool r = player->inventory.addItem("main", item);
-                               assert(r == true);
-                       }*/
+                       craft_give_initial_stuff(player);
                }
 
                return player;
@@ -4194,6 +4250,23 @@ void Server::handlePeerChange(PeerChange &c)
                // The client should exist
                assert(n != NULL);
                
+               /*
+                       Mark objects to be not known by the client
+               */
+               RemoteClient *client = n->getValue();
+               // Handle objects
+               for(core::map<u16, bool>::Iterator
+                               i = client->m_known_objects.getIterator();
+                               i.atEnd()==false; i++)
+               {
+                       // Get object
+                       u16 id = i.getNode()->getKey();
+                       ServerActiveObject* obj = m_env.getActiveObject(id);
+                       
+                       if(obj && obj->m_known_by_count > 0)
+                               obj->m_known_by_count--;
+               }
+
                // Collect information about leaving in chat
                std::wstring message;
                {
@@ -4252,29 +4325,69 @@ void Server::handlePeerChanges()
        }
 }
 
+u64 Server::getPlayerPrivs(Player *player)
+{
+       if(player==NULL)
+               return 0;
+       std::string playername = player->getName();
+       // Local player gets all privileges regardless of
+       // what's set on their account.
+       if(g_settings.get("name") == playername)
+       {
+               return PRIV_ALL;
+       }
+       else
+       {
+               return getPlayerAuthPrivs(playername);
+       }
+}
+
 void dedicated_server_loop(Server &server, bool &kill)
 {
        DSTACK(__FUNCTION_NAME);
        
-       std::cout<<DTIME<<std::endl;
-       std::cout<<"========================"<<std::endl;
-       std::cout<<"Running dedicated server"<<std::endl;
-       std::cout<<"========================"<<std::endl;
-       std::cout<<std::endl;
+       dstream<<DTIME<<std::endl;
+       dstream<<"========================"<<std::endl;
+       dstream<<"Running dedicated server"<<std::endl;
+       dstream<<"========================"<<std::endl;
+       dstream<<std::endl;
+
+       IntervalLimiter m_profiler_interval;
 
        for(;;)
        {
                // This is kind of a hack but can be done like this
                // because server.step() is very light
-               sleep_ms(30);
+               {
+                       ScopeProfiler sp(&g_profiler, "dedicated server sleep");
+                       sleep_ms(30);
+               }
                server.step(0.030);
 
                if(server.getShutdownRequested() || kill)
                {
-                       std::cout<<DTIME<<" dedicated_server_loop(): Quitting."<<std::endl;
+                       dstream<<DTIME<<" dedicated_server_loop(): Quitting."<<std::endl;
                        break;
                }
 
+               /*
+                       Profiler
+               */
+               float profiler_print_interval =
+                               g_settings.getFloat("profiler_print_interval");
+               if(profiler_print_interval != 0)
+               {
+                       if(m_profiler_interval.step(0.030, profiler_print_interval))
+                       {
+                               dstream<<"Profiler:"<<std::endl;
+                               g_profiler.print(dstream);
+                               g_profiler.clear();
+                       }
+               }
+               
+               /*
+                       Player info
+               */
                static int counter = 0;
                counter--;
                if(counter <= 0)
@@ -4287,10 +4400,10 @@ void dedicated_server_loop(Server &server, bool &kill)
                        u32 sum = PIChecksum(list);
                        if(sum != sum_old)
                        {
-                               std::cout<<DTIME<<"Player info:"<<std::endl;
+                               dstream<<DTIME<<"Player info:"<<std::endl;
                                for(i=list.begin(); i!=list.end(); i++)
                                {
-                                       i->PrintLine(&std::cout);
+                                       i->PrintLine(&dstream);
                                }
                        }
                        sum_old = sum;