]> git.lizzy.rs Git - dragonfireclient.git/blobdiff - src/server.cpp
Merge pull request #48 from Oblomov/master
[dragonfireclient.git] / src / server.cpp
index 9fa957c2afa2b498b705715cbc974b6b20ee5154..8a19306a9a55b7c1aeeaf12274178f5248494acc 100644 (file)
@@ -34,9 +34,36 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #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();
@@ -78,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.
@@ -128,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;
@@ -139,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
                */
@@ -196,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
@@ -237,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()
@@ -258,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
@@ -580,6 +609,9 @@ 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())
@@ -597,12 +629,16 @@ 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
@@ -649,6 +685,7 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
                                //TODO: Get value from somewhere
                                // Allow only one block in emerge queue
                                //if(server->m_emerge_queue.peerItemCount(peer_id) < 1)
+                               // Allow two blocks in queue per client
                                if(server->m_emerge_queue.peerItemCount(peer_id) < 2)
                                {
                                        //dstream<<"Adding block to emerge queue"<<std::endl;
@@ -873,7 +910,9 @@ void RemoteClient::SendObjectData(
                        bos.write((char*)buf, 6);
 
                        // Write objects
-                       block->serializeObjects(bos, serialization_version);
+                       //block->serializeObjects(bos, serialization_version); // DEPRECATED
+                       // count=0
+                       writeU16(bos, 0);
 
                        blockcount++;
 
@@ -1019,7 +1058,8 @@ 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),
@@ -1030,6 +1070,7 @@ Server::Server(
        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)
@@ -1268,12 +1309,21 @@ void Server::AsyncRunStep()
        }
 
        {
-               // 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
@@ -1628,38 +1678,113 @@ void Server::AsyncRunStep()
                Send queued-for-sending map edit events.
        */
        {
+               // Don't send too many at a time
+               //u32 count = 0;
+
+               // Single change sending is disabled if queue size is not small
+               bool disable_single_change_sending = false;
+               if(m_unsent_map_edit_queue.size() >= 4)
+                       disable_single_change_sending = true;
+
+               bool got_any_events = false;
+
+               // We'll log the amount of each
+               Profiler prof;
+
                while(m_unsent_map_edit_queue.size() != 0)
                {
+                       got_any_events = true;
+
                        MapEditEvent* event = m_unsent_map_edit_queue.pop_front();
+                       
+                       // Players far away from the change are stored here.
+                       // Instead of sending the changes, MapBlocks are set not sent
+                       // for them.
+                       core::list<u16> far_players;
 
                        if(event->type == MEET_ADDNODE)
                        {
-                               dstream<<"Server: MEET_ADDNODE"<<std::endl;
-                               sendAddNode(event->p, event->n, event->already_known_by_peer);
+                               //dstream<<"Server: MEET_ADDNODE"<<std::endl;
+                               prof.add("MEET_ADDNODE", 1);
+                               if(disable_single_change_sending)
+                                       sendAddNode(event->p, event->n, event->already_known_by_peer,
+                                                       &far_players, 5);
+                               else
+                                       sendAddNode(event->p, event->n, event->already_known_by_peer,
+                                                       &far_players, 30);
                        }
                        else if(event->type == MEET_REMOVENODE)
                        {
-                               dstream<<"Server: MEET_REMOVENODE"<<std::endl;
-                               sendRemoveNode(event->p, event->already_known_by_peer);
+                               //dstream<<"Server: MEET_REMOVENODE"<<std::endl;
+                               prof.add("MEET_REMOVENODE", 1);
+                               if(disable_single_change_sending)
+                                       sendRemoveNode(event->p, event->already_known_by_peer,
+                                                       &far_players, 5);
+                               else
+                                       sendRemoveNode(event->p, event->already_known_by_peer,
+                                                       &far_players, 30);
                        }
                        else if(event->type == MEET_BLOCK_NODE_METADATA_CHANGED)
                        {
                                dstream<<"Server: MEET_BLOCK_NODE_METADATA_CHANGED"<<std::endl;
+                               prof.add("MEET_BLOCK_NODE_METADATA_CHANGED", 1);
                                setBlockNotSent(event->p);
                        }
                        else if(event->type == MEET_OTHER)
                        {
+                               prof.add("MEET_OTHER", 1);
                                dstream<<"WARNING: Server: MEET_OTHER not implemented"
                                                <<std::endl;
                        }
                        else
                        {
+                               prof.add("unknown", 1);
                                dstream<<"WARNING: Server: Unknown MapEditEvent "
                                                <<((u32)event->type)<<std::endl;
                        }
+                       
+                       /*
+                               Set blocks not sent to far players
+                       */
+                       if(far_players.size() > 0)
+                       {
+                               // Convert list format to that wanted by SetBlocksNotSent
+                               core::map<v3s16, MapBlock*> modified_blocks2;
+                               for(core::map<v3s16, bool>::Iterator
+                                               i = event->modified_blocks.getIterator();
+                                               i.atEnd()==false; i++)
+                               {
+                                       v3s16 p = i.getNode()->getKey();
+                                       modified_blocks2.insert(p,
+                                                       m_env.getMap().getBlockNoCreateNoEx(p));
+                               }
+                               // Set blocks not sent
+                               for(core::list<u16>::Iterator
+                                               i = far_players.begin();
+                                               i != far_players.end(); i++)
+                               {
+                                       u16 peer_id = *i;
+                                       RemoteClient *client = getClient(peer_id);
+                                       if(client==NULL)
+                                               continue;
+                                       client->SetBlocksNotSent(modified_blocks2);
+                               }
+                       }
 
                        delete event;
+
+                       /*// Don't send too many at a time
+                       count++;
+                       if(count >= 1 && m_unsent_map_edit_queue.size() < 100)
+                               break;*/
                }
+
+               if(got_any_events)
+               {
+                       dstream<<"Server: MapEditEvents:"<<std::endl;
+                       prof.print(dstream);
+               }
+               
        }
 
        /*
@@ -1682,39 +1807,6 @@ void Server::AsyncRunStep()
                }
        }
        
-       /*
-               Step node metadata
-               TODO: Move to ServerEnvironment and utilize active block stuff
-       */
-       /*{
-               //TimeTaker timer("Step node metadata");
-
-               JMutexAutoLock envlock(m_env_mutex);
-               JMutexAutoLock conlock(m_con_mutex);
-
-               ScopeProfiler sp(&g_profiler, "Server: stepping node metadata");
-
-               core::map<v3s16, MapBlock*> changed_blocks;
-               m_env.getMap().nodeMetadataStep(dtime, changed_blocks);
-               
-               // Use setBlockNotSent
-
-               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)
@@ -1746,26 +1838,29 @@ void Server::AsyncRunStep()
                        
                        // Map
                        JMutexAutoLock lock(m_env_mutex);
-                       if(((ServerMap*)(&m_env.getMap()))->isSavingEnabled() == true)
-                       {
-                               // Save only changed parts
-                               m_env.getMap().save(true);
 
-                               // 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;
-                               }
+                       /*// 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"));
+                                       */
 
-                               // Save players
-                               m_env.serializePlayers(m_mapsavedir);
-                               
-                               // Save environment metadata
-                               m_env.saveMeta(m_mapsavedir);
-                       }
+                       // Save only changed parts
+                       m_env.getMap().save(true);
+
+                       /*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);
                }
        }
 }
@@ -1871,6 +1966,27 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                        derr_server<<DTIME<<"Server: Cannot negotiate "
                                        "serialization version with peer "
                                        <<peer_id<<std::endl;
+                       SendAccessDenied(m_con, peer_id,
+                                       L"Your client is too old (map format)");
+                       return;
+               }
+               
+               /*
+                       Read and check network protocol version
+               */
+
+               u16 net_proto_version = 0;
+               if(datasize >= 2+1+PLAYERNAME_SIZE+PASSWORD_SIZE+2)
+               {
+                       net_proto_version = readU16(&data[2+1+PLAYERNAME_SIZE+PASSWORD_SIZE]);
+               }
+
+               getClient(peer->id)->net_proto_version = net_proto_version;
+
+               if(net_proto_version == 0)
+               {
+                       SendAccessDenied(m_con, peer_id,
+                                       L"Your client is too old. Please upgrade.");
                        return;
                }
 
@@ -1904,7 +2020,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 
                // Get password
                char password[PASSWORD_SIZE];
-               if(datasize == 2+1+PLAYERNAME_SIZE)
+               if(datasize < 2+1+PLAYERNAME_SIZE+PASSWORD_SIZE)
                {
                        // old version - assume blank password
                        password[0] = 0;
@@ -1928,7 +2044,10 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                        checkpwd = g_settings.get("default_password");
                }
                
-               if(password != checkpwd && checkpwd != "")
+               /*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 "
@@ -2068,6 +2187,12 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                        message += L" joined game";
                        BroadcastChatMessage(message);
                }
+               
+               // Warnings about protocol version can be issued here
+               /*if(getClient(peer->id)->net_proto_version == 0)
+               {
+                       SendChatMessage(peer_id, L"# Server: NOTE: YOUR CLIENT IS OLD AND DOES NOT WORK PROPERLY WITH THIS SERVER");
+               }*/
 
                return;
        }
@@ -2275,71 +2400,92 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                        return;
                }
 
+               // Skip if object has been removed
+               if(obj->m_removed)
+                       return;
+               
                //TODO: Check that object is reasonably close
                
                // Left click, pick object up (usually)
                if(button == 0)
                {
-                       InventoryList *ilist = player->inventory.getList("main");
-                       if(g_settings.getBool("creative_mode") == false && ilist != NULL)
-                       {
+                       /*
+                               Try creating inventory item
+                       */
+                       InventoryItem *item = obj->createPickedUpItem();
                        
-                               // Skip if inventory has no free space
-                               if(ilist->getUsedSlots() == ilist->getSize())
+                       if(item)
+                       {
+                               InventoryList *ilist = player->inventory.getList("main");
+                               if(ilist != NULL)
                                {
-                                       dout_server<<"Player inventory has no free space"<<std::endl;
-                                       return;
-                               }
+                                       if(g_settings.getBool("creative_mode") == false)
+                                       {
+                                               // Skip if inventory has no free space
+                                               if(ilist->getUsedSlots() == ilist->getSize())
+                                               {
+                                                       dout_server<<"Player inventory has no free space"<<std::endl;
+                                                       return;
+                                               }
 
-                               // Skip if object has been removed
-                               if(obj->m_removed)
-                                       return;
-                               
-                               /*
-                                       Create the inventory item
-                               */
-                               InventoryItem *item = obj->createPickedUpItem();
-                               
-                               if(item)
-                               {
-                                       // Add to inventory and send inventory
-                                       ilist->addItem(item);
-                                       UpdateCrafting(player->peer_id);
-                                       SendInventory(player->peer_id);
+                                               // Add to inventory and send inventory
+                                               ilist->addItem(item);
+                                               UpdateCrafting(player->peer_id);
+                                               SendInventory(player->peer_id);
+                                       }
 
                                        // Remove object from environment
                                        obj->m_removed = true;
                                }
-                               else
-                               {
-                                       /*
-                                               Item cannot be picked up. Punch it instead.
-                                       */
+                       }
+                       else
+                       {
+                               /*
+                                       Item cannot be picked up. Punch it instead.
+                               */
 
-                                       ToolItem *titem = NULL;
-                                       std::string toolname = "";
+                               ToolItem *titem = NULL;
+                               std::string toolname = "";
 
-                                       InventoryList *mlist = player->inventory.getList("main");
-                                       if(mlist != NULL)
-                                       {
-                                               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);
                        }
                }
        }
@@ -2404,7 +2550,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                        // Mandatory parameter; actually used for nothing
                        core::map<v3s16, MapBlock*> modified_blocks;
 
-                       u8 material = CONTENT_IGNORE;
+                       content_t material = CONTENT_IGNORE;
                        u8 mineral = MINERAL_NONE;
 
                        bool cannot_remove_node = false;
@@ -2415,7 +2561,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                                // Get mineral
                                mineral = n.getMineral();
                                // Get material at position
-                               material = n.d;
+                               material = n.getContent();
                                // If not yet cancelled
                                if(cannot_remove_node == false)
                                {
@@ -2483,12 +2629,12 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                        }
                        
                        /*
-                               Send the removal to all other clients.
+                               Send the removal to all close-by players.
                                - If other player is close, send REMOVENODE
                                - Otherwise set blocks not sent
                        */
                        core::list<u16> far_players;
-                       sendRemoveNode(p_under, peer_id, &far_players, 100);
+                       sendRemoveNode(p_under, peer_id, &far_players, 30);
                        
                        /*
                                Update and send inventory
@@ -2563,10 +2709,11 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                                Remove the node
                                (this takes some time so it is done after the quick stuff)
                        */
-                       m_ignore_map_edit_events = true;
-                       m_env.getMap().removeNodeAndUpdate(p_under, modified_blocks);
-                       m_ignore_map_edit_events = false;
-                       
+                       {
+                               MapEditEventIgnorer ign(&m_ignore_map_edit_events);
+
+                               m_env.getMap().removeNodeAndUpdate(p_under, modified_blocks);
+                       }
                        /*
                                Set blocks not sent to far players
                        */
@@ -2614,7 +2761,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                                                        <<" because privileges are "<<getPlayerPrivs(player)
                                                        <<std::endl;
 
-                                       if(content_buildable_to(n2.d) == false
+                                       if(content_features(n2).buildable_to == false
                                                || no_enough_privs)
                                        {
                                                // Client probably has wrong data.
@@ -2645,15 +2792,37 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                                // Create node data
                                MaterialItem *mitem = (MaterialItem*)item;
                                MapNode n;
-                               n.d = mitem->getMaterial();
-                               if(content_features(n.d).wall_mounted)
-                                       n.dir = packDir(p_under - p_over);
-                               
+                               n.setContent(mitem->getMaterial());
+
+                               // Calculate direction for wall mounted stuff
+                               if(content_features(n).wall_mounted)
+                                       n.param2 = packDir(p_under - p_over);
+
+                               // Calculate the direction for furnaces and chests and stuff
+                               if(content_features(n).param_type == CPT_FACEDIR_SIMPLE)
+                               {
+                                       v3f playerpos = player->getPosition();
+                                       v3f blockpos = intToFloat(p_over, BS) - playerpos;
+                                       blockpos = blockpos.normalize();
+                                       n.param1 = 0;
+                                       if (fabs(blockpos.X) > fabs(blockpos.Z)) {
+                                               if (blockpos.X < 0)
+                                                       n.param1 = 3;
+                                               else
+                                                       n.param1 = 1;
+                                       } else {
+                                               if (blockpos.Z < 0)
+                                                       n.param1 = 2;
+                                               else
+                                                       n.param1 = 0;
+                                       }
+                               }
+
                                /*
-                                       Send to all players
+                                       Send to all close-by players
                                */
                                core::list<u16> far_players;
-                               sendAddNode(p_over, n, 0, &far_players, 100);
+                               sendAddNode(p_over, n, 0, &far_players, 30);
                                
                                /*
                                        Handle inventory
@@ -2677,10 +2846,11 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                                        This takes some time so it is done after the quick stuff
                                */
                                core::map<v3s16, MapBlock*> modified_blocks;
-                               m_ignore_map_edit_events = true;
-                               m_env.getMap().addNodeAndUpdate(p_over, n, modified_blocks);
-                               m_ignore_map_edit_events = false;
-                               
+                               {
+                                       MapEditEventIgnorer ign(&m_ignore_map_edit_events);
+
+                                       m_env.getMap().addNodeAndUpdate(p_over, n, modified_blocks);
+                               }
                                /*
                                        Set blocks not sent to far players
                                */
@@ -3058,9 +3228,14 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                        line += L"Server: ";
 
                        message = message.substr(commandprefix.size());
+                       
+                       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,
@@ -3178,6 +3353,9 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                        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)
@@ -3189,7 +3367,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                }
 
                std::string checkpwd = m_authmanager.getPassword(playername);
-               
+
                if(oldpwd != checkpwd)
                {
                        dstream<<"Server: invalid old password"<<std::endl;
@@ -3221,7 +3399,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 
 void Server::onMapEditEvent(MapEditEvent *event)
 {
-       dstream<<"Server::onMapEditEvent()"<<std::endl;
+       //dstream<<"Server::onMapEditEvent()"<<std::endl;
        if(m_ignore_map_edit_events)
                return;
        MapEditEvent *e = event->clone();
@@ -3880,17 +4058,25 @@ std::wstring Server::getStatusString()
        }
        os<<L"}";
        if(((ServerMap*)(&m_env.getMap()))->isSavingEnabled() == false)
-               os<<" WARNING: Map saving is disabled."<<std::endl;
+               os<<std::endl<<L"# Server: "<<" WARNING: Map saving is disabled.";
+       if(g_settings.get("motd") != "")
+               os<<std::endl<<L"# Server: "<<narrow_to_wide(g_settings.get("motd"));
        return os.str();
 }
 
 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++)
        {
@@ -3920,6 +4106,7 @@ v3f findSpawnPos(ServerMap &map)
                //dstream<<"Searched through "<<i<<" places."<<std::endl;
                break;
        }
+#endif
        
        // If no suitable place was not found, go above water at least.
        if(groundheight < WATER_LEVEL)
@@ -3927,7 +4114,7 @@ v3f findSpawnPos(ServerMap &map)
 
        return intToFloat(v3s16(
                        nodepos.X,
-                       groundheight + 2,
+                       groundheight + 3,
                        nodepos.Y
                        ), BS);
 }
@@ -3953,6 +4140,11 @@ Player *Server::emergePlayer(const char *name, const char *password, u16 peer_id
                // Reset inventory to creative if in creative mode
                if(g_settings.getBool("creative_mode"))
                {
+                       // 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);
                }
 
@@ -4006,6 +4198,11 @@ Player *Server::emergePlayer(const char *name, const char *password, u16 peer_id
                
                if(g_settings.getBool("creative_mode"))
                {
+                       // 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"))