Merge pull request #503 from RealBadAngel/master
[oweals/minetest.git] / src / environment.cpp
index ce81a1941a94e95b44646366fa7ced3e8be68b0a..ebf5e9a63cf0499ecc0d7961912f49afd99b88de 100644 (file)
@@ -3,16 +3,16 @@ Minetest-c55
 Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
 
 This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
+GNU Lesser General Public License for more details.
 
-You should have received a copy of the GNU General Public License along
+You should have received a copy of the GNU Lesser General Public License along
 with this program; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
@@ -37,12 +37,21 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "nodemetadata.h"
 #include "main.h" // For g_settings, g_profiler
 #include "gamedef.h"
-#include "serverremoteplayer.h"
+#ifndef SERVER
+#include "clientmap.h"
+#include "localplayer.h"
+#endif
+#include "daynightratio.h"
+#include "map.h"
+#include "util/serialize.h"
 
 #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
 
 Environment::Environment():
-       m_time_of_day(9000)
+       m_time_of_day(9000),
+       m_time_of_day_f(9000./24000),
+       m_time_of_day_speed(0),
+       m_time_counter(0)
 {
 }
 
@@ -192,15 +201,50 @@ void Environment::printPlayers(std::ostream &o)
        }
 }
 
-/*void Environment::setDayNightRatio(u32 r)
+u32 Environment::getDayNightRatio()
 {
-       getDayNightRatio() = r;
-}*/
+       bool smooth = (g_settings->getS32("enable_shaders") != 0);
+       return time_to_daynight_ratio(m_time_of_day_f*24000, smooth);
+}
 
-u32 Environment::getDayNightRatio()
+void Environment::stepTimeOfDay(float dtime)
+{
+       m_time_counter += dtime;
+       f32 speed = m_time_of_day_speed * 24000./(24.*3600);
+       u32 units = (u32)(m_time_counter*speed);
+       m_time_counter -= (f32)units / speed;
+       bool sync_f = false;
+       if(units > 0){
+               // Sync at overflow
+               if(m_time_of_day + units >= 24000)
+                       sync_f = true;
+               m_time_of_day = (m_time_of_day + units) % 24000;
+               if(sync_f)
+                       m_time_of_day_f = (float)m_time_of_day / 24000.0;
+       }
+       if(!sync_f){
+               m_time_of_day_f += m_time_of_day_speed/24/3600*dtime;
+               if(m_time_of_day_f > 1.0)
+                       m_time_of_day_f -= 1.0;
+               if(m_time_of_day_f < 0.0)
+                       m_time_of_day_f += 1.0;
+       }
+}
+
+/*
+       ABMWithState
+*/
+
+ABMWithState::ABMWithState(ActiveBlockModifier *abm_):
+       abm(abm_),
+       timer(0)
 {
-       //return getDayNightRatio();
-       return time_to_daynight_ratio(m_time_of_day);
+       // Initialize timer to random value to spread processing
+       float itv = abm->getTriggerInterval();
+       itv = MYMAX(0.001, itv); // No less than 1ms
+       int minval = MYMAX(-0.51*itv, -60); // Clamp to
+       int maxval = MYMIN(0.51*itv, 60);   // +-60 seconds
+       timer = myrand_range(minval, maxval);
 }
 
 /*
@@ -284,8 +328,10 @@ ServerEnvironment::ServerEnvironment(ServerMap *map, lua_State *L,
        m_emerger(emerger),
        m_random_spawn_timer(3),
        m_send_recommended_timer(0),
+       m_active_block_interval_overload_skip(0),
        m_game_time(0),
-       m_game_time_fraction_counter(0)
+       m_game_time_fraction_counter(0),
+       m_recommended_send_interval(0.1)
 {
 }
 
@@ -308,6 +354,17 @@ ServerEnvironment::~ServerEnvironment()
        }
 }
 
+Map & ServerEnvironment::getMap()
+{
+       return *m_map;
+}
+
+ServerMap & ServerEnvironment::getServerMap()
+{
+       return *m_map;
+}
+
+
 void ServerEnvironment::serializePlayers(const std::string &savedir)
 {
        std::string players_path = savedir + "/players";
@@ -327,7 +384,7 @@ void ServerEnvironment::serializePlayers(const std::string &savedir)
                //infostream<<"Checking player file "<<path<<std::endl;
 
                // Load player to see what is its name
-               ServerRemotePlayer testplayer(this);
+               RemotePlayer testplayer(m_gamedef);
                {
                        // Open file and deserialize
                        std::ifstream is(path.c_str(), std::ios_base::binary);
@@ -438,10 +495,10 @@ void ServerEnvironment::deSerializePlayers(const std::string &savedir)
                // Full path to this file
                std::string path = players_path + "/" + player_files[i].name;
 
-               infostream<<"Checking player file "<<path<<std::endl;
+               //infostream<<"Checking player file "<<path<<std::endl;
 
                // Load player to see what is its name
-               ServerRemotePlayer testplayer(this);
+               RemotePlayer testplayer(m_gamedef);
                {
                        // Open file and deserialize
                        std::ifstream is(path.c_str(), std::ios_base::binary);
@@ -459,8 +516,8 @@ void ServerEnvironment::deSerializePlayers(const std::string &savedir)
                                        <<testplayer.getName()<<std::endl;
                }
 
-               infostream<<"Loaded test player with name "<<testplayer.getName()
-                               <<std::endl;
+               /*infostream<<"Loaded test player with name "<<testplayer.getName()
+                               <<std::endl;*/
                
                // Search for the player
                std::string playername = testplayer.getName();
@@ -468,16 +525,14 @@ void ServerEnvironment::deSerializePlayers(const std::string &savedir)
                bool newplayer = false;
                if(player == NULL)
                {
-                       infostream<<"Is a new player"<<std::endl;
-                       player = new ServerRemotePlayer(this);
+                       //infostream<<"Is a new player"<<std::endl;
+                       player = new RemotePlayer(m_gamedef);
                        newplayer = true;
                }
 
-               ServerRemotePlayer *srp = static_cast<ServerRemotePlayer*>(player);
-
                // Load player
                {
-                       infostream<<"Reading player "<<testplayer.getName()<<" from "
+                       verbosestream<<"Reading player "<<testplayer.getName()<<" from "
                                        <<path<<std::endl;
                        // Open file and deserialize
                        std::ifstream is(path.c_str(), std::ios_base::binary);
@@ -486,9 +541,7 @@ void ServerEnvironment::deSerializePlayers(const std::string &savedir)
                                infostream<<"Failed to read "<<path<<std::endl;
                                continue;
                        }
-                       srp->deSerialize(is);
-                       srp->m_last_good_position = srp->getBasePosition();
-                       srp->m_last_good_position_age = 0;
+                       player->deSerialize(is);
                }
 
                if(newplayer)
@@ -565,6 +618,7 @@ struct ActiveABM
 {
        ActiveBlockModifier *abm;
        int chance;
+       std::set<content_t> required_neighbors;
 };
 
 class ABMHandler
@@ -587,35 +641,54 @@ public:
                        float trigger_interval = abm->getTriggerInterval();
                        if(trigger_interval < 0.001)
                                trigger_interval = 0.001;
+                       float actual_interval = dtime_s;
                        if(use_timers){
                                i->timer += dtime_s;
                                if(i->timer < trigger_interval)
                                        continue;
                                i->timer -= trigger_interval;
+                               actual_interval = trigger_interval;
                        }
-                       ActiveABM aabm;
-                       aabm.abm = abm;
-                       float intervals = dtime_s / trigger_interval;
+                       float intervals = actual_interval / trigger_interval;
+                       if(intervals == 0)
+                               continue;
                        float chance = abm->getTriggerChance();
                        if(chance == 0)
                                chance = 1;
-                       aabm.chance = 1.0 / pow((float)1.0/chance, (float)intervals);
+                       ActiveABM aabm;
+                       aabm.abm = abm;
+                       aabm.chance = chance / intervals;
                        if(aabm.chance == 0)
                                aabm.chance = 1;
+                       // Trigger neighbors
+                       std::set<std::string> required_neighbors_s
+                                       = abm->getRequiredNeighbors();
+                       for(std::set<std::string>::iterator
+                                       i = required_neighbors_s.begin();
+                                       i != required_neighbors_s.end(); i++)
+                       {
+                               ndef->getIds(*i, aabm.required_neighbors);
+                       }
+                       // Trigger contents
                        std::set<std::string> contents_s = abm->getTriggerContents();
                        for(std::set<std::string>::iterator
-                                       i = contents_s.begin(); i != contents_s.end(); i++){
-                               content_t c = ndef->getId(*i);
-                               if(c == CONTENT_IGNORE)
-                                       continue;
-                               std::map<content_t, std::list<ActiveABM> >::iterator j;
-                               j = m_aabms.find(c);
-                               if(j == m_aabms.end()){
-                                       std::list<ActiveABM> aabmlist;
-                                       m_aabms[c] = aabmlist;
+                                       i = contents_s.begin(); i != contents_s.end(); i++)
+                       {
+                               std::set<content_t> ids;
+                               ndef->getIds(*i, ids);
+                               for(std::set<content_t>::const_iterator k = ids.begin();
+                                               k != ids.end(); k++)
+                               {
+                                       content_t c = *k;
+                                       std::map<content_t, std::list<ActiveABM> >::iterator j;
                                        j = m_aabms.find(c);
+                                       if(j == m_aabms.end()){
+                                               std::list<ActiveABM> aabmlist;
+                                               m_aabms[c] = aabmlist;
+                                               j = m_aabms.find(c);
+                                       }
+                                       j->second.push_back(aabm);
                                }
-                               j->second.push_back(aabm);
                        }
                }
        }
@@ -646,23 +719,52 @@ public:
                                if(myrand() % i->chance != 0)
                                        continue;
 
+                               // Check neighbors
+                               if(!i->required_neighbors.empty())
+                               {
+                                       v3s16 p1;
+                                       for(p1.X = p.X-1; p1.X <= p.X+1; p1.X++)
+                                       for(p1.Y = p.Y-1; p1.Y <= p.Y+1; p1.Y++)
+                                       for(p1.Z = p.Z-1; p1.Z <= p.Z+1; p1.Z++)
+                                       {
+                                               if(p1 == p)
+                                                       continue;
+                                               MapNode n = map->getNodeNoEx(p1);
+                                               content_t c = n.getContent();
+                                               std::set<content_t>::const_iterator k;
+                                               k = i->required_neighbors.find(c);
+                                               if(k != i->required_neighbors.end()){
+                                                       goto neighbor_found;
+                                               }
+                                       }
+                                       // No required neighbor found
+                                       continue;
+                               }
+neighbor_found:
+
                                // Find out how many objects the block contains
                                u32 active_object_count = block->m_static_objects.m_active.size();
                                // Find out how many objects this and all the neighbors contain
                                u32 active_object_count_wider = 0;
+                               u32 wider_unknown_count = 0;
                                for(s16 x=-1; x<=1; x++)
                                for(s16 y=-1; y<=1; y++)
                                for(s16 z=-1; z<=1; z++)
                                {
                                        MapBlock *block2 = map->getBlockNoCreateNoEx(
                                                        block->getPos() + v3s16(x,y,z));
-                                       if(block2==NULL)
+                                       if(block2==NULL){
+                                               wider_unknown_count = 0;
                                                continue;
+                                       }
                                        active_object_count_wider +=
                                                        block2->m_static_objects.m_active.size()
                                                        + block2->m_static_objects.m_stored.size();
                                }
-
+                               // Extrapolate
+                               u32 wider_known_count = 3*3*3 - wider_unknown_count;
+                               active_object_count_wider += wider_unknown_count * active_object_count_wider / wider_known_count;
+                               
                                // Call all the trigger variations
                                i->abm->trigger(m_env, p, n);
                                i->abm->trigger(m_env, p, n,
@@ -691,19 +793,21 @@ void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime)
                        <<dtime_s<<" seconds old."<<std::endl;*/
        
        // Activate stored objects
-       activateObjects(block);
-
-       // Run node metadata
-       bool changed = block->m_node_metadata->step((float)dtime_s);
-       if(changed)
-       {
-               MapEditEvent event;
-               event.type = MEET_BLOCK_NODE_METADATA_CHANGED;
-               event.p = block->getPos();
-               m_map->dispatchEvent(&event);
-
-               block->raiseModified(MOD_STATE_WRITE_NEEDED,
-                               "node metadata modified in activateBlock");
+       activateObjects(block, dtime_s);
+
+       // Run node timers
+       std::map<v3s16, NodeTimer> elapsed_timers =
+               block->m_node_timers.step((float)dtime_s);
+       if(!elapsed_timers.empty()){
+               MapNode n;
+               for(std::map<v3s16, NodeTimer>::iterator
+                               i = elapsed_timers.begin();
+                               i != elapsed_timers.end(); i++){
+                       n = block->getNodeNoEx(i->first);
+                       v3s16 p = i->first + block->getPosRelative();
+                       if(scriptapi_node_on_timer(m_lua,p,n,i->second.elapsed))
+                               block->setNodeTimer(i->first,NodeTimer(i->second.timeout,0));
+               }
        }
 
        /* Handle ActiveBlockModifiers */
@@ -716,6 +820,62 @@ void ServerEnvironment::addActiveBlockModifier(ActiveBlockModifier *abm)
        m_abms.push_back(ABMWithState(abm));
 }
 
+bool ServerEnvironment::setNode(v3s16 p, const MapNode &n)
+{
+       INodeDefManager *ndef = m_gamedef->ndef();
+       MapNode n_old = m_map->getNodeNoEx(p);
+       // Call destructor
+       if(ndef->get(n_old).has_on_destruct)
+               scriptapi_node_on_destruct(m_lua, p, n_old);
+       // Replace node
+       bool succeeded = m_map->addNodeWithEvent(p, n);
+       if(!succeeded)
+               return false;
+       // Call post-destructor
+       if(ndef->get(n_old).has_after_destruct)
+               scriptapi_node_after_destruct(m_lua, p, n_old);
+       // Call constructor
+       if(ndef->get(n).has_on_construct)
+               scriptapi_node_on_construct(m_lua, p, n);
+       return true;
+}
+
+bool ServerEnvironment::removeNode(v3s16 p)
+{
+       INodeDefManager *ndef = m_gamedef->ndef();
+       MapNode n_old = m_map->getNodeNoEx(p);
+       // Call destructor
+       if(ndef->get(n_old).has_on_destruct)
+               scriptapi_node_on_destruct(m_lua, p, n_old);
+       // Replace with air
+       // This is slightly optimized compared to addNodeWithEvent(air)
+       bool succeeded = m_map->removeNodeWithEvent(p);
+       if(!succeeded)
+               return false;
+       // Call post-destructor
+       if(ndef->get(n_old).has_after_destruct)
+               scriptapi_node_after_destruct(m_lua, p, n_old);
+       // Air doesn't require constructor
+       return true;
+}
+
+std::set<u16> ServerEnvironment::getObjectsInsideRadius(v3f pos, float radius)
+{
+       std::set<u16> objects;
+       for(core::map<u16, ServerActiveObject*>::Iterator
+                       i = m_active_objects.getIterator();
+                       i.atEnd()==false; i++)
+       {
+               ServerActiveObject* obj = i.getNode()->getValue();
+               u16 id = i.getNode()->getKey();
+               v3f objectpos = obj->getBasePosition();
+               if(objectpos.getDistanceFrom(pos) > radius)
+                       continue;
+               objects.insert(id);
+       }
+       return objects;
+}
+
 void ServerEnvironment::clearAllObjects()
 {
        infostream<<"ServerEnvironment::clearAllObjects(): "
@@ -726,6 +886,8 @@ void ServerEnvironment::clearAllObjects()
                        i.atEnd()==false; i++)
        {
                ServerActiveObject* obj = i.getNode()->getValue();
+               if(obj->getType() == ACTIVEOBJECT_TYPE_PLAYER)
+                       continue;
                u16 id = i.getNode()->getKey();         
                v3f objectpos = obj->getBasePosition(); 
                // Delete static object if block is loaded
@@ -817,8 +979,13 @@ void ServerEnvironment::step(float dtime)
        
        //TimeTaker timer("ServerEnv step");
 
-       // Get some settings
-       bool footprints = g_settings->getBool("footprints");
+       /* Step time of day */
+       stepTimeOfDay(dtime);
+
+       // Update this one
+       // NOTE: This is kind of funny on a singleplayer game, but doesn't
+       // really matter that much.
+       m_recommended_send_interval = g_settings->getFloat("dedicated_server_step");
 
        /*
                Increment game time
@@ -848,26 +1015,6 @@ void ServerEnvironment::step(float dtime)
                        
                        // Move
                        player->move(dtime, *m_map, 100*BS);
-                       
-                       /*
-                               Add footsteps to grass
-                       */
-                       if(footprints)
-                       {
-                               // Get node that is at BS/4 under player
-                               v3s16 bottompos = floatToInt(playerpos + v3f(0,-BS/4,0), BS);
-                               try{
-                                       MapNode n = m_map->getNode(bottompos);
-                                       if(n.getContent() == LEGN(m_gamedef->ndef(), "CONTENT_GRASS"))
-                                       {
-                                               n.setContent(LEGN(m_gamedef->ndef(), "CONTENT_GRASS_FOOTSTEPS"));
-                                               m_map->setNode(bottompos, n);
-                                       }
-                               }
-                               catch(InvalidPositionException &e)
-                               {
-                               }
-                       }
                }
        }
 
@@ -985,24 +1132,31 @@ void ServerEnvironment::step(float dtime)
                                block->raiseModified(MOD_STATE_WRITE_AT_UNLOAD,
                                                "Timestamp older than 60s (step)");
 
-                       // Run node metadata
-                       bool changed = block->m_node_metadata->step(dtime);
-                       if(changed)
-                       {
-                               MapEditEvent event;
-                               event.type = MEET_BLOCK_NODE_METADATA_CHANGED;
-                               event.p = p;
-                               m_map->dispatchEvent(&event);
-
-                               block->raiseModified(MOD_STATE_WRITE_NEEDED,
-                                               "node metadata modified in step");
+                       // Run node timers
+                       std::map<v3s16, NodeTimer> elapsed_timers =
+                               block->m_node_timers.step((float)dtime);
+                       if(!elapsed_timers.empty()){
+                               MapNode n;
+                               for(std::map<v3s16, NodeTimer>::iterator
+                                               i = elapsed_timers.begin();
+                                               i != elapsed_timers.end(); i++){
+                                       n = block->getNodeNoEx(i->first);
+                                       p = i->first + block->getPosRelative();
+                                       if(scriptapi_node_on_timer(m_lua,p,n,i->second.elapsed))
+                                               block->setNodeTimer(i->first,NodeTimer(i->second.timeout,0));
+                               }
                        }
                }
        }
        
        const float abm_interval = 1.0;
        if(m_active_block_modifier_interval.step(dtime, abm_interval))
-       {
+       do{ // breakable
+               if(m_active_block_interval_overload_skip > 0){
+                       ScopeProfiler sp(g_profiler, "SEnv: ABM overload skips");
+                       m_active_block_interval_overload_skip--;
+                       break;
+               }
                ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg /1s", SPT_AVG);
                TimeTaker timer("modify in active blocks");
                
@@ -1035,8 +1189,9 @@ void ServerEnvironment::step(float dtime)
                        infostream<<"WARNING: active block modifiers took "
                                        <<time_ms<<"ms (longer than "
                                        <<max_time_ms<<"ms)"<<std::endl;
+                       m_active_block_interval_overload_skip = (time_ms / max_time_ms) + 1;
                }
-       }
+       }while(0);
        
        /*
                Step script environment (run global on_step())
@@ -1142,10 +1297,11 @@ u16 getFreeServerActiveObjectId(
 u16 ServerEnvironment::addActiveObject(ServerActiveObject *object)
 {
        assert(object);
-       u16 id = addActiveObjectRaw(object, true);
+       u16 id = addActiveObjectRaw(object, true, 0);
        return id;
 }
 
+#if 0
 bool ServerEnvironment::addActiveObjectAsStatic(ServerActiveObject *obj)
 {
        assert(obj);
@@ -1188,6 +1344,7 @@ bool ServerEnvironment::addActiveObjectAsStatic(ServerActiveObject *obj)
 
        return succeeded;
 }
+#endif
 
 /*
        Finds out what new objects have been added to
@@ -1301,7 +1458,7 @@ ActiveObjectMessage ServerEnvironment::getActiveObjectMessage()
 */
 
 u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object,
-               bool set_changed)
+               bool set_changed, u32 dtime_s)
 {
        assert(object);
        if(object->getId() == 0){
@@ -1341,7 +1498,7 @@ u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object,
        // Register reference in scripting api (must be done before post-init)
        scriptapi_add_object_reference(m_lua, object);
        // Post-initialize object
-       object->addedToEnvironment();
+       object->addedToEnvironment(dtime_s);
        
        // Add static data to block
        if(object->isStaticAllowed())
@@ -1364,9 +1521,10 @@ u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object,
                                                "addActiveObjectRaw");
                }
                else{
+                       v3s16 p = floatToInt(objectpos, BS);
                        errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
                                        <<"could not find block for storing id="<<object->getId()
-                                       <<" statically"<<std::endl;
+                                       <<" statically (pos="<<PP(p)<<")"<<std::endl;
                }
        }
        
@@ -1407,13 +1565,15 @@ void ServerEnvironment::removeRemovedObjects()
                */
                if(obj->m_static_exists && obj->m_removed)
                {
-                       MapBlock *block = m_map->emergeBlock(obj->m_static_block);
-                       if(block)
-                       {
+                       MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
+                       if (block) {
                                block->m_static_objects.remove(id);
                                block->raiseModified(MOD_STATE_WRITE_NEEDED,
                                                "removeRemovedObjects");
                                obj->m_static_exists = false;
+                       } else {
+                               infostream << "failed to emerge block from which "
+                                       "an object to be removed was loaded from. id="<<id<<std::endl;
                        }
                }
 
@@ -1477,7 +1637,7 @@ static void print_hexdump(std::ostream &o, const std::string &data)
 /*
        Convert stored objects from blocks near the players to active.
 */
-void ServerEnvironment::activateObjects(MapBlock *block)
+void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s)
 {
        if(block==NULL)
                return;
@@ -1501,7 +1661,7 @@ void ServerEnvironment::activateObjects(MapBlock *block)
                                "large amount of objects");
                return;
        }
-       // A list for objects that couldn't be converted to static for some
+       // A list for objects that couldn't be converted to active for some
        // reason. They will be stored back.
        core::list<StaticObject> new_stored;
        // Loop through stored static objects
@@ -1531,7 +1691,7 @@ void ServerEnvironment::activateObjects(MapBlock *block)
                                <<"activated static object pos="<<PP(s_obj.pos/BS)
                                <<" type="<<(int)s_obj.type<<std::endl;
                // This will also add the object to the active static list
-               addActiveObjectRaw(obj, false);
+               addActiveObjectRaw(obj, false, dtime_s);
        }
        // Clear stored list
        block->m_static_objects.m_stored.clear();
@@ -1561,6 +1721,8 @@ void ServerEnvironment::activateObjects(MapBlock *block)
 
        If force_delete is set, active object is deleted nevertheless. It
        shall only be set so in the destructor of the environment.
+
+       If block wasn't generated (not in memory or on disk), 
 */
 void ServerEnvironment::deactivateFarObjects(bool force_delete)
 {
@@ -1655,7 +1817,12 @@ void ServerEnvironment::deactivateFarObjects(bool force_delete)
                        // Add to the block where the object is located in
                        v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
                        // Get or generate the block
-                       MapBlock *block = m_map->emergeBlock(blockpos);
+                       MapBlock *block = NULL;
+                       try{
+                               block = m_map->emergeBlock(blockpos);
+                       } catch(InvalidPositionException &e){
+                               // Handled via NULL pointer
+                       }
 
                        if(block)
                        {
@@ -1669,6 +1836,15 @@ void ServerEnvironment::deactivateFarObjects(bool force_delete)
                                        force_delete = true;
                                } else {
                                        u16 new_id = pending_delete ? id : 0;
+                                       // If static counterpart already exists, remove it first.
+                                       // This shouldn't happen, but happens rarely for some
+                                       // unknown reason. Unsuccessful attempts have been made to
+                                       // find said reason.
+                                       if(new_id && block->m_static_objects.m_active.find(new_id)){
+                                               infostream<<"ServerEnv: WARNING: Performing hack #83274"
+                                                               <<std::endl;
+                                               block->m_static_objects.remove(new_id);
+                                       }
                                        block->m_static_objects.insert(new_id, s_obj);
                                        
                                        // Only mark block as modified if data changed considerably
@@ -1683,9 +1859,10 @@ void ServerEnvironment::deactivateFarObjects(bool force_delete)
                        }
                        else{
                                if(!force_delete){
+                                       v3s16 p = floatToInt(objectpos, BS);
                                        errorstream<<"ServerEnv: Could not find or generate "
                                                        <<"a block for storing id="<<obj->getId()
-                                                       <<" statically"<<std::endl;
+                                                       <<" statically (pos="<<PP(p)<<")"<<std::endl;
                                        continue;
                                }
                        }
@@ -1733,6 +1910,8 @@ void ServerEnvironment::deactivateFarObjects(bool force_delete)
 
 #ifndef SERVER
 
+#include "clientsimpleobject.h"
+
 /*
        ClientEnvironment
 */
@@ -1758,10 +1937,26 @@ ClientEnvironment::~ClientEnvironment()
                delete i.getNode()->getValue();
        }
 
+       for(core::list<ClientSimpleObject*>::Iterator
+                       i = m_simple_objects.begin(); i != m_simple_objects.end(); i++)
+       {
+               delete *i;
+       }
+
        // Drop/delete map
        m_map->drop();
 }
 
+Map & ClientEnvironment::getMap()
+{
+       return *m_map;
+}
+
+ClientMap & ClientEnvironment::getClientMap()
+{
+       return *m_map;
+}
+
 void ClientEnvironment::addPlayer(Player *player)
 {
        DSTACK(__FUNCTION_NAME);
@@ -1790,9 +1985,12 @@ void ClientEnvironment::step(float dtime)
 {
        DSTACK(__FUNCTION_NAME);
 
+       /* Step time of day */
+       stepTimeOfDay(dtime);
+
        // Get some settings
-       bool free_move = g_settings->getBool("free_move");
-       bool footprints = g_settings->getBool("footprints");
+       bool fly_allowed = m_gamedef->checkLocalPrivilege("fly");
+       bool free_move = fly_allowed && g_settings->getBool("free_move");
 
        // Get local player
        LocalPlayer *lplayer = getLocalPlayer();
@@ -1867,20 +2065,37 @@ void ClientEnvironment::step(float dtime)
                        {
                                // Gravity
                                v3f speed = lplayer->getSpeed();
-                               if(lplayer->swimming_up == false)
-                                       speed.Y -= 9.81 * BS * dtime_part * 2;
+                               if(lplayer->in_liquid == false)
+                                       speed.Y -= lplayer->movement_gravity * dtime_part * 2;
 
-                               // Water resistance
-                               if(lplayer->in_water_stable || lplayer->in_water)
-                               {
-                                       f32 max_down = 2.0*BS;
-                                       if(speed.Y < -max_down) speed.Y = -max_down;
+                               // Liquid floating / sinking
+                               if(lplayer->in_liquid && !lplayer->swimming_vertical)
+                                       speed.Y -= lplayer->movement_liquid_sink * dtime_part * 2;
 
-                                       f32 max = 2.5*BS;
-                                       if(speed.getLength() > max)
-                                       {
-                                               speed = speed / speed.getLength() * max;
-                                       }
+                               // Liquid resistance
+                               if(lplayer->in_liquid_stable || lplayer->in_liquid)
+                               {
+                                       // How much the node's viscosity blocks movement, ranges between 0 and 1
+                                       // Should match the scale at which viscosity increase affects other liquid attributes
+                                       const f32 viscosity_factor = 0.3;
+
+                                       v3f d_wanted = -speed / lplayer->movement_liquid_fluidity;
+                                       f32 dl = d_wanted.getLength();
+                                       if(dl > lplayer->movement_liquid_fluidity_smooth)
+                                               dl = lplayer->movement_liquid_fluidity_smooth;
+                                       dl *= (lplayer->liquid_viscosity * viscosity_factor) + (1 - viscosity_factor);
+                                       
+                                       v3f d = d_wanted.normalize() * dl;
+                                       speed += d;
+                                       
+#if 0 // old code
+                                       if(speed.X > lplayer->movement_liquid_fluidity + lplayer->movement_liquid_fluidity_smooth)      speed.X -= lplayer->movement_liquid_fluidity_smooth;
+                                       if(speed.X < -lplayer->movement_liquid_fluidity - lplayer->movement_liquid_fluidity_smooth)     speed.X += lplayer->movement_liquid_fluidity_smooth;
+                                       if(speed.Y > lplayer->movement_liquid_fluidity + lplayer->movement_liquid_fluidity_smooth)      speed.Y -= lplayer->movement_liquid_fluidity_smooth;
+                                       if(speed.Y < -lplayer->movement_liquid_fluidity - lplayer->movement_liquid_fluidity_smooth)     speed.Y += lplayer->movement_liquid_fluidity_smooth;
+                                       if(speed.Z > lplayer->movement_liquid_fluidity + lplayer->movement_liquid_fluidity_smooth)      speed.Z -= lplayer->movement_liquid_fluidity_smooth;
+                                       if(speed.Z < -lplayer->movement_liquid_fluidity - lplayer->movement_liquid_fluidity_smooth)     speed.Z += lplayer->movement_liquid_fluidity_smooth;
+#endif
                                }
 
                                lplayer->setSpeed(speed);
@@ -1903,25 +2118,32 @@ void ClientEnvironment::step(float dtime)
                        i != player_collisions.end(); i++)
        {
                CollisionInfo &info = *i;
-               if(info.t == COLLISION_FALL)
+               v3f speed_diff = info.new_speed - info.old_speed;;
+               // Handle only fall damage
+               // (because otherwise walking against something in fast_move kills you)
+               if(speed_diff.Y < 0 || info.old_speed.Y >= 0)
+                       continue;
+               // Get rid of other components
+               speed_diff.X = 0;
+               speed_diff.Z = 0;
+               f32 pre_factor = 1; // 1 hp per node/s
+               f32 tolerance = BS*14; // 5 without damage
+               f32 post_factor = 1; // 1 hp per node/s
+               if(info.type == COLLISION_NODE)
                {
-                       //f32 tolerance = BS*10; // 2 without damage
-                       f32 tolerance = BS*12; // 3 without damage
-                       f32 factor = 1;
-                       if(info.speed > tolerance)
-                       {
-                               f32 damage_f = (info.speed - tolerance)/BS*factor;
-                               u16 damage = (u16)(damage_f+0.5);
-                               if(lplayer->hp > damage)
-                                       lplayer->hp -= damage;
-                               else
-                                       lplayer->hp = 0;
-
-                               ClientEnvEvent event;
-                               event.type = CEE_PLAYER_DAMAGE;
-                               event.player_damage.amount = damage;
-                               m_client_event_queue.push_back(event);
-                       }
+                       const ContentFeatures &f = m_gamedef->ndef()->
+                                       get(m_map->getNodeNoEx(info.node_p));
+                       // Determine fall damage multiplier
+                       int addp = itemgroup_get(f.groups, "fall_damage_add_percent");
+                       pre_factor = 1.0 + (float)addp/100.0;
+               }
+               float speed = pre_factor * speed_diff.getLength();
+               if(speed > tolerance)
+               {
+                       f32 damage_f = (speed - tolerance)/BS * post_factor;
+                       u16 damage = (u16)(damage_f+0.5);
+                       if(damage != 0)
+                               damageLocalPlayer(damage, true);
                }
        }
        
@@ -1950,10 +2172,7 @@ void ClientEnvironment::step(float dtime)
                
                if(damage_per_second != 0)
                {
-                       ClientEnvEvent event;
-                       event.type = CEE_PLAYER_DAMAGE;
-                       event.player_damage.amount = damage_per_second;
-                       m_client_event_queue.push_back(event);
+                       damageLocalPlayer(damage_per_second, true);
                }
        }
        
@@ -1977,49 +2196,24 @@ void ClientEnvironment::step(float dtime)
                }
                
                // Update lighting on all players on client
-               u8 light = LIGHT_MAX;
+               float light = 1.0;
                try{
                        // Get node at head
                        v3s16 p = player->getLightPosition();
                        MapNode n = m_map->getNode(p);
-                       light = n.getLightBlend(getDayNightRatio(), m_gamedef->ndef());
+                       light = n.getLightBlendF1((float)getDayNightRatio()/1000, m_gamedef->ndef());
                }
-               catch(InvalidPositionException &e) {}
-               player->updateLight(light);
-
-               /*
-                       Add footsteps to grass
-               */
-               if(footprints)
-               {
-                       // Get node that is at BS/4 under player
-                       v3s16 bottompos = floatToInt(playerpos + v3f(0,-BS/4,0), BS);
-                       try{
-                               MapNode n = m_map->getNode(bottompos);
-                               if(n.getContent() == LEGN(m_gamedef->ndef(), "CONTENT_GRASS"))
-                               {
-                                       n.setContent(LEGN(m_gamedef->ndef(), "CONTENT_GRASS_FOOTSTEPS"));
-                                       m_map->setNode(bottompos, n);
-                                       // Update mesh on client
-                                       if(m_map->mapType() == MAPTYPE_CLIENT)
-                                       {
-                                               v3s16 p_blocks = getNodeBlockPos(bottompos);
-                                               MapBlock *b = m_map->getBlockNoCreate(p_blocks);
-                                               //b->updateMesh(getDayNightRatio());
-                                               b->setMeshExpired(true);
-                                       }
-                               }
-                       }
-                       catch(InvalidPositionException &e)
-                       {
-                       }
+               catch(InvalidPositionException &e){
+                       light = blend_light_f1((float)getDayNightRatio()/1000, LIGHT_SUN, 0);
                }
+               player->light = light;
        }
        
        /*
                Step active objects and update lighting of them
        */
        
+       bool update_lighting = m_active_object_light_update_interval.step(dtime, 0.21);
        for(core::map<u16, ClientActiveObject*>::Iterator
                        i = m_active_objects.getIterator();
                        i.atEnd()==false; i++)
@@ -2028,10 +2222,9 @@ void ClientEnvironment::step(float dtime)
                // Step object
                obj->step(dtime, this);
 
-               if(m_active_object_light_update_interval.step(dtime, 0.21))
+               if(update_lighting)
                {
                        // Update lighting
-                       //u8 light = LIGHT_MAX;
                        u8 light = 0;
                        try{
                                // Get node at head
@@ -2039,20 +2232,33 @@ void ClientEnvironment::step(float dtime)
                                MapNode n = m_map->getNode(p);
                                light = n.getLightBlend(getDayNightRatio(), m_gamedef->ndef());
                        }
-                       catch(InvalidPositionException &e) {}
+                       catch(InvalidPositionException &e){
+                               light = blend_light(getDayNightRatio(), LIGHT_SUN, 0);
+                       }
                        obj->updateLight(light);
                }
        }
-}
 
-void ClientEnvironment::updateMeshes(v3s16 blockpos)
-{
-       m_map->updateMeshes(blockpos, getDayNightRatio());
+       /*
+               Step and handle simple objects
+       */
+       for(core::list<ClientSimpleObject*>::Iterator
+                       i = m_simple_objects.begin(); i != m_simple_objects.end();)
+       {
+               ClientSimpleObject *simple = *i;
+               core::list<ClientSimpleObject*>::Iterator cur = i;
+               i++;
+               simple->step(dtime);
+               if(simple->m_to_be_removed){
+                       delete simple;
+                       m_simple_objects.erase(cur);
+               }
+       }
 }
-
-void ClientEnvironment::expireMeshes(bool only_daynight_diffed)
+       
+void ClientEnvironment::addSimpleObject(ClientSimpleObject *simple)
 {
-       m_map->expireMeshes(only_daynight_diffed);
+       m_simple_objects.push_back(simple);
 }
 
 ClientActiveObject* ClientEnvironment::getActiveObject(u16 id)
@@ -2130,7 +2336,9 @@ u16 ClientEnvironment::addActiveObject(ClientActiveObject *object)
                        MapNode n = m_map->getNode(p);
                        light = n.getLightBlend(getDayNightRatio(), m_gamedef->ndef());
                }
-               catch(InvalidPositionException &e) {}
+               catch(InvalidPositionException &e){
+                       light = blend_light(getDayNightRatio(), LIGHT_SUN, 0);
+               }
                object->updateLight(light);
        }
        return object->getId();
@@ -2151,14 +2359,26 @@ void ClientEnvironment::addActiveObject(u16 id, u8 type,
        
        obj->setId(id);
 
-       obj->initialize(init_data);
-       
+       try
+       {
+               obj->initialize(init_data);
+       }
+       catch(SerializationError &e)
+       {
+               errorstream<<"ClientEnvironment::addActiveObject():"
+                               <<" id="<<id<<" type="<<type
+                               <<": SerializationError in initialize(): "
+                               <<e.what()
+                               <<": init_data="<<serializeJsonString(init_data)
+                               <<std::endl;
+       }
+
        addActiveObject(obj);
 }
 
 void ClientEnvironment::removeActiveObject(u16 id)
 {
-       infostream<<"ClientEnvironment::removeActiveObject(): "
+       verbosestream<<"ClientEnvironment::removeActiveObject(): "
                        <<"id="<<id<<std::endl;
        ClientActiveObject* obj = getActiveObject(id);
        if(obj == NULL)
@@ -2167,7 +2387,7 @@ void ClientEnvironment::removeActiveObject(u16 id)
                                <<"id="<<id<<" not found"<<std::endl;
                return;
        }
-       obj->removeFromScene();
+       obj->removeFromScene(true);
        delete obj;
        m_active_objects.remove(id);
 }
@@ -2183,7 +2403,18 @@ void ClientEnvironment::processActiveObjectMessage(u16 id,
                                <<std::endl;
                return;
        }
-       obj->processMessage(data);
+       try
+       {
+               obj->processMessage(data);
+       }
+       catch(SerializationError &e)
+       {
+               errorstream<<"ClientEnvironment::processActiveObjectMessage():"
+                               <<" id="<<id<<" type="<<obj->getType()
+                               <<" SerializationError in processMessage(),"
+                               <<" message="<<serializeJsonString(data)
+                               <<std::endl;
+       }
 }
 
 /*
@@ -2205,6 +2436,7 @@ void ClientEnvironment::damageLocalPlayer(u8 damage, bool handle_hp)
        ClientEnvEvent event;
        event.type = CEE_PLAYER_DAMAGE;
        event.player_damage.amount = damage;
+       event.player_damage.send_to_server = handle_hp;
        m_client_event_queue.push_back(event);
 }