Clients inform server on wielded item
[oweals/minetest.git] / src / server.cpp
index d47dcb9ce1ad9cd46e17cbfda3b187c40bedd2f1..e3747775a22ae91c0a6295a855da12b8854a6602 100644 (file)
@@ -29,9 +29,41 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #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();
@@ -73,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.
@@ -123,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;
@@ -134,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
                */
@@ -191,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
@@ -232,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()
@@ -253,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
@@ -310,11 +344,14 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
        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
@@ -324,24 +361,33 @@ 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);
        
        // Camera position and direction
-       v3f camera_pos =
-                       playerpos + v3f(0, BS+BS/2, 0);
+       v3f camera_pos = player->getEyePosition();
        v3f camera_dir = v3f(0,0,1);
        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.
        */
@@ -354,11 +400,18 @@ 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;*/
        }
 
        //s16 last_nearest_unsent_d = m_nearest_unsent_d;
@@ -400,11 +453,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;
 
        bool sending_something = false;
 
-       for(s16 d = d_start; d <= d_max; d++)
+       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;
                
@@ -449,7 +513,10 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
 
                        // Don't select too many blocks for sending
                        if(num_blocks_selected >= max_simul_dynamic)
-                               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)
@@ -511,6 +578,8 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
                        }
 #endif
 
+                       //dstream<<"d="<<d<<std::endl;
+                       
                        /*
                                Don't generate or send if not in sight
                        */
@@ -525,7 +594,9 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
                        */
                        {
                                if(m_blocks_sent.find(p) != NULL)
+                               {
                                        continue;
+                               }
                        }
 
                        /*
@@ -537,11 +608,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;
@@ -551,19 +628,23 @@ 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 not near ground level if night-time mesh
-                                       doesn't differ from day-time mesh.
+                                       Block is near ground level if night-time mesh
+                                       differs from day-time mesh.
                                */
                                if(d > 3)
                                {
@@ -584,14 +665,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)
                        {
                                if(generate == true)
                                        new_nearest_unsent_d = d;
                        }
+
+                       no_blocks_found_for_sending = false;
                                        
                        /*
                                Add inexistent block to emerge queue.
@@ -601,6 +684,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;
@@ -631,20 +715,30 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
                        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)
        {
-               m_nearest_unsent_d = new_nearest_unsent_d;
+               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(m_nothing_to_send_counter >= 3)
+               if((s16)m_nothing_to_send_counter >=
+                               g_settings.getS16("max_block_send_distance"))
                {
                        // Pause time in seconds
-                       m_nothing_to_send_pause_timer = 2.0;
+                       m_nothing_to_send_pause_timer = 1.0;
+                       /*dstream<<"nothing to send to "
+                                       <<server->getPlayerName(peer_id)
+                                       <<" (d="<<d<<")"<<std::endl;*/
                }
        }
        else
@@ -797,7 +891,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();
                        }
@@ -815,7 +909,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++;
 
@@ -961,17 +1057,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)
@@ -986,10 +1084,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);
 }
 
@@ -1031,6 +1138,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
@@ -1113,12 +1226,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;
        
@@ -1135,14 +1252,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;
 
@@ -1166,7 +1286,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);
                        }
@@ -1176,21 +1296,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
@@ -1205,7 +1337,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          
@@ -1278,10 +1412,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;
 
@@ -1293,8 +1428,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);
@@ -1426,6 +1563,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;
@@ -1538,33 +1677,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);
                }
+               
        }
 
        /*
@@ -1578,40 +1797,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)
@@ -1627,7 +1821,7 @@ void Server::AsyncRunStep()
                }
        }
 
-       // Save map
+       // Save map, players and auth stuff
        {
                float &counter = m_savemap_timer;
                counter += dtime;
@@ -1635,25 +1829,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);
                }
        }
 }
@@ -1733,8 +1939,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 "
@@ -1758,6 +1965,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;
                }
 
@@ -1766,17 +1994,82 @@ 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;
+               
+               if(playername[0]=='\0')
+               {
+                       derr_server<<DTIME<<"Server: Player has empty name"<<std::endl;
+                       SendAccessDenied(m_con, peer_id,
+                                       L"Empty name");
+                       return;
+               }
+
+               if(string_allowed(playername, PLAYERNAME_ALLOWED_CHARS)==false)
+               {
+                       derr_server<<DTIME<<"Server: Player has invalid name"<<std::endl;
+                       SendAccessDenied(m_con, peer_id,
+                                       L"Name contains unallowed characters");
+                       return;
+               }
+
+               // 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, "", peer_id);
-               //Player *player = m_env.getPlayer(peer_id);
+               Player *player = emergePlayer(playername, password, peer_id);
+
 
                /*{
                        // DEBUG: Test serialization
@@ -1830,8 +2123,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);
@@ -1874,7 +2166,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);
                }
                
@@ -1894,6 +2186,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;
        }
@@ -1994,6 +2292,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)
@@ -2075,6 +2376,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
@@ -2095,71 +2399,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)
-                                       {
-                                               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)
+                               InventoryList *mlist = player->inventory.getList("main");
+                               if(mlist != NULL)
+                               {
+                                       InventoryItem *item = mlist->getItem(item_i);
+                                       if(item && (std::string)item->getName() == "ToolItem")
                                        {
-                                               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);
                        }
                }
        }
@@ -2224,7 +2549,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;
@@ -2235,7 +2560,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)
                                {
@@ -2272,6 +2597,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.
@@ -2294,12 +2628,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
@@ -2374,10 +2708,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
                        */
@@ -2418,7 +2753,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
@@ -2448,15 +2791,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
@@ -2480,10 +2845,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
                                */
@@ -2615,6 +2981,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
@@ -2672,6 +3040,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
@@ -2846,6 +3216,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)
@@ -2853,82 +3227,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 time ";
-                               }
-                               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;
-                               }
-                               else if(message_s.substr(0,5) == "time ")
-                               {
-                                       u32 time = stoi(message_s.substr(5));
-                                       m_time_of_day.set(time);
-                                       m_time_of_day_send_timer = 0;
-                                       line += L"-!- time_of_day 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"")
                {
@@ -2988,6 +3321,74 @@ 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 if (command == TOSERVER_PLAYERITEM)
+       {
+               if (datasize < 2+2)
+                       return;
+
+               u16 item = readU16(&data[2]);
+               player->wieldItem(item);
+       }
        else
        {
                derr_server<<"WARNING: Server::ProcessData(): Ignoring "
@@ -3005,7 +3406,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();
@@ -3168,6 +3569,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
 */
@@ -3434,6 +3851,17 @@ 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);
@@ -3499,20 +3927,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.
@@ -3583,317 +4015,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
@@ -3940,106 +4065,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)
-{
-       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);
-       }
-
-       /*
-               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
-       };
-       
-       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);
-       }
-#endif
-
-       /*// Sign
-       {
-               InventoryItem *item = new MapBlockObjectItem("Sign Example text");
-               void* r = player->inventory.addItem("main", item);
-               assert(r == NULL);
-       }*/
-}
-
 v3f findSpawnPos(ServerMap &map)
 {
        //return v3f(50,50,50)*BS;
-       
+
        v2s16 nodepos;
        s16 groundheight = 0;
        
+#if 0
+       nodepos = v2s16(0,0);
+       groundheight = 20;
+#endif
+
+#if 1
        // Try to find a good place a few times
        for(s32 i=0; i<1000; i++)
        {
@@ -4069,6 +4113,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)
@@ -4076,13 +4121,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
@@ -4103,7 +4147,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;
@@ -4128,6 +4177,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
@@ -4152,78 +4205,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;
@@ -4341,29 +4332,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)
@@ -4376,10 +4407,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;