rollback_interface.cpp
serialization.cpp
server.cpp
+ serverenvironment.cpp
serverlist.cpp
serverobject.cpp
settings.cpp
#include "settings.h"
#include "mapblock.h"
#include "network/connection.h"
-#include "environment.h"
+#include "serverenvironment.h"
#include "map.h"
#include "emerge.h"
#include "content_sao.h" // TODO this is used for cleanup of only
#include "nodedef.h"
#include "gamedef.h"
#include "clientenvironment.h"
+#include "serverenvironment.h"
#include "serverobject.h"
#include "profiler.h"
#include <fstream>
#include "environment.h"
-#include "filesys.h"
-#include "porting.h"
#include "collision.h"
-#include "content_mapnode.h"
-#include "mapblock.h"
#include "serverobject.h"
-#include "content_sao.h"
-#include "settings.h"
-#include "log.h"
-#include "profiler.h"
#include "scripting_game.h"
-#include "nodedef.h"
-#include "nodemetadata.h"
-#include "gamedef.h"
#include "server.h"
#include "daynightratio.h"
-#include "map.h"
#include "emerge.h"
-#include "raycast.h"
-#include "voxelalgorithms.h"
-#include "util/serialize.h"
-#include "util/basic_macros.h"
-#include "util/pointedthing.h"
-#include "threading/mutex_auto_lock.h"
-
-#define LBM_NAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_:"
-
-// A number that is much smaller than the timeout for particle spawners should/could ever be
-#define PARTICLE_SPAWNER_NO_EXPIRY -1024.f
Environment::Environment():
m_time_of_day_speed(0),
// Atomic<u32> counter
return m_day_count;
}
-
-
-/*
- ABMWithState
-*/
-
-ABMWithState::ABMWithState(ActiveBlockModifier *abm_):
- abm(abm_),
- timer(0)
-{
- // 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);
-}
-
-/*
- LBMManager
-*/
-
-void LBMContentMapping::deleteContents()
-{
- for (std::vector<LoadingBlockModifierDef *>::iterator it = lbm_list.begin();
- it != lbm_list.end(); ++it) {
- delete *it;
- }
-}
-
-void LBMContentMapping::addLBM(LoadingBlockModifierDef *lbm_def, IGameDef *gamedef)
-{
- // Add the lbm_def to the LBMContentMapping.
- // Unknown names get added to the global NameIdMapping.
- INodeDefManager *nodedef = gamedef->ndef();
-
- lbm_list.push_back(lbm_def);
-
- for (std::set<std::string>::const_iterator it = lbm_def->trigger_contents.begin();
- it != lbm_def->trigger_contents.end(); ++it) {
- std::set<content_t> c_ids;
- bool found = nodedef->getIds(*it, c_ids);
- if (!found) {
- content_t c_id = gamedef->allocateUnknownNodeId(*it);
- if (c_id == CONTENT_IGNORE) {
- // Seems it can't be allocated.
- warningstream << "Could not internalize node name \"" << *it
- << "\" while loading LBM \"" << lbm_def->name << "\"." << std::endl;
- continue;
- }
- c_ids.insert(c_id);
- }
-
- for (std::set<content_t>::const_iterator iit =
- c_ids.begin(); iit != c_ids.end(); ++iit) {
- content_t c_id = *iit;
- map[c_id].push_back(lbm_def);
- }
- }
-}
-
-const std::vector<LoadingBlockModifierDef *> *
- LBMContentMapping::lookup(content_t c) const
-{
- container_map::const_iterator it = map.find(c);
- if (it == map.end())
- return NULL;
- // This first dereferences the iterator, returning
- // a std::vector<LoadingBlockModifierDef *>
- // reference, then we convert it to a pointer.
- return &(it->second);
-}
-
-LBMManager::~LBMManager()
-{
- for (std::map<std::string, LoadingBlockModifierDef *>::iterator it =
- m_lbm_defs.begin(); it != m_lbm_defs.end(); ++it) {
- delete it->second;
- }
- for (lbm_lookup_map::iterator it = m_lbm_lookup.begin();
- it != m_lbm_lookup.end(); ++it) {
- (it->second).deleteContents();
- }
-}
-
-void LBMManager::addLBMDef(LoadingBlockModifierDef *lbm_def)
-{
- // Precondition, in query mode the map isn't used anymore
- FATAL_ERROR_IF(m_query_mode == true,
- "attempted to modify LBMManager in query mode");
-
- if (!string_allowed(lbm_def->name, LBM_NAME_ALLOWED_CHARS)) {
- throw ModError("Error adding LBM \"" + lbm_def->name +
- "\": Does not follow naming conventions: "
- "Only chararacters [a-z0-9_:] are allowed.");
- }
-
- m_lbm_defs[lbm_def->name] = lbm_def;
-}
-
-void LBMManager::loadIntroductionTimes(const std::string ×,
- IGameDef *gamedef, u32 now)
-{
- m_query_mode = true;
-
- // name -> time map.
- // Storing it in a map first instead of
- // handling the stuff directly in the loop
- // removes all duplicate entries.
- // TODO make this std::unordered_map
- std::map<std::string, u32> introduction_times;
-
- /*
- The introduction times string consists of name~time entries,
- with each entry terminated by a semicolon. The time is decimal.
- */
-
- size_t idx = 0;
- size_t idx_new;
- while ((idx_new = times.find(";", idx)) != std::string::npos) {
- std::string entry = times.substr(idx, idx_new - idx);
- std::vector<std::string> components = str_split(entry, '~');
- if (components.size() != 2)
- throw SerializationError("Introduction times entry \""
- + entry + "\" requires exactly one '~'!");
- const std::string &name = components[0];
- u32 time = from_string<u32>(components[1]);
- introduction_times[name] = time;
- idx = idx_new + 1;
- }
-
- // Put stuff from introduction_times into m_lbm_lookup
- for (std::map<std::string, u32>::const_iterator it = introduction_times.begin();
- it != introduction_times.end(); ++it) {
- const std::string &name = it->first;
- u32 time = it->second;
-
- std::map<std::string, LoadingBlockModifierDef *>::iterator def_it =
- m_lbm_defs.find(name);
- if (def_it == m_lbm_defs.end()) {
- // This seems to be an LBM entry for
- // an LBM we haven't loaded. Discard it.
- continue;
- }
- LoadingBlockModifierDef *lbm_def = def_it->second;
- if (lbm_def->run_at_every_load) {
- // This seems to be an LBM entry for
- // an LBM that runs at every load.
- // Don't add it just yet.
- continue;
- }
-
- m_lbm_lookup[time].addLBM(lbm_def, gamedef);
-
- // Erase the entry so that we know later
- // what elements didn't get put into m_lbm_lookup
- m_lbm_defs.erase(name);
- }
-
- // Now also add the elements from m_lbm_defs to m_lbm_lookup
- // that weren't added in the previous step.
- // They are introduced first time to this world,
- // or are run at every load (introducement time hardcoded to U32_MAX).
-
- LBMContentMapping &lbms_we_introduce_now = m_lbm_lookup[now];
- LBMContentMapping &lbms_running_always = m_lbm_lookup[U32_MAX];
-
- for (std::map<std::string, LoadingBlockModifierDef *>::iterator it =
- m_lbm_defs.begin(); it != m_lbm_defs.end(); ++it) {
- if (it->second->run_at_every_load) {
- lbms_running_always.addLBM(it->second, gamedef);
- } else {
- lbms_we_introduce_now.addLBM(it->second, gamedef);
- }
- }
-
- // Clear the list, so that we don't delete remaining elements
- // twice in the destructor
- m_lbm_defs.clear();
-}
-
-std::string LBMManager::createIntroductionTimesString()
-{
- // Precondition, we must be in query mode
- FATAL_ERROR_IF(m_query_mode == false,
- "attempted to query on non fully set up LBMManager");
-
- std::ostringstream oss;
- for (lbm_lookup_map::iterator it = m_lbm_lookup.begin();
- it != m_lbm_lookup.end(); ++it) {
- u32 time = it->first;
- std::vector<LoadingBlockModifierDef *> &lbm_list = it->second.lbm_list;
- for (std::vector<LoadingBlockModifierDef *>::iterator iit = lbm_list.begin();
- iit != lbm_list.end(); ++iit) {
- // Don't add if the LBM runs at every load,
- // then introducement time is hardcoded
- // and doesn't need to be stored
- if ((*iit)->run_at_every_load)
- continue;
- oss << (*iit)->name << "~" << time << ";";
- }
- }
- return oss.str();
-}
-
-void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, u32 stamp)
-{
- // Precondition, we need m_lbm_lookup to be initialized
- FATAL_ERROR_IF(m_query_mode == false,
- "attempted to query on non fully set up LBMManager");
- v3s16 pos_of_block = block->getPosRelative();
- v3s16 pos;
- MapNode n;
- content_t c;
- lbm_lookup_map::const_iterator it = getLBMsIntroducedAfter(stamp);
- for (pos.X = 0; pos.X < MAP_BLOCKSIZE; pos.X++)
- for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++)
- for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++)
- {
- n = block->getNodeNoEx(pos);
- c = n.getContent();
- for (LBMManager::lbm_lookup_map::const_iterator iit = it;
- iit != m_lbm_lookup.end(); ++iit) {
- const std::vector<LoadingBlockModifierDef *> *lbm_list =
- iit->second.lookup(c);
- if (!lbm_list)
- continue;
- for (std::vector<LoadingBlockModifierDef *>::const_iterator iit =
- lbm_list->begin(); iit != lbm_list->end(); ++iit) {
- (*iit)->trigger(env, pos + pos_of_block, n);
- }
- }
- }
-}
-
-/*
- ActiveBlockList
-*/
-
-void fillRadiusBlock(v3s16 p0, s16 r, std::set<v3s16> &list)
-{
- v3s16 p;
- for(p.X=p0.X-r; p.X<=p0.X+r; p.X++)
- for(p.Y=p0.Y-r; p.Y<=p0.Y+r; p.Y++)
- for(p.Z=p0.Z-r; p.Z<=p0.Z+r; p.Z++)
- {
- // limit to a sphere
- if (p.getDistanceFrom(p0) <= r) {
- // Set in list
- list.insert(p);
- }
- }
-}
-
-void ActiveBlockList::update(std::vector<v3s16> &active_positions,
- s16 radius,
- std::set<v3s16> &blocks_removed,
- std::set<v3s16> &blocks_added)
-{
- /*
- Create the new list
- */
- std::set<v3s16> newlist = m_forceloaded_list;
- for(std::vector<v3s16>::iterator i = active_positions.begin();
- i != active_positions.end(); ++i)
- {
- fillRadiusBlock(*i, radius, newlist);
- }
-
- /*
- Find out which blocks on the old list are not on the new list
- */
- // Go through old list
- for(std::set<v3s16>::iterator i = m_list.begin();
- i != m_list.end(); ++i)
- {
- v3s16 p = *i;
- // If not on new list, it's been removed
- if(newlist.find(p) == newlist.end())
- blocks_removed.insert(p);
- }
-
- /*
- Find out which blocks on the new list are not on the old list
- */
- // Go through new list
- for(std::set<v3s16>::iterator i = newlist.begin();
- i != newlist.end(); ++i)
- {
- v3s16 p = *i;
- // If not on old list, it's been added
- if(m_list.find(p) == m_list.end())
- blocks_added.insert(p);
- }
-
- /*
- Update m_list
- */
- m_list.clear();
- for(std::set<v3s16>::iterator i = newlist.begin();
- i != newlist.end(); ++i)
- {
- v3s16 p = *i;
- m_list.insert(p);
- }
-}
-
-/*
- ServerEnvironment
-*/
-
-ServerEnvironment::ServerEnvironment(ServerMap *map,
- GameScripting *scriptIface, IGameDef *gamedef,
- const std::string &path_world) :
- m_map(map),
- m_script(scriptIface),
- m_gamedef(gamedef),
- m_path_world(path_world),
- m_send_recommended_timer(0),
- m_active_block_interval_overload_skip(0),
- m_game_time(0),
- m_game_time_fraction_counter(0),
- m_last_clear_objects_time(0),
- m_recommended_send_interval(0.1),
- m_max_lag_estimate(0.1)
-{
-}
-
-ServerEnvironment::~ServerEnvironment()
-{
- // Clear active block list.
- // This makes the next one delete all active objects.
- m_active_blocks.clear();
-
- // Convert all objects to static and delete the active objects
- deactivateFarObjects(true);
-
- // Drop/delete map
- m_map->drop();
-
- // Delete ActiveBlockModifiers
- for (std::vector<ABMWithState>::iterator
- i = m_abms.begin(); i != m_abms.end(); ++i){
- delete i->abm;
- }
-
- // Deallocate players
- for (std::vector<RemotePlayer *>::iterator i = m_players.begin();
- i != m_players.end(); ++i) {
- delete (*i);
- }
-}
-
-Map & ServerEnvironment::getMap()
-{
- return *m_map;
-}
-
-ServerMap & ServerEnvironment::getServerMap()
-{
- return *m_map;
-}
-
-RemotePlayer *ServerEnvironment::getPlayer(const u16 peer_id)
-{
- for (std::vector<RemotePlayer *>::iterator i = m_players.begin();
- i != m_players.end(); ++i) {
- RemotePlayer *player = *i;
- if (player->peer_id == peer_id)
- return player;
- }
- return NULL;
-}
-
-RemotePlayer *ServerEnvironment::getPlayer(const char* name)
-{
- for (std::vector<RemotePlayer *>::iterator i = m_players.begin();
- i != m_players.end(); ++i) {
- RemotePlayer *player = *i;
- if (strcmp(player->getName(), name) == 0)
- return player;
- }
- return NULL;
-}
-
-void ServerEnvironment::addPlayer(RemotePlayer *player)
-{
- DSTACK(FUNCTION_NAME);
- /*
- Check that peer_ids are unique.
- Also check that names are unique.
- Exception: there can be multiple players with peer_id=0
- */
- // If peer id is non-zero, it has to be unique.
- if (player->peer_id != 0)
- FATAL_ERROR_IF(getPlayer(player->peer_id) != NULL, "Peer id not unique");
- // Name has to be unique.
- FATAL_ERROR_IF(getPlayer(player->getName()) != NULL, "Player name not unique");
- // Add.
- m_players.push_back(player);
-}
-
-void ServerEnvironment::removePlayer(RemotePlayer *player)
-{
- for (std::vector<RemotePlayer *>::iterator it = m_players.begin();
- it != m_players.end(); ++it) {
- if ((*it) == player) {
- delete *it;
- m_players.erase(it);
- return;
- }
- }
-}
-
-bool ServerEnvironment::line_of_sight(v3f pos1, v3f pos2, float stepsize, v3s16 *p)
-{
- float distance = pos1.getDistanceFrom(pos2);
-
- //calculate normalized direction vector
- v3f normalized_vector = v3f((pos2.X - pos1.X)/distance,
- (pos2.Y - pos1.Y)/distance,
- (pos2.Z - pos1.Z)/distance);
-
- //find out if there's a node on path between pos1 and pos2
- for (float i = 1; i < distance; i += stepsize) {
- v3s16 pos = floatToInt(v3f(normalized_vector.X * i,
- normalized_vector.Y * i,
- normalized_vector.Z * i) +pos1,BS);
-
- MapNode n = getMap().getNodeNoEx(pos);
-
- if(n.param0 != CONTENT_AIR) {
- if (p) {
- *p = pos;
- }
- return false;
- }
- }
- return true;
-}
-
-void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason,
- const std::string &str_reason, bool reconnect)
-{
- for (std::vector<RemotePlayer *>::iterator it = m_players.begin();
- it != m_players.end(); ++it) {
- RemotePlayer *player = dynamic_cast<RemotePlayer *>(*it);
- ((Server*)m_gamedef)->DenyAccessVerCompliant(player->peer_id,
- player->protocol_version, reason, str_reason, reconnect);
- }
-}
-
-void ServerEnvironment::saveLoadedPlayers()
-{
- std::string players_path = m_path_world + DIR_DELIM "players";
- fs::CreateDir(players_path);
-
- for (std::vector<RemotePlayer *>::iterator it = m_players.begin();
- it != m_players.end();
- ++it) {
- if ((*it)->checkModified()) {
- (*it)->save(players_path, m_gamedef);
- }
- }
-}
-
-void ServerEnvironment::savePlayer(RemotePlayer *player)
-{
- std::string players_path = m_path_world + DIR_DELIM "players";
- fs::CreateDir(players_path);
-
- player->save(players_path, m_gamedef);
-}
-
-RemotePlayer *ServerEnvironment::loadPlayer(const std::string &playername, PlayerSAO *sao)
-{
- bool newplayer = false;
- bool found = false;
- std::string players_path = m_path_world + DIR_DELIM "players" DIR_DELIM;
- std::string path = players_path + playername;
-
- RemotePlayer *player = getPlayer(playername.c_str());
- if (!player) {
- player = new RemotePlayer("", m_gamedef->idef());
- newplayer = true;
- }
-
- for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
- //// Open file and deserialize
- std::ifstream is(path.c_str(), std::ios_base::binary);
- if (!is.good())
- continue;
-
- player->deSerialize(is, path, sao);
- is.close();
-
- if (player->getName() == playername) {
- found = true;
- break;
- }
-
- path = players_path + playername + itos(i);
- }
-
- if (!found) {
- infostream << "Player file for player " << playername
- << " not found" << std::endl;
- if (newplayer)
- delete player;
-
- return NULL;
- }
-
- if (newplayer) {
- addPlayer(player);
- }
- player->setModified(false);
- return player;
-}
-
-void ServerEnvironment::saveMeta()
-{
- std::string path = m_path_world + DIR_DELIM "env_meta.txt";
-
- // Open file and serialize
- std::ostringstream ss(std::ios_base::binary);
-
- Settings args;
- args.setU64("game_time", m_game_time);
- args.setU64("time_of_day", getTimeOfDay());
- args.setU64("last_clear_objects_time", m_last_clear_objects_time);
- args.setU64("lbm_introduction_times_version", 1);
- args.set("lbm_introduction_times",
- m_lbm_mgr.createIntroductionTimesString());
- args.setU64("day_count", m_day_count);
- args.writeLines(ss);
- ss<<"EnvArgsEnd\n";
-
- if(!fs::safeWriteToFile(path, ss.str()))
- {
- infostream<<"ServerEnvironment::saveMeta(): Failed to write "
- <<path<<std::endl;
- throw SerializationError("Couldn't save env meta");
- }
-}
-
-void ServerEnvironment::loadMeta()
-{
- std::string path = m_path_world + DIR_DELIM "env_meta.txt";
-
- // Open file and deserialize
- std::ifstream is(path.c_str(), std::ios_base::binary);
- if (!is.good()) {
- infostream << "ServerEnvironment::loadMeta(): Failed to open "
- << path << std::endl;
- throw SerializationError("Couldn't load env meta");
- }
-
- Settings args;
-
- if (!args.parseConfigLines(is, "EnvArgsEnd")) {
- throw SerializationError("ServerEnvironment::loadMeta(): "
- "EnvArgsEnd not found!");
- }
-
- try {
- m_game_time = args.getU64("game_time");
- } catch (SettingNotFoundException &e) {
- // Getting this is crucial, otherwise timestamps are useless
- throw SerializationError("Couldn't load env meta game_time");
- }
-
- setTimeOfDay(args.exists("time_of_day") ?
- // set day to morning by default
- args.getU64("time_of_day") : 9000);
-
- m_last_clear_objects_time = args.exists("last_clear_objects_time") ?
- // If missing, do as if clearObjects was never called
- args.getU64("last_clear_objects_time") : 0;
-
- std::string lbm_introduction_times = "";
- try {
- u64 ver = args.getU64("lbm_introduction_times_version");
- if (ver == 1) {
- lbm_introduction_times = args.get("lbm_introduction_times");
- } else {
- infostream << "ServerEnvironment::loadMeta(): Non-supported"
- << " introduction time version " << ver << std::endl;
- }
- } catch (SettingNotFoundException &e) {
- // No problem, this is expected. Just continue with an empty string
- }
- m_lbm_mgr.loadIntroductionTimes(lbm_introduction_times, m_gamedef, m_game_time);
-
- m_day_count = args.exists("day_count") ?
- args.getU64("day_count") : 0;
-}
-
-void ServerEnvironment::loadDefaultMeta()
-{
- m_lbm_mgr.loadIntroductionTimes("", m_gamedef, m_game_time);
-}
-
-struct ActiveABM
-{
- ActiveBlockModifier *abm;
- int chance;
- std::set<content_t> required_neighbors;
-};
-
-class ABMHandler
-{
-private:
- ServerEnvironment *m_env;
- std::vector<std::vector<ActiveABM> *> m_aabms;
-public:
- ABMHandler(std::vector<ABMWithState> &abms,
- float dtime_s, ServerEnvironment *env,
- bool use_timers):
- m_env(env)
- {
- if(dtime_s < 0.001)
- return;
- INodeDefManager *ndef = env->getGameDef()->ndef();
- for(std::vector<ABMWithState>::iterator
- i = abms.begin(); i != abms.end(); ++i) {
- ActiveBlockModifier *abm = i->abm;
- 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;
- }
- float chance = abm->getTriggerChance();
- if(chance == 0)
- chance = 1;
- ActiveABM aabm;
- aabm.abm = abm;
- if(abm->getSimpleCatchUp()) {
- float intervals = actual_interval / trigger_interval;
- if(intervals == 0)
- continue;
- aabm.chance = chance / intervals;
- if(aabm.chance == 0)
- aabm.chance = 1;
- } else {
- aabm.chance = chance;
- }
- // 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)
- {
- 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;
- if (c >= m_aabms.size())
- m_aabms.resize(c + 256, NULL);
- if (!m_aabms[c])
- m_aabms[c] = new std::vector<ActiveABM>;
- m_aabms[c]->push_back(aabm);
- }
- }
- }
- }
-
- ~ABMHandler()
- {
- for (size_t i = 0; i < m_aabms.size(); i++)
- delete m_aabms[i];
- }
-
- // Find out how many objects the given block and its neighbours contain.
- // Returns the number of objects in the block, and also in 'wider' the
- // number of objects in the block and all its neighbours. The latter
- // may an estimate if any neighbours are unloaded.
- u32 countObjects(MapBlock *block, ServerMap * map, u32 &wider)
- {
- 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){
- wider_unknown_count++;
- continue;
- }
- wider += block2->m_static_objects.m_active.size()
- + block2->m_static_objects.m_stored.size();
- }
- // Extrapolate
- u32 active_object_count = block->m_static_objects.m_active.size();
- u32 wider_known_count = 3*3*3 - wider_unknown_count;
- wider += wider_unknown_count * wider / wider_known_count;
- return active_object_count;
-
- }
- void apply(MapBlock *block)
- {
- if(m_aabms.empty() || block->isDummy())
- return;
-
- ServerMap *map = &m_env->getServerMap();
-
- u32 active_object_count_wider;
- u32 active_object_count = this->countObjects(block, map, active_object_count_wider);
- m_env->m_added_objects = 0;
-
- v3s16 p0;
- for(p0.X=0; p0.X<MAP_BLOCKSIZE; p0.X++)
- for(p0.Y=0; p0.Y<MAP_BLOCKSIZE; p0.Y++)
- for(p0.Z=0; p0.Z<MAP_BLOCKSIZE; p0.Z++)
- {
- const MapNode &n = block->getNodeUnsafe(p0);
- content_t c = n.getContent();
-
- if (c >= m_aabms.size() || !m_aabms[c])
- continue;
-
- v3s16 p = p0 + block->getPosRelative();
- for(std::vector<ActiveABM>::iterator
- i = m_aabms[c]->begin(); i != m_aabms[c]->end(); ++i) {
- if(myrand() % i->chance != 0)
- continue;
-
- // Check neighbors
- if(!i->required_neighbors.empty())
- {
- v3s16 p1;
- for(p1.X = p0.X-1; p1.X <= p0.X+1; p1.X++)
- for(p1.Y = p0.Y-1; p1.Y <= p0.Y+1; p1.Y++)
- for(p1.Z = p0.Z-1; p1.Z <= p0.Z+1; p1.Z++)
- {
- if(p1 == p0)
- continue;
- content_t c;
- if (block->isValidPosition(p1)) {
- // if the neighbor is found on the same map block
- // get it straight from there
- const MapNode &n = block->getNodeUnsafe(p1);
- c = n.getContent();
- } else {
- // otherwise consult the map
- MapNode n = map->getNodeNoEx(p1 + block->getPosRelative());
- 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:
-
- // Call all the trigger variations
- i->abm->trigger(m_env, p, n);
- i->abm->trigger(m_env, p, n,
- active_object_count, active_object_count_wider);
-
- // Count surrounding objects again if the abms added any
- if(m_env->m_added_objects > 0) {
- active_object_count = countObjects(block, map, active_object_count_wider);
- m_env->m_added_objects = 0;
- }
- }
- }
- }
-};
-
-void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime)
-{
- // Reset usage timer immediately, otherwise a block that becomes active
- // again at around the same time as it would normally be unloaded will
- // get unloaded incorrectly. (I think this still leaves a small possibility
- // of a race condition between this and server::AsyncRunStep, which only
- // some kind of synchronisation will fix, but it at least reduces the window
- // of opportunity for it to break from seconds to nanoseconds)
- block->resetUsageTimer();
-
- // Get time difference
- u32 dtime_s = 0;
- u32 stamp = block->getTimestamp();
- if (m_game_time > stamp && stamp != BLOCK_TIMESTAMP_UNDEFINED)
- dtime_s = m_game_time - stamp;
- dtime_s += additional_dtime;
-
- /*infostream<<"ServerEnvironment::activateBlock(): block timestamp: "
- <<stamp<<", game time: "<<m_game_time<<std::endl;*/
-
- // Remove stored static objects if clearObjects was called since block's timestamp
- if (stamp == BLOCK_TIMESTAMP_UNDEFINED || stamp < m_last_clear_objects_time) {
- block->m_static_objects.m_stored.clear();
- // do not set changed flag to avoid unnecessary mapblock writes
- }
-
- // Set current time as timestamp
- block->setTimestampNoChangedFlag(m_game_time);
-
- /*infostream<<"ServerEnvironment::activateBlock(): block is "
- <<dtime_s<<" seconds old."<<std::endl;*/
-
- // Activate stored objects
- activateObjects(block, dtime_s);
-
- /* Handle LoadingBlockModifiers */
- m_lbm_mgr.applyLBMs(this, block, stamp);
-
- // Run node timers
- std::vector<NodeTimer> elapsed_timers =
- block->m_node_timers.step((float)dtime_s);
- if (!elapsed_timers.empty()) {
- MapNode n;
- for (std::vector<NodeTimer>::iterator
- i = elapsed_timers.begin();
- i != elapsed_timers.end(); ++i){
- n = block->getNodeNoEx(i->position);
- v3s16 p = i->position + block->getPosRelative();
- if (m_script->node_on_timer(p, n, i->elapsed))
- block->setNodeTimer(NodeTimer(i->timeout, 0, i->position));
- }
- }
-
- /* Handle ActiveBlockModifiers */
- ABMHandler abmhandler(m_abms, dtime_s, this, false);
- abmhandler.apply(block);
-}
-
-void ServerEnvironment::addActiveBlockModifier(ActiveBlockModifier *abm)
-{
- m_abms.push_back(ABMWithState(abm));
-}
-
-void ServerEnvironment::addLoadingBlockModifierDef(LoadingBlockModifierDef *lbm)
-{
- m_lbm_mgr.addLBMDef(lbm);
-}
-
-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)
- m_script->node_on_destruct(p, n_old);
-
- // Replace node
- if (!m_map->addNodeWithEvent(p, n))
- return false;
-
- // Update active VoxelManipulator if a mapgen thread
- m_map->updateVManip(p);
-
- // Call post-destructor
- if (ndef->get(n_old).has_after_destruct)
- m_script->node_after_destruct(p, n_old);
-
- // Call constructor
- if (ndef->get(n).has_on_construct)
- m_script->node_on_construct(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)
- m_script->node_on_destruct(p, n_old);
-
- // Replace with air
- // This is slightly optimized compared to addNodeWithEvent(air)
- if (!m_map->removeNodeWithEvent(p))
- return false;
-
- // Update active VoxelManipulator if a mapgen thread
- m_map->updateVManip(p);
-
- // Call post-destructor
- if (ndef->get(n_old).has_after_destruct)
- m_script->node_after_destruct(p, n_old);
-
- // Air doesn't require constructor
- return true;
-}
-
-bool ServerEnvironment::swapNode(v3s16 p, const MapNode &n)
-{
- if (!m_map->addNodeWithEvent(p, n, false))
- return false;
-
- // Update active VoxelManipulator if a mapgen thread
- m_map->updateVManip(p);
-
- return true;
-}
-
-void ServerEnvironment::getObjectsInsideRadius(std::vector<u16> &objects, v3f pos, float radius)
-{
- for (ActiveObjectMap::iterator i = m_active_objects.begin();
- i != m_active_objects.end(); ++i) {
- ServerActiveObject* obj = i->second;
- u16 id = i->first;
- v3f objectpos = obj->getBasePosition();
- if (objectpos.getDistanceFrom(pos) > radius)
- continue;
- objects.push_back(id);
- }
-}
-
-void ServerEnvironment::clearObjects(ClearObjectsMode mode)
-{
- infostream << "ServerEnvironment::clearObjects(): "
- << "Removing all active objects" << std::endl;
- std::vector<u16> objects_to_remove;
- for (ActiveObjectMap::iterator i = m_active_objects.begin();
- i != m_active_objects.end(); ++i) {
- ServerActiveObject* obj = i->second;
- if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER)
- continue;
- u16 id = i->first;
- // Delete static object if block is loaded
- if (obj->m_static_exists) {
- MapBlock *block = m_map->getBlockNoCreateNoEx(obj->m_static_block);
- if (block) {
- block->m_static_objects.remove(id);
- block->raiseModified(MOD_STATE_WRITE_NEEDED,
- MOD_REASON_CLEAR_ALL_OBJECTS);
- obj->m_static_exists = false;
- }
- }
- // If known by some client, don't delete immediately
- if (obj->m_known_by_count > 0) {
- obj->m_pending_deactivation = true;
- obj->m_removed = true;
- continue;
- }
-
- // Tell the object about removal
- obj->removingFromEnvironment();
- // Deregister in scripting api
- m_script->removeObjectReference(obj);
-
- // Delete active object
- if (obj->environmentDeletes())
- delete obj;
- // Id to be removed from m_active_objects
- objects_to_remove.push_back(id);
- }
-
- // Remove references from m_active_objects
- for (std::vector<u16>::iterator i = objects_to_remove.begin();
- i != objects_to_remove.end(); ++i) {
- m_active_objects.erase(*i);
- }
-
- // Get list of loaded blocks
- std::vector<v3s16> loaded_blocks;
- infostream << "ServerEnvironment::clearObjects(): "
- << "Listing all loaded blocks" << std::endl;
- m_map->listAllLoadedBlocks(loaded_blocks);
- infostream << "ServerEnvironment::clearObjects(): "
- << "Done listing all loaded blocks: "
- << loaded_blocks.size()<<std::endl;
-
- // Get list of loadable blocks
- std::vector<v3s16> loadable_blocks;
- if (mode == CLEAR_OBJECTS_MODE_FULL) {
- infostream << "ServerEnvironment::clearObjects(): "
- << "Listing all loadable blocks" << std::endl;
- m_map->listAllLoadableBlocks(loadable_blocks);
- infostream << "ServerEnvironment::clearObjects(): "
- << "Done listing all loadable blocks: "
- << loadable_blocks.size() << std::endl;
- } else {
- loadable_blocks = loaded_blocks;
- }
-
- infostream << "ServerEnvironment::clearObjects(): "
- << "Now clearing objects in " << loadable_blocks.size()
- << " blocks" << std::endl;
-
- // Grab a reference on each loaded block to avoid unloading it
- for (std::vector<v3s16>::iterator i = loaded_blocks.begin();
- i != loaded_blocks.end(); ++i) {
- v3s16 p = *i;
- MapBlock *block = m_map->getBlockNoCreateNoEx(p);
- assert(block != NULL);
- block->refGrab();
- }
-
- // Remove objects in all loadable blocks
- u32 unload_interval = U32_MAX;
- if (mode == CLEAR_OBJECTS_MODE_FULL) {
- unload_interval = g_settings->getS32("max_clearobjects_extra_loaded_blocks");
- unload_interval = MYMAX(unload_interval, 1);
- }
- u32 report_interval = loadable_blocks.size() / 10;
- u32 num_blocks_checked = 0;
- u32 num_blocks_cleared = 0;
- u32 num_objs_cleared = 0;
- for (std::vector<v3s16>::iterator i = loadable_blocks.begin();
- i != loadable_blocks.end(); ++i) {
- v3s16 p = *i;
- MapBlock *block = m_map->emergeBlock(p, false);
- if (!block) {
- errorstream << "ServerEnvironment::clearObjects(): "
- << "Failed to emerge block " << PP(p) << std::endl;
- continue;
- }
- u32 num_stored = block->m_static_objects.m_stored.size();
- u32 num_active = block->m_static_objects.m_active.size();
- if (num_stored != 0 || num_active != 0) {
- block->m_static_objects.m_stored.clear();
- block->m_static_objects.m_active.clear();
- block->raiseModified(MOD_STATE_WRITE_NEEDED,
- MOD_REASON_CLEAR_ALL_OBJECTS);
- num_objs_cleared += num_stored + num_active;
- num_blocks_cleared++;
- }
- num_blocks_checked++;
-
- if (report_interval != 0 &&
- num_blocks_checked % report_interval == 0) {
- float percent = 100.0 * (float)num_blocks_checked /
- loadable_blocks.size();
- infostream << "ServerEnvironment::clearObjects(): "
- << "Cleared " << num_objs_cleared << " objects"
- << " in " << num_blocks_cleared << " blocks ("
- << percent << "%)" << std::endl;
- }
- if (num_blocks_checked % unload_interval == 0) {
- m_map->unloadUnreferencedBlocks();
- }
- }
- m_map->unloadUnreferencedBlocks();
-
- // Drop references that were added above
- for (std::vector<v3s16>::iterator i = loaded_blocks.begin();
- i != loaded_blocks.end(); ++i) {
- v3s16 p = *i;
- MapBlock *block = m_map->getBlockNoCreateNoEx(p);
- assert(block);
- block->refDrop();
- }
-
- m_last_clear_objects_time = m_game_time;
-
- infostream << "ServerEnvironment::clearObjects(): "
- << "Finished: Cleared " << num_objs_cleared << " objects"
- << " in " << num_blocks_cleared << " blocks" << std::endl;
-}
-
-void ServerEnvironment::step(float dtime)
-{
- DSTACK(FUNCTION_NAME);
-
- //TimeTaker timer("ServerEnv step");
-
- /* 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.
- static const float server_step = g_settings->getFloat("dedicated_server_step");
- m_recommended_send_interval = server_step;
-
- /*
- Increment game time
- */
- {
- m_game_time_fraction_counter += dtime;
- u32 inc_i = (u32)m_game_time_fraction_counter;
- m_game_time += inc_i;
- m_game_time_fraction_counter -= (float)inc_i;
- }
-
- /*
- Handle players
- */
- {
- ScopeProfiler sp(g_profiler, "SEnv: handle players avg", SPT_AVG);
- for (std::vector<RemotePlayer *>::iterator i = m_players.begin();
- i != m_players.end(); ++i) {
- RemotePlayer *player = dynamic_cast<RemotePlayer *>(*i);
- assert(player);
-
- // Ignore disconnected players
- if(player->peer_id == 0)
- continue;
-
- // Move
- player->move(dtime, this, 100*BS);
- }
- }
-
- /*
- Manage active block list
- */
- if (m_active_blocks_management_interval.step(dtime, m_cache_active_block_mgmt_interval)) {
- ScopeProfiler sp(g_profiler, "SEnv: manage act. block list avg per interval", SPT_AVG);
- /*
- Get player block positions
- */
- std::vector<v3s16> players_blockpos;
- for (std::vector<RemotePlayer *>::iterator i = m_players.begin();
- i != m_players.end(); ++i) {
- RemotePlayer *player = dynamic_cast<RemotePlayer *>(*i);
- assert(player);
-
- // Ignore disconnected players
- if (player->peer_id == 0)
- continue;
-
- PlayerSAO *playersao = player->getPlayerSAO();
- assert(playersao);
-
- v3s16 blockpos = getNodeBlockPos(
- floatToInt(playersao->getBasePosition(), BS));
- players_blockpos.push_back(blockpos);
- }
-
- /*
- Update list of active blocks, collecting changes
- */
- static const s16 active_block_range = g_settings->getS16("active_block_range");
- std::set<v3s16> blocks_removed;
- std::set<v3s16> blocks_added;
- m_active_blocks.update(players_blockpos, active_block_range,
- blocks_removed, blocks_added);
-
- /*
- Handle removed blocks
- */
-
- // Convert active objects that are no more in active blocks to static
- deactivateFarObjects(false);
-
- for(std::set<v3s16>::iterator
- i = blocks_removed.begin();
- i != blocks_removed.end(); ++i) {
- v3s16 p = *i;
-
- /* infostream<<"Server: Block " << PP(p)
- << " became inactive"<<std::endl; */
-
- MapBlock *block = m_map->getBlockNoCreateNoEx(p);
- if(block==NULL)
- continue;
-
- // Set current time as timestamp (and let it set ChangedFlag)
- block->setTimestamp(m_game_time);
- }
-
- /*
- Handle added blocks
- */
-
- for(std::set<v3s16>::iterator
- i = blocks_added.begin();
- i != blocks_added.end(); ++i)
- {
- v3s16 p = *i;
-
- MapBlock *block = m_map->getBlockOrEmerge(p);
- if(block==NULL){
- m_active_blocks.m_list.erase(p);
- continue;
- }
-
- activateBlock(block);
- /* infostream<<"Server: Block " << PP(p)
- << " became active"<<std::endl; */
- }
- }
-
- /*
- Mess around in active blocks
- */
- if (m_active_blocks_nodemetadata_interval.step(dtime, m_cache_nodetimer_interval)) {
- ScopeProfiler sp(g_profiler, "SEnv: mess in act. blocks avg per interval", SPT_AVG);
-
- float dtime = m_cache_nodetimer_interval;
-
- for(std::set<v3s16>::iterator
- i = m_active_blocks.m_list.begin();
- i != m_active_blocks.m_list.end(); ++i)
- {
- v3s16 p = *i;
-
- /*infostream<<"Server: Block ("<<p.X<<","<<p.Y<<","<<p.Z
- <<") being handled"<<std::endl;*/
-
- MapBlock *block = m_map->getBlockNoCreateNoEx(p);
- if(block==NULL)
- continue;
-
- // Reset block usage timer
- block->resetUsageTimer();
-
- // Set current time as timestamp
- block->setTimestampNoChangedFlag(m_game_time);
- // If time has changed much from the one on disk,
- // set block to be saved when it is unloaded
- if(block->getTimestamp() > block->getDiskTimestamp() + 60)
- block->raiseModified(MOD_STATE_WRITE_AT_UNLOAD,
- MOD_REASON_BLOCK_EXPIRED);
-
- // Run node timers
- std::vector<NodeTimer> elapsed_timers =
- block->m_node_timers.step((float)dtime);
- if (!elapsed_timers.empty()) {
- MapNode n;
- for (std::vector<NodeTimer>::iterator i = elapsed_timers.begin();
- i != elapsed_timers.end(); ++i) {
- n = block->getNodeNoEx(i->position);
- p = i->position + block->getPosRelative();
- if (m_script->node_on_timer(p, n, i->elapsed)) {
- block->setNodeTimer(NodeTimer(
- i->timeout, 0, i->position));
- }
- }
- }
- }
- }
-
- if (m_active_block_modifier_interval.step(dtime, m_cache_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 per interval", SPT_AVG);
- TimeTaker timer("modify in active blocks per interval");
-
- // Initialize handling of ActiveBlockModifiers
- ABMHandler abmhandler(m_abms, m_cache_abm_interval, this, true);
-
- for(std::set<v3s16>::iterator
- i = m_active_blocks.m_list.begin();
- i != m_active_blocks.m_list.end(); ++i)
- {
- v3s16 p = *i;
-
- /*infostream<<"Server: Block ("<<p.X<<","<<p.Y<<","<<p.Z
- <<") being handled"<<std::endl;*/
-
- MapBlock *block = m_map->getBlockNoCreateNoEx(p);
- if(block == NULL)
- continue;
-
- // Set current time as timestamp
- block->setTimestampNoChangedFlag(m_game_time);
-
- /* Handle ActiveBlockModifiers */
- abmhandler.apply(block);
- }
-
- u32 time_ms = timer.stop(true);
- u32 max_time_ms = 200;
- if(time_ms > max_time_ms){
- warningstream<<"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())
- */
- m_script->environment_Step(dtime);
-
- /*
- Step active objects
- */
- {
- ScopeProfiler sp(g_profiler, "SEnv: step act. objs avg", SPT_AVG);
- //TimeTaker timer("Step active objects");
-
- g_profiler->avg("SEnv: num of objects", m_active_objects.size());
-
- // This helps the objects to send data at the same time
- bool send_recommended = false;
- m_send_recommended_timer += dtime;
- if(m_send_recommended_timer > getSendRecommendedInterval())
- {
- m_send_recommended_timer -= getSendRecommendedInterval();
- send_recommended = true;
- }
-
- for(ActiveObjectMap::iterator i = m_active_objects.begin();
- i != m_active_objects.end(); ++i) {
- ServerActiveObject* obj = i->second;
- // Don't step if is to be removed or stored statically
- if(obj->m_removed || obj->m_pending_deactivation)
- continue;
- // Step object
- obj->step(dtime, send_recommended);
- // Read messages from object
- while(!obj->m_messages_out.empty())
- {
- m_active_object_messages.push(
- obj->m_messages_out.front());
- obj->m_messages_out.pop();
- }
- }
- }
-
- /*
- Manage active objects
- */
- if(m_object_management_interval.step(dtime, 0.5))
- {
- ScopeProfiler sp(g_profiler, "SEnv: remove removed objs avg /.5s", SPT_AVG);
- /*
- Remove objects that satisfy (m_removed && m_known_by_count==0)
- */
- removeRemovedObjects();
- }
-
- /*
- Manage particle spawner expiration
- */
- if (m_particle_management_interval.step(dtime, 1.0)) {
- for (UNORDERED_MAP<u32, float>::iterator i = m_particle_spawners.begin();
- i != m_particle_spawners.end(); ) {
- //non expiring spawners
- if (i->second == PARTICLE_SPAWNER_NO_EXPIRY) {
- ++i;
- continue;
- }
-
- i->second -= 1.0f;
- if (i->second <= 0.f)
- m_particle_spawners.erase(i++);
- else
- ++i;
- }
- }
-}
-
-u32 ServerEnvironment::addParticleSpawner(float exptime)
-{
- // Timers with lifetime 0 do not expire
- float time = exptime > 0.f ? exptime : PARTICLE_SPAWNER_NO_EXPIRY;
-
- u32 id = 0;
- for (;;) { // look for unused particlespawner id
- id++;
- UNORDERED_MAP<u32, float>::iterator f = m_particle_spawners.find(id);
- if (f == m_particle_spawners.end()) {
- m_particle_spawners[id] = time;
- break;
- }
- }
- return id;
-}
-
-u32 ServerEnvironment::addParticleSpawner(float exptime, u16 attached_id)
-{
- u32 id = addParticleSpawner(exptime);
- m_particle_spawner_attachments[id] = attached_id;
- if (ServerActiveObject *obj = getActiveObject(attached_id)) {
- obj->attachParticleSpawner(id);
- }
- return id;
-}
-
-void ServerEnvironment::deleteParticleSpawner(u32 id, bool remove_from_object)
-{
- m_particle_spawners.erase(id);
- UNORDERED_MAP<u32, u16>::iterator it = m_particle_spawner_attachments.find(id);
- if (it != m_particle_spawner_attachments.end()) {
- u16 obj_id = (*it).second;
- ServerActiveObject *sao = getActiveObject(obj_id);
- if (sao != NULL && remove_from_object) {
- sao->detachParticleSpawner(id);
- }
- m_particle_spawner_attachments.erase(id);
- }
-}
-
-ServerActiveObject* ServerEnvironment::getActiveObject(u16 id)
-{
- ActiveObjectMap::iterator n = m_active_objects.find(id);
- return (n != m_active_objects.end() ? n->second : NULL);
-}
-
-bool isFreeServerActiveObjectId(u16 id, ActiveObjectMap &objects)
-{
- if (id == 0)
- return false;
-
- return objects.find(id) == objects.end();
-}
-
-u16 getFreeServerActiveObjectId(ActiveObjectMap &objects)
-{
- //try to reuse id's as late as possible
- static u16 last_used_id = 0;
- u16 startid = last_used_id;
- for(;;)
- {
- last_used_id ++;
- if(isFreeServerActiveObjectId(last_used_id, objects))
- return last_used_id;
-
- if(last_used_id == startid)
- return 0;
- }
-}
-
-u16 ServerEnvironment::addActiveObject(ServerActiveObject *object)
-{
- assert(object); // Pre-condition
- m_added_objects++;
- u16 id = addActiveObjectRaw(object, true, 0);
- return id;
-}
-
-/*
- Finds out what new objects have been added to
- inside a radius around a position
-*/
-void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius,
- s16 player_radius,
- std::set<u16> ¤t_objects,
- std::queue<u16> &added_objects)
-{
- f32 radius_f = radius * BS;
- f32 player_radius_f = player_radius * BS;
-
- if (player_radius_f < 0)
- player_radius_f = 0;
- /*
- Go through the object list,
- - discard m_removed objects,
- - discard objects that are too far away,
- - discard objects that are found in current_objects.
- - add remaining objects to added_objects
- */
- for (ActiveObjectMap::iterator i = m_active_objects.begin();
- i != m_active_objects.end(); ++i) {
- u16 id = i->first;
-
- // Get object
- ServerActiveObject *object = i->second;
- if (object == NULL)
- continue;
-
- // Discard if removed or deactivating
- if(object->m_removed || object->m_pending_deactivation)
- continue;
-
- f32 distance_f = object->getBasePosition().
- getDistanceFrom(playersao->getBasePosition());
- if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
- // Discard if too far
- if (distance_f > player_radius_f && player_radius_f != 0)
- continue;
- } else if (distance_f > radius_f)
- continue;
-
- // Discard if already on current_objects
- std::set<u16>::iterator n;
- n = current_objects.find(id);
- if(n != current_objects.end())
- continue;
- // Add to added_objects
- added_objects.push(id);
- }
-}
-
-/*
- Finds out what objects have been removed from
- inside a radius around a position
-*/
-void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius,
- s16 player_radius,
- std::set<u16> ¤t_objects,
- std::queue<u16> &removed_objects)
-{
- f32 radius_f = radius * BS;
- f32 player_radius_f = player_radius * BS;
-
- if (player_radius_f < 0)
- player_radius_f = 0;
- /*
- Go through current_objects; object is removed if:
- - object is not found in m_active_objects (this is actually an
- error condition; objects should be set m_removed=true and removed
- only after all clients have been informed about removal), or
- - object has m_removed=true, or
- - object is too far away
- */
- for(std::set<u16>::iterator
- i = current_objects.begin();
- i != current_objects.end(); ++i)
- {
- u16 id = *i;
- ServerActiveObject *object = getActiveObject(id);
-
- if (object == NULL) {
- infostream << "ServerEnvironment::getRemovedActiveObjects():"
- << " object in current_objects is NULL" << std::endl;
- removed_objects.push(id);
- continue;
- }
-
- if (object->m_removed || object->m_pending_deactivation) {
- removed_objects.push(id);
- continue;
- }
-
- f32 distance_f = object->getBasePosition().getDistanceFrom(playersao->getBasePosition());
- if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
- if (distance_f <= player_radius_f || player_radius_f == 0)
- continue;
- } else if (distance_f <= radius_f)
- continue;
-
- // Object is no longer visible
- removed_objects.push(id);
- }
-}
-
-void ServerEnvironment::setStaticForActiveObjectsInBlock(
- v3s16 blockpos, bool static_exists, v3s16 static_block)
-{
- MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos);
- if (!block)
- return;
-
- for (std::map<u16, StaticObject>::iterator
- so_it = block->m_static_objects.m_active.begin();
- so_it != block->m_static_objects.m_active.end(); ++so_it) {
- // Get the ServerActiveObject counterpart to this StaticObject
- ActiveObjectMap::iterator ao_it = m_active_objects.find(so_it->first);
- if (ao_it == m_active_objects.end()) {
- // If this ever happens, there must be some kind of nasty bug.
- errorstream << "ServerEnvironment::setStaticForObjectsInBlock(): "
- "Object from MapBlock::m_static_objects::m_active not found "
- "in m_active_objects";
- continue;
- }
-
- ServerActiveObject *sao = ao_it->second;
- sao->m_static_exists = static_exists;
- sao->m_static_block = static_block;
- }
-}
-
-ActiveObjectMessage ServerEnvironment::getActiveObjectMessage()
-{
- if(m_active_object_messages.empty())
- return ActiveObjectMessage(0);
-
- ActiveObjectMessage message = m_active_object_messages.front();
- m_active_object_messages.pop();
- return message;
-}
-
-/*
- ************ Private methods *************
-*/
-
-u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object,
- bool set_changed, u32 dtime_s)
-{
- assert(object); // Pre-condition
- if(object->getId() == 0){
- u16 new_id = getFreeServerActiveObjectId(m_active_objects);
- if(new_id == 0)
- {
- errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
- <<"no free ids available"<<std::endl;
- if(object->environmentDeletes())
- delete object;
- return 0;
- }
- object->setId(new_id);
- }
- else{
- verbosestream<<"ServerEnvironment::addActiveObjectRaw(): "
- <<"supplied with id "<<object->getId()<<std::endl;
- }
-
- if(!isFreeServerActiveObjectId(object->getId(), m_active_objects)) {
- errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
- <<"id is not free ("<<object->getId()<<")"<<std::endl;
- if(object->environmentDeletes())
- delete object;
- return 0;
- }
-
- if (objectpos_over_limit(object->getBasePosition())) {
- v3f p = object->getBasePosition();
- errorstream << "ServerEnvironment::addActiveObjectRaw(): "
- << "object position (" << p.X << "," << p.Y << "," << p.Z
- << ") outside maximum range" << std::endl;
- if (object->environmentDeletes())
- delete object;
- return 0;
- }
-
- /*infostream<<"ServerEnvironment::addActiveObjectRaw(): "
- <<"added (id="<<object->getId()<<")"<<std::endl;*/
-
- m_active_objects[object->getId()] = object;
-
- verbosestream<<"ServerEnvironment::addActiveObjectRaw(): "
- <<"Added id="<<object->getId()<<"; there are now "
- <<m_active_objects.size()<<" active objects."
- <<std::endl;
-
- // Register reference in scripting api (must be done before post-init)
- m_script->addObjectReference(object);
- // Post-initialize object
- object->addedToEnvironment(dtime_s);
-
- // Add static data to block
- if(object->isStaticAllowed())
- {
- // Add static object to active static list of the block
- v3f objectpos = object->getBasePosition();
- std::string staticdata = object->getStaticData();
- StaticObject s_obj(object->getType(), objectpos, staticdata);
- // Add to the block where the object is located in
- v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
- MapBlock *block = m_map->emergeBlock(blockpos);
- if(block){
- block->m_static_objects.m_active[object->getId()] = s_obj;
- object->m_static_exists = true;
- object->m_static_block = blockpos;
-
- if(set_changed)
- block->raiseModified(MOD_STATE_WRITE_NEEDED,
- MOD_REASON_ADD_ACTIVE_OBJECT_RAW);
- } else {
- v3s16 p = floatToInt(objectpos, BS);
- errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
- <<"could not emerge block for storing id="<<object->getId()
- <<" statically (pos="<<PP(p)<<")"<<std::endl;
- }
- }
-
- return object->getId();
-}
-
-/*
- Remove objects that satisfy (m_removed && m_known_by_count==0)
-*/
-void ServerEnvironment::removeRemovedObjects()
-{
- std::vector<u16> objects_to_remove;
- for(ActiveObjectMap::iterator i = m_active_objects.begin();
- i != m_active_objects.end(); ++i) {
- u16 id = i->first;
- ServerActiveObject* obj = i->second;
- // This shouldn't happen but check it
- if(obj == NULL)
- {
- infostream<<"NULL object found in ServerEnvironment"
- <<" while finding removed objects. id="<<id<<std::endl;
- // Id to be removed from m_active_objects
- objects_to_remove.push_back(id);
- continue;
- }
-
- /*
- We will delete objects that are marked as removed or thatare
- waiting for deletion after deactivation
- */
- if (!obj->m_removed && !obj->m_pending_deactivation)
- continue;
-
- /*
- Delete static data from block if is marked as removed
- */
- if(obj->m_static_exists && obj->m_removed)
- {
- MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
- if (block) {
- block->m_static_objects.remove(id);
- block->raiseModified(MOD_STATE_WRITE_NEEDED,
- MOD_REASON_REMOVE_OBJECTS_REMOVE);
- 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;
- }
- }
-
- // If m_known_by_count > 0, don't actually remove. On some future
- // invocation this will be 0, which is when removal will continue.
- if(obj->m_known_by_count > 0)
- continue;
-
- /*
- Move static data from active to stored if not marked as removed
- */
- if(obj->m_static_exists && !obj->m_removed){
- MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
- if (block) {
- std::map<u16, StaticObject>::iterator i =
- block->m_static_objects.m_active.find(id);
- if(i != block->m_static_objects.m_active.end()){
- block->m_static_objects.m_stored.push_back(i->second);
- block->m_static_objects.m_active.erase(id);
- block->raiseModified(MOD_STATE_WRITE_NEEDED,
- MOD_REASON_REMOVE_OBJECTS_DEACTIVATE);
- }
- } else {
- infostream<<"Failed to emerge block from which an object to "
- <<"be deactivated was loaded from. id="<<id<<std::endl;
- }
- }
-
- // Tell the object about removal
- obj->removingFromEnvironment();
- // Deregister in scripting api
- m_script->removeObjectReference(obj);
-
- // Delete
- if(obj->environmentDeletes())
- delete obj;
-
- // Id to be removed from m_active_objects
- objects_to_remove.push_back(id);
- }
- // Remove references from m_active_objects
- for(std::vector<u16>::iterator i = objects_to_remove.begin();
- i != objects_to_remove.end(); ++i) {
- m_active_objects.erase(*i);
- }
-}
-
-static void print_hexdump(std::ostream &o, const std::string &data)
-{
- const int linelength = 16;
- for(int l=0; ; l++){
- int i0 = linelength * l;
- bool at_end = false;
- int thislinelength = linelength;
- if(i0 + thislinelength > (int)data.size()){
- thislinelength = data.size() - i0;
- at_end = true;
- }
- for(int di=0; di<linelength; di++){
- int i = i0 + di;
- char buf[4];
- if(di<thislinelength)
- snprintf(buf, 4, "%.2x ", data[i]);
- else
- snprintf(buf, 4, " ");
- o<<buf;
- }
- o<<" ";
- for(int di=0; di<thislinelength; di++){
- int i = i0 + di;
- if(data[i] >= 32)
- o<<data[i];
- else
- o<<".";
- }
- o<<std::endl;
- if(at_end)
- break;
- }
-}
-
-/*
- Convert stored objects from blocks near the players to active.
-*/
-void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s)
-{
- if(block == NULL)
- return;
-
- // Ignore if no stored objects (to not set changed flag)
- if(block->m_static_objects.m_stored.empty())
- return;
-
- verbosestream<<"ServerEnvironment::activateObjects(): "
- <<"activating objects of block "<<PP(block->getPos())
- <<" ("<<block->m_static_objects.m_stored.size()
- <<" objects)"<<std::endl;
- bool large_amount = (block->m_static_objects.m_stored.size() > g_settings->getU16("max_objects_per_block"));
- if (large_amount) {
- errorstream<<"suspiciously large amount of objects detected: "
- <<block->m_static_objects.m_stored.size()<<" in "
- <<PP(block->getPos())
- <<"; removing all of them."<<std::endl;
- // Clear stored list
- block->m_static_objects.m_stored.clear();
- block->raiseModified(MOD_STATE_WRITE_NEEDED,
- MOD_REASON_TOO_MANY_OBJECTS);
- return;
- }
-
- // Activate stored objects
- std::vector<StaticObject> new_stored;
- for (std::vector<StaticObject>::iterator
- i = block->m_static_objects.m_stored.begin();
- i != block->m_static_objects.m_stored.end(); ++i) {
- StaticObject &s_obj = *i;
-
- // Create an active object from the data
- ServerActiveObject *obj = ServerActiveObject::create
- ((ActiveObjectType) s_obj.type, this, 0, s_obj.pos, s_obj.data);
- // If couldn't create object, store static data back.
- if(obj == NULL) {
- errorstream<<"ServerEnvironment::activateObjects(): "
- <<"failed to create active object from static object "
- <<"in block "<<PP(s_obj.pos/BS)
- <<" type="<<(int)s_obj.type<<" data:"<<std::endl;
- print_hexdump(verbosestream, s_obj.data);
-
- new_stored.push_back(s_obj);
- continue;
- }
- verbosestream<<"ServerEnvironment::activateObjects(): "
- <<"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, dtime_s);
- }
- // Clear stored list
- block->m_static_objects.m_stored.clear();
- // Add leftover failed stuff to stored list
- for(std::vector<StaticObject>::iterator
- i = new_stored.begin();
- i != new_stored.end(); ++i) {
- StaticObject &s_obj = *i;
- block->m_static_objects.m_stored.push_back(s_obj);
- }
-
- // Turn the active counterparts of activated objects not pending for
- // deactivation
- for(std::map<u16, StaticObject>::iterator
- i = block->m_static_objects.m_active.begin();
- i != block->m_static_objects.m_active.end(); ++i)
- {
- u16 id = i->first;
- ServerActiveObject *object = getActiveObject(id);
- assert(object);
- object->m_pending_deactivation = false;
- }
-
- /*
- Note: Block hasn't really been modified here.
- The objects have just been activated and moved from the stored
- static list to the active static list.
- As such, the block is essentially the same.
- Thus, do not call block->raiseModified(MOD_STATE_WRITE_NEEDED).
- Otherwise there would be a huge amount of unnecessary I/O.
- */
-}
-
-/*
- Convert objects that are not standing inside active blocks to static.
-
- If m_known_by_count != 0, active object is not deleted, but static
- data is still updated.
-
- 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)
-{
- std::vector<u16> objects_to_remove;
- for(ActiveObjectMap::iterator i = m_active_objects.begin();
- i != m_active_objects.end(); ++i) {
- ServerActiveObject* obj = i->second;
- assert(obj);
-
- // Do not deactivate if static data creation not allowed
- if(!force_delete && !obj->isStaticAllowed())
- continue;
-
- // If pending deactivation, let removeRemovedObjects() do it
- if(!force_delete && obj->m_pending_deactivation)
- continue;
-
- u16 id = i->first;
- v3f objectpos = obj->getBasePosition();
-
- // The block in which the object resides in
- v3s16 blockpos_o = getNodeBlockPos(floatToInt(objectpos, BS));
-
- // If object's static data is stored in a deactivated block and object
- // is actually located in an active block, re-save to the block in
- // which the object is actually located in.
- if(!force_delete &&
- obj->m_static_exists &&
- !m_active_blocks.contains(obj->m_static_block) &&
- m_active_blocks.contains(blockpos_o))
- {
- v3s16 old_static_block = obj->m_static_block;
-
- // Save to block where object is located
- MapBlock *block = m_map->emergeBlock(blockpos_o, false);
- if(!block){
- errorstream<<"ServerEnvironment::deactivateFarObjects(): "
- <<"Could not save object id="<<id
- <<" to it's current block "<<PP(blockpos_o)
- <<std::endl;
- continue;
- }
- std::string staticdata_new = obj->getStaticData();
- StaticObject s_obj(obj->getType(), objectpos, staticdata_new);
- block->m_static_objects.insert(id, s_obj);
- obj->m_static_block = blockpos_o;
- block->raiseModified(MOD_STATE_WRITE_NEEDED,
- MOD_REASON_STATIC_DATA_ADDED);
-
- // Delete from block where object was located
- block = m_map->emergeBlock(old_static_block, false);
- if(!block){
- errorstream<<"ServerEnvironment::deactivateFarObjects(): "
- <<"Could not delete object id="<<id
- <<" from it's previous block "<<PP(old_static_block)
- <<std::endl;
- continue;
- }
- block->m_static_objects.remove(id);
- block->raiseModified(MOD_STATE_WRITE_NEEDED,
- MOD_REASON_STATIC_DATA_REMOVED);
- continue;
- }
-
- // If block is active, don't remove
- if(!force_delete && m_active_blocks.contains(blockpos_o))
- continue;
-
- verbosestream<<"ServerEnvironment::deactivateFarObjects(): "
- <<"deactivating object id="<<id<<" on inactive block "
- <<PP(blockpos_o)<<std::endl;
-
- // If known by some client, don't immediately delete.
- bool pending_delete = (obj->m_known_by_count > 0 && !force_delete);
-
- /*
- Update the static data
- */
-
- if(obj->isStaticAllowed())
- {
- // Create new static object
- std::string staticdata_new = obj->getStaticData();
- StaticObject s_obj(obj->getType(), objectpos, staticdata_new);
-
- bool stays_in_same_block = false;
- bool data_changed = true;
-
- if (obj->m_static_exists) {
- if (obj->m_static_block == blockpos_o)
- stays_in_same_block = true;
-
- MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
-
- if (block) {
- std::map<u16, StaticObject>::iterator n =
- block->m_static_objects.m_active.find(id);
- if (n != block->m_static_objects.m_active.end()) {
- StaticObject static_old = n->second;
-
- float save_movem = obj->getMinimumSavedMovement();
-
- if (static_old.data == staticdata_new &&
- (static_old.pos - objectpos).getLength() < save_movem)
- data_changed = false;
- } else {
- errorstream<<"ServerEnvironment::deactivateFarObjects(): "
- <<"id="<<id<<" m_static_exists=true but "
- <<"static data doesn't actually exist in "
- <<PP(obj->m_static_block)<<std::endl;
- }
- }
- }
-
- bool shall_be_written = (!stays_in_same_block || data_changed);
-
- // Delete old static object
- if(obj->m_static_exists)
- {
- MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
- if(block)
- {
- block->m_static_objects.remove(id);
- obj->m_static_exists = false;
- // Only mark block as modified if data changed considerably
- if(shall_be_written)
- block->raiseModified(MOD_STATE_WRITE_NEEDED,
- MOD_REASON_STATIC_DATA_CHANGED);
- }
- }
-
- // Add to the block where the object is located in
- v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
- // Get or generate the block
- MapBlock *block = NULL;
- try{
- block = m_map->emergeBlock(blockpos);
- } catch(InvalidPositionException &e){
- // Handled via NULL pointer
- // NOTE: emergeBlock's failure is usually determined by it
- // actually returning NULL
- }
-
- if(block)
- {
- if (block->m_static_objects.m_stored.size() >= g_settings->getU16("max_objects_per_block")) {
- warningstream << "ServerEnv: Trying to store id = " << obj->getId()
- << " statically but block " << PP(blockpos)
- << " already contains "
- << block->m_static_objects.m_stored.size()
- << " objects."
- << " Forcing delete." << std::endl;
- force_delete = true;
- } else {
- // If static counterpart already exists in target block,
- // remove it first.
- // This shouldn't happen because the object is removed from
- // the previous block before this according to
- // obj->m_static_block, but happens rarely for some unknown
- // reason. Unsuccessful attempts have been made to find
- // said reason.
- if(id && block->m_static_objects.m_active.find(id) != block->m_static_objects.m_active.end()){
- warningstream<<"ServerEnv: Performing hack #83274"
- <<std::endl;
- block->m_static_objects.remove(id);
- }
- // Store static data
- u16 store_id = pending_delete ? id : 0;
- block->m_static_objects.insert(store_id, s_obj);
-
- // Only mark block as modified if data changed considerably
- if(shall_be_written)
- block->raiseModified(MOD_STATE_WRITE_NEEDED,
- MOD_REASON_STATIC_DATA_CHANGED);
-
- obj->m_static_exists = true;
- obj->m_static_block = block->getPos();
- }
- }
- else{
- if(!force_delete){
- v3s16 p = floatToInt(objectpos, BS);
- errorstream<<"ServerEnv: Could not find or generate "
- <<"a block for storing id="<<obj->getId()
- <<" statically (pos="<<PP(p)<<")"<<std::endl;
- continue;
- }
- }
- }
-
- /*
- If known by some client, set pending deactivation.
- Otherwise delete it immediately.
- */
-
- if(pending_delete && !force_delete)
- {
- verbosestream<<"ServerEnvironment::deactivateFarObjects(): "
- <<"object id="<<id<<" is known by clients"
- <<"; not deleting yet"<<std::endl;
-
- obj->m_pending_deactivation = true;
- continue;
- }
-
- verbosestream<<"ServerEnvironment::deactivateFarObjects(): "
- <<"object id="<<id<<" is not known by clients"
- <<"; deleting"<<std::endl;
-
- // Tell the object about removal
- obj->removingFromEnvironment();
- // Deregister in scripting api
- m_script->removeObjectReference(obj);
-
- // Delete active object
- if(obj->environmentDeletes())
- delete obj;
- // Id to be removed from m_active_objects
- objects_to_remove.push_back(id);
- }
-
- // Remove references from m_active_objects
- for(std::vector<u16>::iterator i = objects_to_remove.begin();
- i != objects_to_remove.end(); ++i) {
- m_active_objects.erase(*i);
- }
-}
-
- etc.
*/
-#include <set>
#include <list>
#include <queue>
#include <map>
#include "threading/atomic.h"
#include "network/networkprotocol.h" // for AccessDeniedCode
-class ServerEnvironment;
-class ActiveBlockModifier;
-class ServerActiveObject;
class ITextureSource;
class IGameDef;
class Map;
-class ServerMap;
class GameScripting;
class Player;
-class RemotePlayer;
-class PlayerSAO;
class PointedThing;
class Environment
DISABLE_CLASS_COPY(Environment);
};
-/*
- {Active, Loading} block modifier interface.
-
- These are fed into ServerEnvironment at initialization time;
- ServerEnvironment handles deleting them.
-*/
-
-class ActiveBlockModifier
-{
-public:
- ActiveBlockModifier(){};
- virtual ~ActiveBlockModifier(){};
-
- // Set of contents to trigger on
- virtual std::set<std::string> getTriggerContents()=0;
- // Set of required neighbors (trigger doesn't happen if none are found)
- // Empty = do not check neighbors
- virtual std::set<std::string> getRequiredNeighbors()
- { return std::set<std::string>(); }
- // Trigger interval in seconds
- virtual float getTriggerInterval() = 0;
- // Random chance of (1 / return value), 0 is disallowed
- virtual u32 getTriggerChance() = 0;
- // Whether to modify chance to simulate time lost by an unnattended block
- virtual bool getSimpleCatchUp() = 0;
- // This is called usually at interval for 1/chance of the nodes
- virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n){};
- virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n,
- u32 active_object_count, u32 active_object_count_wider){};
-};
-
-struct ABMWithState
-{
- ActiveBlockModifier *abm;
- float timer;
-
- ABMWithState(ActiveBlockModifier *abm_);
-};
-
-struct LoadingBlockModifierDef
-{
- // Set of contents to trigger on
- std::set<std::string> trigger_contents;
- std::string name;
- bool run_at_every_load;
-
- virtual ~LoadingBlockModifierDef() {}
- virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n){};
-};
-
-struct LBMContentMapping
-{
- typedef std::map<content_t, std::vector<LoadingBlockModifierDef *> > container_map;
- container_map map;
-
- std::vector<LoadingBlockModifierDef *> lbm_list;
-
- // Needs to be separate method (not inside destructor),
- // because the LBMContentMapping may be copied and destructed
- // many times during operation in the lbm_lookup_map.
- void deleteContents();
- void addLBM(LoadingBlockModifierDef *lbm_def, IGameDef *gamedef);
- const std::vector<LoadingBlockModifierDef *> *lookup(content_t c) const;
-};
-
-class LBMManager
-{
-public:
- LBMManager():
- m_query_mode(false)
- {}
-
- ~LBMManager();
-
- // Don't call this after loadIntroductionTimes() ran.
- void addLBMDef(LoadingBlockModifierDef *lbm_def);
-
- void loadIntroductionTimes(const std::string ×,
- IGameDef *gamedef, u32 now);
-
- // Don't call this before loadIntroductionTimes() ran.
- std::string createIntroductionTimesString();
-
- // Don't call this before loadIntroductionTimes() ran.
- void applyLBMs(ServerEnvironment *env, MapBlock *block, u32 stamp);
-
- // Warning: do not make this std::unordered_map, order is relevant here
- typedef std::map<u32, LBMContentMapping> lbm_lookup_map;
-
-private:
- // Once we set this to true, we can only query,
- // not modify
- bool m_query_mode;
-
- // For m_query_mode == false:
- // The key of the map is the LBM def's name.
- // TODO make this std::unordered_map
- std::map<std::string, LoadingBlockModifierDef *> m_lbm_defs;
-
- // For m_query_mode == true:
- // The key of the map is the LBM def's first introduction time.
- lbm_lookup_map m_lbm_lookup;
-
- // Returns an iterator to the LBMs that were introduced
- // after the given time. This is guaranteed to return
- // valid values for everything
- lbm_lookup_map::const_iterator getLBMsIntroducedAfter(u32 time)
- { return m_lbm_lookup.lower_bound(time); }
-};
-
-/*
- List of active blocks, used by ServerEnvironment
-*/
-
-class ActiveBlockList
-{
-public:
- void update(std::vector<v3s16> &active_positions,
- s16 radius,
- std::set<v3s16> &blocks_removed,
- std::set<v3s16> &blocks_added);
-
- bool contains(v3s16 p){
- return (m_list.find(p) != m_list.end());
- }
-
- void clear(){
- m_list.clear();
- }
-
- std::set<v3s16> m_list;
- std::set<v3s16> m_forceloaded_list;
-
-private:
-};
-
-/*
- Operation mode for ServerEnvironment::clearObjects()
-*/
-enum ClearObjectsMode {
- // Load and go through every mapblock, clearing objects
- CLEAR_OBJECTS_MODE_FULL,
-
- // Clear objects immediately in loaded mapblocks;
- // clear objects in unloaded mapblocks only when the mapblocks are next activated.
- CLEAR_OBJECTS_MODE_QUICK,
-};
-
-/*
- The server-side environment.
-
- This is not thread-safe. Server uses an environment mutex.
-*/
-
-typedef UNORDERED_MAP<u16, ServerActiveObject *> ActiveObjectMap;
-
-class ServerEnvironment : public Environment
-{
-public:
- ServerEnvironment(ServerMap *map, GameScripting *scriptIface,
- IGameDef *gamedef, const std::string &path_world);
- ~ServerEnvironment();
-
- Map & getMap();
-
- ServerMap & getServerMap();
-
- //TODO find way to remove this fct!
- GameScripting* getScriptIface()
- { return m_script; }
-
- IGameDef *getGameDef()
- { return m_gamedef; }
-
- float getSendRecommendedInterval()
- { return m_recommended_send_interval; }
-
- void kickAllPlayers(AccessDeniedCode reason,
- const std::string &str_reason, bool reconnect);
- // Save players
- void saveLoadedPlayers();
- void savePlayer(RemotePlayer *player);
- RemotePlayer *loadPlayer(const std::string &playername, PlayerSAO *sao);
- void addPlayer(RemotePlayer *player);
- void removePlayer(RemotePlayer *player);
-
- /*
- Save and load time of day and game timer
- */
- void saveMeta();
- void loadMeta();
- // to be called instead of loadMeta if
- // env_meta.txt doesn't exist (e.g. new world)
- void loadDefaultMeta();
-
- u32 addParticleSpawner(float exptime);
- u32 addParticleSpawner(float exptime, u16 attached_id);
- void deleteParticleSpawner(u32 id, bool remove_from_object = true);
-
- /*
- External ActiveObject interface
- -------------------------------------------
- */
-
- ServerActiveObject* getActiveObject(u16 id);
-
- /*
- Add an active object to the environment.
- Environment handles deletion of object.
- Object may be deleted by environment immediately.
- If id of object is 0, assigns a free id to it.
- Returns the id of the object.
- Returns 0 if not added and thus deleted.
- */
- u16 addActiveObject(ServerActiveObject *object);
-
- /*
- Add an active object as a static object to the corresponding
- MapBlock.
- Caller allocates memory, ServerEnvironment frees memory.
- Return value: true if succeeded, false if failed.
- (note: not used, pending removal from engine)
- */
- //bool addActiveObjectAsStatic(ServerActiveObject *object);
-
- /*
- Find out what new objects have been added to
- inside a radius around a position
- */
- void getAddedActiveObjects(PlayerSAO *playersao, s16 radius,
- s16 player_radius,
- std::set<u16> ¤t_objects,
- std::queue<u16> &added_objects);
-
- /*
- Find out what new objects have been removed from
- inside a radius around a position
- */
- void getRemovedActiveObjects(PlayerSAO *playersao, s16 radius,
- s16 player_radius,
- std::set<u16> ¤t_objects,
- std::queue<u16> &removed_objects);
-
- /*
- Get the next message emitted by some active object.
- Returns a message with id=0 if no messages are available.
- */
- ActiveObjectMessage getActiveObjectMessage();
-
- /*
- Activate objects and dynamically modify for the dtime determined
- from timestamp and additional_dtime
- */
- void activateBlock(MapBlock *block, u32 additional_dtime=0);
-
- /*
- {Active,Loading}BlockModifiers
- -------------------------------------------
- */
-
- void addActiveBlockModifier(ActiveBlockModifier *abm);
- void addLoadingBlockModifierDef(LoadingBlockModifierDef *lbm);
-
- /*
- Other stuff
- -------------------------------------------
- */
-
- // Script-aware node setters
- bool setNode(v3s16 p, const MapNode &n);
- bool removeNode(v3s16 p);
- bool swapNode(v3s16 p, const MapNode &n);
-
- // Find all active objects inside a radius around a point
- void getObjectsInsideRadius(std::vector<u16> &objects, v3f pos, float radius);
-
- // Clear objects, loading and going through every MapBlock
- void clearObjects(ClearObjectsMode mode);
-
- // This makes stuff happen
- void step(f32 dtime);
-
- //check if there's a line of sight between two positions
- bool line_of_sight(v3f pos1, v3f pos2, float stepsize=1.0, v3s16 *p=NULL);
-
- u32 getGameTime() { return m_game_time; }
-
- void reportMaxLagEstimate(float f) { m_max_lag_estimate = f; }
- float getMaxLagEstimate() { return m_max_lag_estimate; }
-
- std::set<v3s16>* getForceloadedBlocks() { return &m_active_blocks.m_forceloaded_list; };
-
- // Sets the static object status all the active objects in the specified block
- // This is only really needed for deleting blocks from the map
- void setStaticForActiveObjectsInBlock(v3s16 blockpos,
- bool static_exists, v3s16 static_block=v3s16(0,0,0));
-
- RemotePlayer *getPlayer(const u16 peer_id);
- RemotePlayer *getPlayer(const char* name);
-private:
-
- /*
- Internal ActiveObject interface
- -------------------------------------------
- */
-
- /*
- Add an active object to the environment.
-
- Called by addActiveObject.
-
- Object may be deleted by environment immediately.
- If id of object is 0, assigns a free id to it.
- Returns the id of the object.
- Returns 0 if not added and thus deleted.
- */
- u16 addActiveObjectRaw(ServerActiveObject *object, bool set_changed, u32 dtime_s);
-
- /*
- Remove all objects that satisfy (m_removed && m_known_by_count==0)
- */
- void removeRemovedObjects();
-
- /*
- Convert stored objects from block to active
- */
- void activateObjects(MapBlock *block, u32 dtime_s);
-
- /*
- Convert objects that are not in active blocks to static.
-
- If m_known_by_count != 0, active object is not deleted, but static
- data is still updated.
-
- If force_delete is set, active object is deleted nevertheless. It
- shall only be set so in the destructor of the environment.
- */
- void deactivateFarObjects(bool force_delete);
-
- /*
- Member variables
- */
-
- // The map
- ServerMap *m_map;
- // Lua state
- GameScripting* m_script;
- // Game definition
- IGameDef *m_gamedef;
- // World path
- const std::string m_path_world;
- // Active object list
- ActiveObjectMap m_active_objects;
- // Outgoing network message buffer for active objects
- std::queue<ActiveObjectMessage> m_active_object_messages;
- // Some timers
- float m_send_recommended_timer;
- IntervalLimiter m_object_management_interval;
- // List of active blocks
- ActiveBlockList m_active_blocks;
- IntervalLimiter m_active_blocks_management_interval;
- IntervalLimiter m_active_block_modifier_interval;
- IntervalLimiter m_active_blocks_nodemetadata_interval;
- int m_active_block_interval_overload_skip;
- // Time from the beginning of the game in seconds.
- // Incremented in step().
- u32 m_game_time;
- // A helper variable for incrementing the latter
- float m_game_time_fraction_counter;
- // Time of last clearObjects call (game time).
- // When a mapblock older than this is loaded, its objects are cleared.
- u32 m_last_clear_objects_time;
- // Active block modifiers
- std::vector<ABMWithState> m_abms;
- LBMManager m_lbm_mgr;
- // An interval for generally sending object positions and stuff
- float m_recommended_send_interval;
- // Estimate for general maximum lag as determined by server.
- // Can raise to high values like 15s with eg. map generation mods.
- float m_max_lag_estimate;
-
- // peer_ids in here should be unique, except that there may be many 0s
- std::vector<RemotePlayer*> m_players;
-
- // Particles
- IntervalLimiter m_particle_management_interval;
- UNORDERED_MAP<u32, float> m_particle_spawners;
- UNORDERED_MAP<u32, u16> m_particle_spawner_attachments;
-};
-
#endif
#include "inventorymanager.h"
#include "log.h"
-#include "environment.h"
+#include "serverenvironment.h"
#include "scripting_game.h"
#include "serverobject.h"
#include "settings.h"
/******************************************************************************/
#include "pathfinder.h"
-#include "environment.h"
+#include "serverenvironment.h"
#include "gamedef.h"
#include "nodedef.h"
#include "map.h"
#define L_ENV_H_
#include "lua_api/l_base.h"
-#include "environment.h"
+#include "serverenvironment.h"
class ModApiEnvMod : public ModApiBase {
private:
#include "lua_api/l_inventory.h"
#include "common/c_converter.h"
#include "common/c_content.h"
-#include "environment.h"
+#include "serverenvironment.h"
#include "map.h"
#include "gamedef.h"
#include "nodemetadata.h"
#include "lua_api/l_nodetimer.h"
#include "lua_api/l_internal.h"
-#include "environment.h"
+#include "serverenvironment.h"
#include "map.h"
#include "util/numeric.h"
#include "util/thread.h"
#include "util/basic_macros.h"
-#include "environment.h"
+#include "serverenvironment.h"
#include "chat_interface.h"
#include "clientiface.h"
#include "remoteplayer.h"
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2017 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 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 Lesser General Public License for more details.
+
+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.
+*/
+
+#include "serverenvironment.h"
+#include "content_sao.h"
+#include "settings.h"
+#include "log.h"
+#include "nodedef.h"
+#include "nodemetadata.h"
+#include "gamedef.h"
+#include "map.h"
+#include "profiler.h"
+#include "raycast.h"
+#include "remoteplayer.h"
+#include "scripting_game.h"
+#include "server.h"
+#include "voxelalgorithms.h"
+#include "util/serialize.h"
+#include "util/basic_macros.h"
+#include "util/pointedthing.h"
+#include "threading/mutex_auto_lock.h"
+#include "filesys.h"
+
+#define LBM_NAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_:"
+
+// A number that is much smaller than the timeout for particle spawners should/could ever be
+#define PARTICLE_SPAWNER_NO_EXPIRY -1024.f
+
+/*
+ ABMWithState
+*/
+
+ABMWithState::ABMWithState(ActiveBlockModifier *abm_):
+ abm(abm_),
+ timer(0)
+{
+ // 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);
+}
+
+/*
+ LBMManager
+*/
+
+void LBMContentMapping::deleteContents()
+{
+ for (std::vector<LoadingBlockModifierDef *>::iterator it = lbm_list.begin();
+ it != lbm_list.end(); ++it) {
+ delete *it;
+ }
+}
+
+void LBMContentMapping::addLBM(LoadingBlockModifierDef *lbm_def, IGameDef *gamedef)
+{
+ // Add the lbm_def to the LBMContentMapping.
+ // Unknown names get added to the global NameIdMapping.
+ INodeDefManager *nodedef = gamedef->ndef();
+
+ lbm_list.push_back(lbm_def);
+
+ for (std::set<std::string>::const_iterator it = lbm_def->trigger_contents.begin();
+ it != lbm_def->trigger_contents.end(); ++it) {
+ std::set<content_t> c_ids;
+ bool found = nodedef->getIds(*it, c_ids);
+ if (!found) {
+ content_t c_id = gamedef->allocateUnknownNodeId(*it);
+ if (c_id == CONTENT_IGNORE) {
+ // Seems it can't be allocated.
+ warningstream << "Could not internalize node name \"" << *it
+ << "\" while loading LBM \"" << lbm_def->name << "\"." << std::endl;
+ continue;
+ }
+ c_ids.insert(c_id);
+ }
+
+ for (std::set<content_t>::const_iterator iit =
+ c_ids.begin(); iit != c_ids.end(); ++iit) {
+ content_t c_id = *iit;
+ map[c_id].push_back(lbm_def);
+ }
+ }
+}
+
+const std::vector<LoadingBlockModifierDef *> *
+LBMContentMapping::lookup(content_t c) const
+{
+ container_map::const_iterator it = map.find(c);
+ if (it == map.end())
+ return NULL;
+ // This first dereferences the iterator, returning
+ // a std::vector<LoadingBlockModifierDef *>
+ // reference, then we convert it to a pointer.
+ return &(it->second);
+}
+
+LBMManager::~LBMManager()
+{
+ for (std::map<std::string, LoadingBlockModifierDef *>::iterator it =
+ m_lbm_defs.begin(); it != m_lbm_defs.end(); ++it) {
+ delete it->second;
+ }
+ for (lbm_lookup_map::iterator it = m_lbm_lookup.begin();
+ it != m_lbm_lookup.end(); ++it) {
+ (it->second).deleteContents();
+ }
+}
+
+void LBMManager::addLBMDef(LoadingBlockModifierDef *lbm_def)
+{
+ // Precondition, in query mode the map isn't used anymore
+ FATAL_ERROR_IF(m_query_mode == true,
+ "attempted to modify LBMManager in query mode");
+
+ if (!string_allowed(lbm_def->name, LBM_NAME_ALLOWED_CHARS)) {
+ throw ModError("Error adding LBM \"" + lbm_def->name +
+ "\": Does not follow naming conventions: "
+ "Only chararacters [a-z0-9_:] are allowed.");
+ }
+
+ m_lbm_defs[lbm_def->name] = lbm_def;
+}
+
+void LBMManager::loadIntroductionTimes(const std::string ×,
+ IGameDef *gamedef, u32 now)
+{
+ m_query_mode = true;
+
+ // name -> time map.
+ // Storing it in a map first instead of
+ // handling the stuff directly in the loop
+ // removes all duplicate entries.
+ // TODO make this std::unordered_map
+ std::map<std::string, u32> introduction_times;
+
+ /*
+ The introduction times string consists of name~time entries,
+ with each entry terminated by a semicolon. The time is decimal.
+ */
+
+ size_t idx = 0;
+ size_t idx_new;
+ while ((idx_new = times.find(";", idx)) != std::string::npos) {
+ std::string entry = times.substr(idx, idx_new - idx);
+ std::vector<std::string> components = str_split(entry, '~');
+ if (components.size() != 2)
+ throw SerializationError("Introduction times entry \""
+ + entry + "\" requires exactly one '~'!");
+ const std::string &name = components[0];
+ u32 time = from_string<u32>(components[1]);
+ introduction_times[name] = time;
+ idx = idx_new + 1;
+ }
+
+ // Put stuff from introduction_times into m_lbm_lookup
+ for (std::map<std::string, u32>::const_iterator it = introduction_times.begin();
+ it != introduction_times.end(); ++it) {
+ const std::string &name = it->first;
+ u32 time = it->second;
+
+ std::map<std::string, LoadingBlockModifierDef *>::iterator def_it =
+ m_lbm_defs.find(name);
+ if (def_it == m_lbm_defs.end()) {
+ // This seems to be an LBM entry for
+ // an LBM we haven't loaded. Discard it.
+ continue;
+ }
+ LoadingBlockModifierDef *lbm_def = def_it->second;
+ if (lbm_def->run_at_every_load) {
+ // This seems to be an LBM entry for
+ // an LBM that runs at every load.
+ // Don't add it just yet.
+ continue;
+ }
+
+ m_lbm_lookup[time].addLBM(lbm_def, gamedef);
+
+ // Erase the entry so that we know later
+ // what elements didn't get put into m_lbm_lookup
+ m_lbm_defs.erase(name);
+ }
+
+ // Now also add the elements from m_lbm_defs to m_lbm_lookup
+ // that weren't added in the previous step.
+ // They are introduced first time to this world,
+ // or are run at every load (introducement time hardcoded to U32_MAX).
+
+ LBMContentMapping &lbms_we_introduce_now = m_lbm_lookup[now];
+ LBMContentMapping &lbms_running_always = m_lbm_lookup[U32_MAX];
+
+ for (std::map<std::string, LoadingBlockModifierDef *>::iterator it =
+ m_lbm_defs.begin(); it != m_lbm_defs.end(); ++it) {
+ if (it->second->run_at_every_load) {
+ lbms_running_always.addLBM(it->second, gamedef);
+ } else {
+ lbms_we_introduce_now.addLBM(it->second, gamedef);
+ }
+ }
+
+ // Clear the list, so that we don't delete remaining elements
+ // twice in the destructor
+ m_lbm_defs.clear();
+}
+
+std::string LBMManager::createIntroductionTimesString()
+{
+ // Precondition, we must be in query mode
+ FATAL_ERROR_IF(m_query_mode == false,
+ "attempted to query on non fully set up LBMManager");
+
+ std::ostringstream oss;
+ for (lbm_lookup_map::iterator it = m_lbm_lookup.begin();
+ it != m_lbm_lookup.end(); ++it) {
+ u32 time = it->first;
+ std::vector<LoadingBlockModifierDef *> &lbm_list = it->second.lbm_list;
+ for (std::vector<LoadingBlockModifierDef *>::iterator iit = lbm_list.begin();
+ iit != lbm_list.end(); ++iit) {
+ // Don't add if the LBM runs at every load,
+ // then introducement time is hardcoded
+ // and doesn't need to be stored
+ if ((*iit)->run_at_every_load)
+ continue;
+ oss << (*iit)->name << "~" << time << ";";
+ }
+ }
+ return oss.str();
+}
+
+void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, u32 stamp)
+{
+ // Precondition, we need m_lbm_lookup to be initialized
+ FATAL_ERROR_IF(m_query_mode == false,
+ "attempted to query on non fully set up LBMManager");
+ v3s16 pos_of_block = block->getPosRelative();
+ v3s16 pos;
+ MapNode n;
+ content_t c;
+ lbm_lookup_map::const_iterator it = getLBMsIntroducedAfter(stamp);
+ for (pos.X = 0; pos.X < MAP_BLOCKSIZE; pos.X++)
+ for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++)
+ for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++)
+ {
+ n = block->getNodeNoEx(pos);
+ c = n.getContent();
+ for (LBMManager::lbm_lookup_map::const_iterator iit = it;
+ iit != m_lbm_lookup.end(); ++iit) {
+ const std::vector<LoadingBlockModifierDef *> *lbm_list =
+ iit->second.lookup(c);
+ if (!lbm_list)
+ continue;
+ for (std::vector<LoadingBlockModifierDef *>::const_iterator iit =
+ lbm_list->begin(); iit != lbm_list->end(); ++iit) {
+ (*iit)->trigger(env, pos + pos_of_block, n);
+ }
+ }
+ }
+}
+
+/*
+ ActiveBlockList
+*/
+
+void fillRadiusBlock(v3s16 p0, s16 r, std::set<v3s16> &list)
+{
+ v3s16 p;
+ for(p.X=p0.X-r; p.X<=p0.X+r; p.X++)
+ for(p.Y=p0.Y-r; p.Y<=p0.Y+r; p.Y++)
+ for(p.Z=p0.Z-r; p.Z<=p0.Z+r; p.Z++)
+ {
+ // limit to a sphere
+ if (p.getDistanceFrom(p0) <= r) {
+ // Set in list
+ list.insert(p);
+ }
+ }
+}
+
+void ActiveBlockList::update(std::vector<v3s16> &active_positions,
+ s16 radius,
+ std::set<v3s16> &blocks_removed,
+ std::set<v3s16> &blocks_added)
+{
+ /*
+ Create the new list
+ */
+ std::set<v3s16> newlist = m_forceloaded_list;
+ for(std::vector<v3s16>::iterator i = active_positions.begin();
+ i != active_positions.end(); ++i)
+ {
+ fillRadiusBlock(*i, radius, newlist);
+ }
+
+ /*
+ Find out which blocks on the old list are not on the new list
+ */
+ // Go through old list
+ for(std::set<v3s16>::iterator i = m_list.begin();
+ i != m_list.end(); ++i)
+ {
+ v3s16 p = *i;
+ // If not on new list, it's been removed
+ if(newlist.find(p) == newlist.end())
+ blocks_removed.insert(p);
+ }
+
+ /*
+ Find out which blocks on the new list are not on the old list
+ */
+ // Go through new list
+ for(std::set<v3s16>::iterator i = newlist.begin();
+ i != newlist.end(); ++i)
+ {
+ v3s16 p = *i;
+ // If not on old list, it's been added
+ if(m_list.find(p) == m_list.end())
+ blocks_added.insert(p);
+ }
+
+ /*
+ Update m_list
+ */
+ m_list.clear();
+ for(std::set<v3s16>::iterator i = newlist.begin();
+ i != newlist.end(); ++i)
+ {
+ v3s16 p = *i;
+ m_list.insert(p);
+ }
+}
+
+/*
+ ServerEnvironment
+*/
+
+ServerEnvironment::ServerEnvironment(ServerMap *map,
+ GameScripting *scriptIface, IGameDef *gamedef,
+ const std::string &path_world) :
+ m_map(map),
+ m_script(scriptIface),
+ m_gamedef(gamedef),
+ m_path_world(path_world),
+ m_send_recommended_timer(0),
+ m_active_block_interval_overload_skip(0),
+ m_game_time(0),
+ m_game_time_fraction_counter(0),
+ m_last_clear_objects_time(0),
+ m_recommended_send_interval(0.1),
+ m_max_lag_estimate(0.1)
+{
+}
+
+ServerEnvironment::~ServerEnvironment()
+{
+ // Clear active block list.
+ // This makes the next one delete all active objects.
+ m_active_blocks.clear();
+
+ // Convert all objects to static and delete the active objects
+ deactivateFarObjects(true);
+
+ // Drop/delete map
+ m_map->drop();
+
+ // Delete ActiveBlockModifiers
+ for (std::vector<ABMWithState>::iterator
+ i = m_abms.begin(); i != m_abms.end(); ++i){
+ delete i->abm;
+ }
+
+ // Deallocate players
+ for (std::vector<RemotePlayer *>::iterator i = m_players.begin();
+ i != m_players.end(); ++i) {
+ delete (*i);
+ }
+}
+
+Map & ServerEnvironment::getMap()
+{
+ return *m_map;
+}
+
+ServerMap & ServerEnvironment::getServerMap()
+{
+ return *m_map;
+}
+
+RemotePlayer *ServerEnvironment::getPlayer(const u16 peer_id)
+{
+ for (std::vector<RemotePlayer *>::iterator i = m_players.begin();
+ i != m_players.end(); ++i) {
+ RemotePlayer *player = *i;
+ if (player->peer_id == peer_id)
+ return player;
+ }
+ return NULL;
+}
+
+RemotePlayer *ServerEnvironment::getPlayer(const char* name)
+{
+ for (std::vector<RemotePlayer *>::iterator i = m_players.begin();
+ i != m_players.end(); ++i) {
+ RemotePlayer *player = *i;
+ if (strcmp(player->getName(), name) == 0)
+ return player;
+ }
+ return NULL;
+}
+
+void ServerEnvironment::addPlayer(RemotePlayer *player)
+{
+ DSTACK(FUNCTION_NAME);
+ /*
+ Check that peer_ids are unique.
+ Also check that names are unique.
+ Exception: there can be multiple players with peer_id=0
+ */
+ // If peer id is non-zero, it has to be unique.
+ if (player->peer_id != 0)
+ FATAL_ERROR_IF(getPlayer(player->peer_id) != NULL, "Peer id not unique");
+ // Name has to be unique.
+ FATAL_ERROR_IF(getPlayer(player->getName()) != NULL, "Player name not unique");
+ // Add.
+ m_players.push_back(player);
+}
+
+void ServerEnvironment::removePlayer(RemotePlayer *player)
+{
+ for (std::vector<RemotePlayer *>::iterator it = m_players.begin();
+ it != m_players.end(); ++it) {
+ if ((*it) == player) {
+ delete *it;
+ m_players.erase(it);
+ return;
+ }
+ }
+}
+
+bool ServerEnvironment::line_of_sight(v3f pos1, v3f pos2, float stepsize, v3s16 *p)
+{
+ float distance = pos1.getDistanceFrom(pos2);
+
+ //calculate normalized direction vector
+ v3f normalized_vector = v3f((pos2.X - pos1.X)/distance,
+ (pos2.Y - pos1.Y)/distance,
+ (pos2.Z - pos1.Z)/distance);
+
+ //find out if there's a node on path between pos1 and pos2
+ for (float i = 1; i < distance; i += stepsize) {
+ v3s16 pos = floatToInt(v3f(normalized_vector.X * i,
+ normalized_vector.Y * i,
+ normalized_vector.Z * i) +pos1,BS);
+
+ MapNode n = getMap().getNodeNoEx(pos);
+
+ if(n.param0 != CONTENT_AIR) {
+ if (p) {
+ *p = pos;
+ }
+ return false;
+ }
+ }
+ return true;
+}
+
+void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason,
+ const std::string &str_reason, bool reconnect)
+{
+ for (std::vector<RemotePlayer *>::iterator it = m_players.begin();
+ it != m_players.end(); ++it) {
+ RemotePlayer *player = dynamic_cast<RemotePlayer *>(*it);
+ ((Server*)m_gamedef)->DenyAccessVerCompliant(player->peer_id,
+ player->protocol_version, reason, str_reason, reconnect);
+ }
+}
+
+void ServerEnvironment::saveLoadedPlayers()
+{
+ std::string players_path = m_path_world + DIR_DELIM "players";
+ fs::CreateDir(players_path);
+
+ for (std::vector<RemotePlayer *>::iterator it = m_players.begin();
+ it != m_players.end();
+ ++it) {
+ if ((*it)->checkModified()) {
+ (*it)->save(players_path, m_gamedef);
+ }
+ }
+}
+
+void ServerEnvironment::savePlayer(RemotePlayer *player)
+{
+ std::string players_path = m_path_world + DIR_DELIM "players";
+ fs::CreateDir(players_path);
+
+ player->save(players_path, m_gamedef);
+}
+
+RemotePlayer *ServerEnvironment::loadPlayer(const std::string &playername, PlayerSAO *sao)
+{
+ bool newplayer = false;
+ bool found = false;
+ std::string players_path = m_path_world + DIR_DELIM "players" DIR_DELIM;
+ std::string path = players_path + playername;
+
+ RemotePlayer *player = getPlayer(playername.c_str());
+ if (!player) {
+ player = new RemotePlayer("", m_gamedef->idef());
+ newplayer = true;
+ }
+
+ for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
+ //// Open file and deserialize
+ std::ifstream is(path.c_str(), std::ios_base::binary);
+ if (!is.good())
+ continue;
+
+ player->deSerialize(is, path, sao);
+ is.close();
+
+ if (player->getName() == playername) {
+ found = true;
+ break;
+ }
+
+ path = players_path + playername + itos(i);
+ }
+
+ if (!found) {
+ infostream << "Player file for player " << playername
+ << " not found" << std::endl;
+ if (newplayer)
+ delete player;
+
+ return NULL;
+ }
+
+ if (newplayer) {
+ addPlayer(player);
+ }
+ player->setModified(false);
+ return player;
+}
+
+void ServerEnvironment::saveMeta()
+{
+ std::string path = m_path_world + DIR_DELIM "env_meta.txt";
+
+ // Open file and serialize
+ std::ostringstream ss(std::ios_base::binary);
+
+ Settings args;
+ args.setU64("game_time", m_game_time);
+ args.setU64("time_of_day", getTimeOfDay());
+ args.setU64("last_clear_objects_time", m_last_clear_objects_time);
+ args.setU64("lbm_introduction_times_version", 1);
+ args.set("lbm_introduction_times",
+ m_lbm_mgr.createIntroductionTimesString());
+ args.setU64("day_count", m_day_count);
+ args.writeLines(ss);
+ ss<<"EnvArgsEnd\n";
+
+ if(!fs::safeWriteToFile(path, ss.str()))
+ {
+ infostream<<"ServerEnvironment::saveMeta(): Failed to write "
+ <<path<<std::endl;
+ throw SerializationError("Couldn't save env meta");
+ }
+}
+
+void ServerEnvironment::loadMeta()
+{
+ std::string path = m_path_world + DIR_DELIM "env_meta.txt";
+
+ // Open file and deserialize
+ std::ifstream is(path.c_str(), std::ios_base::binary);
+ if (!is.good()) {
+ infostream << "ServerEnvironment::loadMeta(): Failed to open "
+ << path << std::endl;
+ throw SerializationError("Couldn't load env meta");
+ }
+
+ Settings args;
+
+ if (!args.parseConfigLines(is, "EnvArgsEnd")) {
+ throw SerializationError("ServerEnvironment::loadMeta(): "
+ "EnvArgsEnd not found!");
+ }
+
+ try {
+ m_game_time = args.getU64("game_time");
+ } catch (SettingNotFoundException &e) {
+ // Getting this is crucial, otherwise timestamps are useless
+ throw SerializationError("Couldn't load env meta game_time");
+ }
+
+ setTimeOfDay(args.exists("time_of_day") ?
+ // set day to morning by default
+ args.getU64("time_of_day") : 9000);
+
+ m_last_clear_objects_time = args.exists("last_clear_objects_time") ?
+ // If missing, do as if clearObjects was never called
+ args.getU64("last_clear_objects_time") : 0;
+
+ std::string lbm_introduction_times = "";
+ try {
+ u64 ver = args.getU64("lbm_introduction_times_version");
+ if (ver == 1) {
+ lbm_introduction_times = args.get("lbm_introduction_times");
+ } else {
+ infostream << "ServerEnvironment::loadMeta(): Non-supported"
+ << " introduction time version " << ver << std::endl;
+ }
+ } catch (SettingNotFoundException &e) {
+ // No problem, this is expected. Just continue with an empty string
+ }
+ m_lbm_mgr.loadIntroductionTimes(lbm_introduction_times, m_gamedef, m_game_time);
+
+ m_day_count = args.exists("day_count") ?
+ args.getU64("day_count") : 0;
+}
+
+void ServerEnvironment::loadDefaultMeta()
+{
+ m_lbm_mgr.loadIntroductionTimes("", m_gamedef, m_game_time);
+}
+
+struct ActiveABM
+{
+ ActiveBlockModifier *abm;
+ int chance;
+ std::set<content_t> required_neighbors;
+};
+
+class ABMHandler
+{
+private:
+ ServerEnvironment *m_env;
+ std::vector<std::vector<ActiveABM> *> m_aabms;
+public:
+ ABMHandler(std::vector<ABMWithState> &abms,
+ float dtime_s, ServerEnvironment *env,
+ bool use_timers):
+ m_env(env)
+ {
+ if(dtime_s < 0.001)
+ return;
+ INodeDefManager *ndef = env->getGameDef()->ndef();
+ for(std::vector<ABMWithState>::iterator
+ i = abms.begin(); i != abms.end(); ++i) {
+ ActiveBlockModifier *abm = i->abm;
+ 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;
+ }
+ float chance = abm->getTriggerChance();
+ if(chance == 0)
+ chance = 1;
+ ActiveABM aabm;
+ aabm.abm = abm;
+ if(abm->getSimpleCatchUp()) {
+ float intervals = actual_interval / trigger_interval;
+ if(intervals == 0)
+ continue;
+ aabm.chance = chance / intervals;
+ if(aabm.chance == 0)
+ aabm.chance = 1;
+ } else {
+ aabm.chance = chance;
+ }
+ // 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)
+ {
+ 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;
+ if (c >= m_aabms.size())
+ m_aabms.resize(c + 256, NULL);
+ if (!m_aabms[c])
+ m_aabms[c] = new std::vector<ActiveABM>;
+ m_aabms[c]->push_back(aabm);
+ }
+ }
+ }
+ }
+
+ ~ABMHandler()
+ {
+ for (size_t i = 0; i < m_aabms.size(); i++)
+ delete m_aabms[i];
+ }
+
+ // Find out how many objects the given block and its neighbours contain.
+ // Returns the number of objects in the block, and also in 'wider' the
+ // number of objects in the block and all its neighbours. The latter
+ // may an estimate if any neighbours are unloaded.
+ u32 countObjects(MapBlock *block, ServerMap * map, u32 &wider)
+ {
+ 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){
+ wider_unknown_count++;
+ continue;
+ }
+ wider += block2->m_static_objects.m_active.size()
+ + block2->m_static_objects.m_stored.size();
+ }
+ // Extrapolate
+ u32 active_object_count = block->m_static_objects.m_active.size();
+ u32 wider_known_count = 3*3*3 - wider_unknown_count;
+ wider += wider_unknown_count * wider / wider_known_count;
+ return active_object_count;
+
+ }
+ void apply(MapBlock *block)
+ {
+ if(m_aabms.empty() || block->isDummy())
+ return;
+
+ ServerMap *map = &m_env->getServerMap();
+
+ u32 active_object_count_wider;
+ u32 active_object_count = this->countObjects(block, map, active_object_count_wider);
+ m_env->m_added_objects = 0;
+
+ v3s16 p0;
+ for(p0.X=0; p0.X<MAP_BLOCKSIZE; p0.X++)
+ for(p0.Y=0; p0.Y<MAP_BLOCKSIZE; p0.Y++)
+ for(p0.Z=0; p0.Z<MAP_BLOCKSIZE; p0.Z++)
+ {
+ const MapNode &n = block->getNodeUnsafe(p0);
+ content_t c = n.getContent();
+
+ if (c >= m_aabms.size() || !m_aabms[c])
+ continue;
+
+ v3s16 p = p0 + block->getPosRelative();
+ for(std::vector<ActiveABM>::iterator
+ i = m_aabms[c]->begin(); i != m_aabms[c]->end(); ++i) {
+ if(myrand() % i->chance != 0)
+ continue;
+
+ // Check neighbors
+ if(!i->required_neighbors.empty())
+ {
+ v3s16 p1;
+ for(p1.X = p0.X-1; p1.X <= p0.X+1; p1.X++)
+ for(p1.Y = p0.Y-1; p1.Y <= p0.Y+1; p1.Y++)
+ for(p1.Z = p0.Z-1; p1.Z <= p0.Z+1; p1.Z++)
+ {
+ if(p1 == p0)
+ continue;
+ content_t c;
+ if (block->isValidPosition(p1)) {
+ // if the neighbor is found on the same map block
+ // get it straight from there
+ const MapNode &n = block->getNodeUnsafe(p1);
+ c = n.getContent();
+ } else {
+ // otherwise consult the map
+ MapNode n = map->getNodeNoEx(p1 + block->getPosRelative());
+ 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:
+
+ // Call all the trigger variations
+ i->abm->trigger(m_env, p, n);
+ i->abm->trigger(m_env, p, n,
+ active_object_count, active_object_count_wider);
+
+ // Count surrounding objects again if the abms added any
+ if(m_env->m_added_objects > 0) {
+ active_object_count = countObjects(block, map, active_object_count_wider);
+ m_env->m_added_objects = 0;
+ }
+ }
+ }
+ }
+};
+
+void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime)
+{
+ // Reset usage timer immediately, otherwise a block that becomes active
+ // again at around the same time as it would normally be unloaded will
+ // get unloaded incorrectly. (I think this still leaves a small possibility
+ // of a race condition between this and server::AsyncRunStep, which only
+ // some kind of synchronisation will fix, but it at least reduces the window
+ // of opportunity for it to break from seconds to nanoseconds)
+ block->resetUsageTimer();
+
+ // Get time difference
+ u32 dtime_s = 0;
+ u32 stamp = block->getTimestamp();
+ if (m_game_time > stamp && stamp != BLOCK_TIMESTAMP_UNDEFINED)
+ dtime_s = m_game_time - stamp;
+ dtime_s += additional_dtime;
+
+ /*infostream<<"ServerEnvironment::activateBlock(): block timestamp: "
+ <<stamp<<", game time: "<<m_game_time<<std::endl;*/
+
+ // Remove stored static objects if clearObjects was called since block's timestamp
+ if (stamp == BLOCK_TIMESTAMP_UNDEFINED || stamp < m_last_clear_objects_time) {
+ block->m_static_objects.m_stored.clear();
+ // do not set changed flag to avoid unnecessary mapblock writes
+ }
+
+ // Set current time as timestamp
+ block->setTimestampNoChangedFlag(m_game_time);
+
+ /*infostream<<"ServerEnvironment::activateBlock(): block is "
+ <<dtime_s<<" seconds old."<<std::endl;*/
+
+ // Activate stored objects
+ activateObjects(block, dtime_s);
+
+ /* Handle LoadingBlockModifiers */
+ m_lbm_mgr.applyLBMs(this, block, stamp);
+
+ // Run node timers
+ std::vector<NodeTimer> elapsed_timers =
+ block->m_node_timers.step((float)dtime_s);
+ if (!elapsed_timers.empty()) {
+ MapNode n;
+ for (std::vector<NodeTimer>::iterator
+ i = elapsed_timers.begin();
+ i != elapsed_timers.end(); ++i){
+ n = block->getNodeNoEx(i->position);
+ v3s16 p = i->position + block->getPosRelative();
+ if (m_script->node_on_timer(p, n, i->elapsed))
+ block->setNodeTimer(NodeTimer(i->timeout, 0, i->position));
+ }
+ }
+
+ /* Handle ActiveBlockModifiers */
+ ABMHandler abmhandler(m_abms, dtime_s, this, false);
+ abmhandler.apply(block);
+}
+
+void ServerEnvironment::addActiveBlockModifier(ActiveBlockModifier *abm)
+{
+ m_abms.push_back(ABMWithState(abm));
+}
+
+void ServerEnvironment::addLoadingBlockModifierDef(LoadingBlockModifierDef *lbm)
+{
+ m_lbm_mgr.addLBMDef(lbm);
+}
+
+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)
+ m_script->node_on_destruct(p, n_old);
+
+ // Replace node
+ if (!m_map->addNodeWithEvent(p, n))
+ return false;
+
+ // Update active VoxelManipulator if a mapgen thread
+ m_map->updateVManip(p);
+
+ // Call post-destructor
+ if (ndef->get(n_old).has_after_destruct)
+ m_script->node_after_destruct(p, n_old);
+
+ // Call constructor
+ if (ndef->get(n).has_on_construct)
+ m_script->node_on_construct(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)
+ m_script->node_on_destruct(p, n_old);
+
+ // Replace with air
+ // This is slightly optimized compared to addNodeWithEvent(air)
+ if (!m_map->removeNodeWithEvent(p))
+ return false;
+
+ // Update active VoxelManipulator if a mapgen thread
+ m_map->updateVManip(p);
+
+ // Call post-destructor
+ if (ndef->get(n_old).has_after_destruct)
+ m_script->node_after_destruct(p, n_old);
+
+ // Air doesn't require constructor
+ return true;
+}
+
+bool ServerEnvironment::swapNode(v3s16 p, const MapNode &n)
+{
+ if (!m_map->addNodeWithEvent(p, n, false))
+ return false;
+
+ // Update active VoxelManipulator if a mapgen thread
+ m_map->updateVManip(p);
+
+ return true;
+}
+
+void ServerEnvironment::getObjectsInsideRadius(std::vector<u16> &objects, v3f pos, float radius)
+{
+ for (ActiveObjectMap::iterator i = m_active_objects.begin();
+ i != m_active_objects.end(); ++i) {
+ ServerActiveObject* obj = i->second;
+ u16 id = i->first;
+ v3f objectpos = obj->getBasePosition();
+ if (objectpos.getDistanceFrom(pos) > radius)
+ continue;
+ objects.push_back(id);
+ }
+}
+
+void ServerEnvironment::clearObjects(ClearObjectsMode mode)
+{
+ infostream << "ServerEnvironment::clearObjects(): "
+ << "Removing all active objects" << std::endl;
+ std::vector<u16> objects_to_remove;
+ for (ActiveObjectMap::iterator i = m_active_objects.begin();
+ i != m_active_objects.end(); ++i) {
+ ServerActiveObject* obj = i->second;
+ if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER)
+ continue;
+ u16 id = i->first;
+ // Delete static object if block is loaded
+ if (obj->m_static_exists) {
+ MapBlock *block = m_map->getBlockNoCreateNoEx(obj->m_static_block);
+ if (block) {
+ block->m_static_objects.remove(id);
+ block->raiseModified(MOD_STATE_WRITE_NEEDED,
+ MOD_REASON_CLEAR_ALL_OBJECTS);
+ obj->m_static_exists = false;
+ }
+ }
+ // If known by some client, don't delete immediately
+ if (obj->m_known_by_count > 0) {
+ obj->m_pending_deactivation = true;
+ obj->m_removed = true;
+ continue;
+ }
+
+ // Tell the object about removal
+ obj->removingFromEnvironment();
+ // Deregister in scripting api
+ m_script->removeObjectReference(obj);
+
+ // Delete active object
+ if (obj->environmentDeletes())
+ delete obj;
+ // Id to be removed from m_active_objects
+ objects_to_remove.push_back(id);
+ }
+
+ // Remove references from m_active_objects
+ for (std::vector<u16>::iterator i = objects_to_remove.begin();
+ i != objects_to_remove.end(); ++i) {
+ m_active_objects.erase(*i);
+ }
+
+ // Get list of loaded blocks
+ std::vector<v3s16> loaded_blocks;
+ infostream << "ServerEnvironment::clearObjects(): "
+ << "Listing all loaded blocks" << std::endl;
+ m_map->listAllLoadedBlocks(loaded_blocks);
+ infostream << "ServerEnvironment::clearObjects(): "
+ << "Done listing all loaded blocks: "
+ << loaded_blocks.size()<<std::endl;
+
+ // Get list of loadable blocks
+ std::vector<v3s16> loadable_blocks;
+ if (mode == CLEAR_OBJECTS_MODE_FULL) {
+ infostream << "ServerEnvironment::clearObjects(): "
+ << "Listing all loadable blocks" << std::endl;
+ m_map->listAllLoadableBlocks(loadable_blocks);
+ infostream << "ServerEnvironment::clearObjects(): "
+ << "Done listing all loadable blocks: "
+ << loadable_blocks.size() << std::endl;
+ } else {
+ loadable_blocks = loaded_blocks;
+ }
+
+ infostream << "ServerEnvironment::clearObjects(): "
+ << "Now clearing objects in " << loadable_blocks.size()
+ << " blocks" << std::endl;
+
+ // Grab a reference on each loaded block to avoid unloading it
+ for (std::vector<v3s16>::iterator i = loaded_blocks.begin();
+ i != loaded_blocks.end(); ++i) {
+ v3s16 p = *i;
+ MapBlock *block = m_map->getBlockNoCreateNoEx(p);
+ assert(block != NULL);
+ block->refGrab();
+ }
+
+ // Remove objects in all loadable blocks
+ u32 unload_interval = U32_MAX;
+ if (mode == CLEAR_OBJECTS_MODE_FULL) {
+ unload_interval = g_settings->getS32("max_clearobjects_extra_loaded_blocks");
+ unload_interval = MYMAX(unload_interval, 1);
+ }
+ u32 report_interval = loadable_blocks.size() / 10;
+ u32 num_blocks_checked = 0;
+ u32 num_blocks_cleared = 0;
+ u32 num_objs_cleared = 0;
+ for (std::vector<v3s16>::iterator i = loadable_blocks.begin();
+ i != loadable_blocks.end(); ++i) {
+ v3s16 p = *i;
+ MapBlock *block = m_map->emergeBlock(p, false);
+ if (!block) {
+ errorstream << "ServerEnvironment::clearObjects(): "
+ << "Failed to emerge block " << PP(p) << std::endl;
+ continue;
+ }
+ u32 num_stored = block->m_static_objects.m_stored.size();
+ u32 num_active = block->m_static_objects.m_active.size();
+ if (num_stored != 0 || num_active != 0) {
+ block->m_static_objects.m_stored.clear();
+ block->m_static_objects.m_active.clear();
+ block->raiseModified(MOD_STATE_WRITE_NEEDED,
+ MOD_REASON_CLEAR_ALL_OBJECTS);
+ num_objs_cleared += num_stored + num_active;
+ num_blocks_cleared++;
+ }
+ num_blocks_checked++;
+
+ if (report_interval != 0 &&
+ num_blocks_checked % report_interval == 0) {
+ float percent = 100.0 * (float)num_blocks_checked /
+ loadable_blocks.size();
+ infostream << "ServerEnvironment::clearObjects(): "
+ << "Cleared " << num_objs_cleared << " objects"
+ << " in " << num_blocks_cleared << " blocks ("
+ << percent << "%)" << std::endl;
+ }
+ if (num_blocks_checked % unload_interval == 0) {
+ m_map->unloadUnreferencedBlocks();
+ }
+ }
+ m_map->unloadUnreferencedBlocks();
+
+ // Drop references that were added above
+ for (std::vector<v3s16>::iterator i = loaded_blocks.begin();
+ i != loaded_blocks.end(); ++i) {
+ v3s16 p = *i;
+ MapBlock *block = m_map->getBlockNoCreateNoEx(p);
+ assert(block);
+ block->refDrop();
+ }
+
+ m_last_clear_objects_time = m_game_time;
+
+ infostream << "ServerEnvironment::clearObjects(): "
+ << "Finished: Cleared " << num_objs_cleared << " objects"
+ << " in " << num_blocks_cleared << " blocks" << std::endl;
+}
+
+void ServerEnvironment::step(float dtime)
+{
+ DSTACK(FUNCTION_NAME);
+
+ //TimeTaker timer("ServerEnv step");
+
+ /* 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.
+ static const float server_step = g_settings->getFloat("dedicated_server_step");
+ m_recommended_send_interval = server_step;
+
+ /*
+ Increment game time
+ */
+ {
+ m_game_time_fraction_counter += dtime;
+ u32 inc_i = (u32)m_game_time_fraction_counter;
+ m_game_time += inc_i;
+ m_game_time_fraction_counter -= (float)inc_i;
+ }
+
+ /*
+ Handle players
+ */
+ {
+ ScopeProfiler sp(g_profiler, "SEnv: handle players avg", SPT_AVG);
+ for (std::vector<RemotePlayer *>::iterator i = m_players.begin();
+ i != m_players.end(); ++i) {
+ RemotePlayer *player = dynamic_cast<RemotePlayer *>(*i);
+ assert(player);
+
+ // Ignore disconnected players
+ if(player->peer_id == 0)
+ continue;
+
+ // Move
+ player->move(dtime, this, 100*BS);
+ }
+ }
+
+ /*
+ Manage active block list
+ */
+ if (m_active_blocks_management_interval.step(dtime, m_cache_active_block_mgmt_interval)) {
+ ScopeProfiler sp(g_profiler, "SEnv: manage act. block list avg per interval", SPT_AVG);
+ /*
+ Get player block positions
+ */
+ std::vector<v3s16> players_blockpos;
+ for (std::vector<RemotePlayer *>::iterator i = m_players.begin();
+ i != m_players.end(); ++i) {
+ RemotePlayer *player = dynamic_cast<RemotePlayer *>(*i);
+ assert(player);
+
+ // Ignore disconnected players
+ if (player->peer_id == 0)
+ continue;
+
+ PlayerSAO *playersao = player->getPlayerSAO();
+ assert(playersao);
+
+ v3s16 blockpos = getNodeBlockPos(
+ floatToInt(playersao->getBasePosition(), BS));
+ players_blockpos.push_back(blockpos);
+ }
+
+ /*
+ Update list of active blocks, collecting changes
+ */
+ static const s16 active_block_range = g_settings->getS16("active_block_range");
+ std::set<v3s16> blocks_removed;
+ std::set<v3s16> blocks_added;
+ m_active_blocks.update(players_blockpos, active_block_range,
+ blocks_removed, blocks_added);
+
+ /*
+ Handle removed blocks
+ */
+
+ // Convert active objects that are no more in active blocks to static
+ deactivateFarObjects(false);
+
+ for(std::set<v3s16>::iterator
+ i = blocks_removed.begin();
+ i != blocks_removed.end(); ++i) {
+ v3s16 p = *i;
+
+ /* infostream<<"Server: Block " << PP(p)
+ << " became inactive"<<std::endl; */
+
+ MapBlock *block = m_map->getBlockNoCreateNoEx(p);
+ if(block==NULL)
+ continue;
+
+ // Set current time as timestamp (and let it set ChangedFlag)
+ block->setTimestamp(m_game_time);
+ }
+
+ /*
+ Handle added blocks
+ */
+
+ for(std::set<v3s16>::iterator
+ i = blocks_added.begin();
+ i != blocks_added.end(); ++i)
+ {
+ v3s16 p = *i;
+
+ MapBlock *block = m_map->getBlockOrEmerge(p);
+ if(block==NULL){
+ m_active_blocks.m_list.erase(p);
+ continue;
+ }
+
+ activateBlock(block);
+ /* infostream<<"Server: Block " << PP(p)
+ << " became active"<<std::endl; */
+ }
+ }
+
+ /*
+ Mess around in active blocks
+ */
+ if (m_active_blocks_nodemetadata_interval.step(dtime, m_cache_nodetimer_interval)) {
+ ScopeProfiler sp(g_profiler, "SEnv: mess in act. blocks avg per interval", SPT_AVG);
+
+ float dtime = m_cache_nodetimer_interval;
+
+ for(std::set<v3s16>::iterator
+ i = m_active_blocks.m_list.begin();
+ i != m_active_blocks.m_list.end(); ++i)
+ {
+ v3s16 p = *i;
+
+ /*infostream<<"Server: Block ("<<p.X<<","<<p.Y<<","<<p.Z
+ <<") being handled"<<std::endl;*/
+
+ MapBlock *block = m_map->getBlockNoCreateNoEx(p);
+ if(block==NULL)
+ continue;
+
+ // Reset block usage timer
+ block->resetUsageTimer();
+
+ // Set current time as timestamp
+ block->setTimestampNoChangedFlag(m_game_time);
+ // If time has changed much from the one on disk,
+ // set block to be saved when it is unloaded
+ if(block->getTimestamp() > block->getDiskTimestamp() + 60)
+ block->raiseModified(MOD_STATE_WRITE_AT_UNLOAD,
+ MOD_REASON_BLOCK_EXPIRED);
+
+ // Run node timers
+ std::vector<NodeTimer> elapsed_timers =
+ block->m_node_timers.step((float)dtime);
+ if (!elapsed_timers.empty()) {
+ MapNode n;
+ for (std::vector<NodeTimer>::iterator i = elapsed_timers.begin();
+ i != elapsed_timers.end(); ++i) {
+ n = block->getNodeNoEx(i->position);
+ p = i->position + block->getPosRelative();
+ if (m_script->node_on_timer(p, n, i->elapsed)) {
+ block->setNodeTimer(NodeTimer(
+ i->timeout, 0, i->position));
+ }
+ }
+ }
+ }
+ }
+
+ if (m_active_block_modifier_interval.step(dtime, m_cache_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 per interval", SPT_AVG);
+ TimeTaker timer("modify in active blocks per interval");
+
+ // Initialize handling of ActiveBlockModifiers
+ ABMHandler abmhandler(m_abms, m_cache_abm_interval, this, true);
+
+ for(std::set<v3s16>::iterator
+ i = m_active_blocks.m_list.begin();
+ i != m_active_blocks.m_list.end(); ++i)
+ {
+ v3s16 p = *i;
+
+ /*infostream<<"Server: Block ("<<p.X<<","<<p.Y<<","<<p.Z
+ <<") being handled"<<std::endl;*/
+
+ MapBlock *block = m_map->getBlockNoCreateNoEx(p);
+ if(block == NULL)
+ continue;
+
+ // Set current time as timestamp
+ block->setTimestampNoChangedFlag(m_game_time);
+
+ /* Handle ActiveBlockModifiers */
+ abmhandler.apply(block);
+ }
+
+ u32 time_ms = timer.stop(true);
+ u32 max_time_ms = 200;
+ if(time_ms > max_time_ms){
+ warningstream<<"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())
+ */
+ m_script->environment_Step(dtime);
+
+ /*
+ Step active objects
+ */
+ {
+ ScopeProfiler sp(g_profiler, "SEnv: step act. objs avg", SPT_AVG);
+ //TimeTaker timer("Step active objects");
+
+ g_profiler->avg("SEnv: num of objects", m_active_objects.size());
+
+ // This helps the objects to send data at the same time
+ bool send_recommended = false;
+ m_send_recommended_timer += dtime;
+ if(m_send_recommended_timer > getSendRecommendedInterval())
+ {
+ m_send_recommended_timer -= getSendRecommendedInterval();
+ send_recommended = true;
+ }
+
+ for(ActiveObjectMap::iterator i = m_active_objects.begin();
+ i != m_active_objects.end(); ++i) {
+ ServerActiveObject* obj = i->second;
+ // Don't step if is to be removed or stored statically
+ if(obj->m_removed || obj->m_pending_deactivation)
+ continue;
+ // Step object
+ obj->step(dtime, send_recommended);
+ // Read messages from object
+ while(!obj->m_messages_out.empty())
+ {
+ m_active_object_messages.push(
+ obj->m_messages_out.front());
+ obj->m_messages_out.pop();
+ }
+ }
+ }
+
+ /*
+ Manage active objects
+ */
+ if(m_object_management_interval.step(dtime, 0.5))
+ {
+ ScopeProfiler sp(g_profiler, "SEnv: remove removed objs avg /.5s", SPT_AVG);
+ /*
+ Remove objects that satisfy (m_removed && m_known_by_count==0)
+ */
+ removeRemovedObjects();
+ }
+
+ /*
+ Manage particle spawner expiration
+ */
+ if (m_particle_management_interval.step(dtime, 1.0)) {
+ for (UNORDERED_MAP<u32, float>::iterator i = m_particle_spawners.begin();
+ i != m_particle_spawners.end(); ) {
+ //non expiring spawners
+ if (i->second == PARTICLE_SPAWNER_NO_EXPIRY) {
+ ++i;
+ continue;
+ }
+
+ i->second -= 1.0f;
+ if (i->second <= 0.f)
+ m_particle_spawners.erase(i++);
+ else
+ ++i;
+ }
+ }
+}
+
+u32 ServerEnvironment::addParticleSpawner(float exptime)
+{
+ // Timers with lifetime 0 do not expire
+ float time = exptime > 0.f ? exptime : PARTICLE_SPAWNER_NO_EXPIRY;
+
+ u32 id = 0;
+ for (;;) { // look for unused particlespawner id
+ id++;
+ UNORDERED_MAP<u32, float>::iterator f = m_particle_spawners.find(id);
+ if (f == m_particle_spawners.end()) {
+ m_particle_spawners[id] = time;
+ break;
+ }
+ }
+ return id;
+}
+
+u32 ServerEnvironment::addParticleSpawner(float exptime, u16 attached_id)
+{
+ u32 id = addParticleSpawner(exptime);
+ m_particle_spawner_attachments[id] = attached_id;
+ if (ServerActiveObject *obj = getActiveObject(attached_id)) {
+ obj->attachParticleSpawner(id);
+ }
+ return id;
+}
+
+void ServerEnvironment::deleteParticleSpawner(u32 id, bool remove_from_object)
+{
+ m_particle_spawners.erase(id);
+ UNORDERED_MAP<u32, u16>::iterator it = m_particle_spawner_attachments.find(id);
+ if (it != m_particle_spawner_attachments.end()) {
+ u16 obj_id = (*it).second;
+ ServerActiveObject *sao = getActiveObject(obj_id);
+ if (sao != NULL && remove_from_object) {
+ sao->detachParticleSpawner(id);
+ }
+ m_particle_spawner_attachments.erase(id);
+ }
+}
+
+ServerActiveObject* ServerEnvironment::getActiveObject(u16 id)
+{
+ ActiveObjectMap::iterator n = m_active_objects.find(id);
+ return (n != m_active_objects.end() ? n->second : NULL);
+}
+
+bool isFreeServerActiveObjectId(u16 id, ActiveObjectMap &objects)
+{
+ if (id == 0)
+ return false;
+
+ return objects.find(id) == objects.end();
+}
+
+u16 getFreeServerActiveObjectId(ActiveObjectMap &objects)
+{
+ //try to reuse id's as late as possible
+ static u16 last_used_id = 0;
+ u16 startid = last_used_id;
+ for(;;)
+ {
+ last_used_id ++;
+ if(isFreeServerActiveObjectId(last_used_id, objects))
+ return last_used_id;
+
+ if(last_used_id == startid)
+ return 0;
+ }
+}
+
+u16 ServerEnvironment::addActiveObject(ServerActiveObject *object)
+{
+ assert(object); // Pre-condition
+ m_added_objects++;
+ u16 id = addActiveObjectRaw(object, true, 0);
+ return id;
+}
+
+/*
+ Finds out what new objects have been added to
+ inside a radius around a position
+*/
+void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius,
+ s16 player_radius,
+ std::set<u16> ¤t_objects,
+ std::queue<u16> &added_objects)
+{
+ f32 radius_f = radius * BS;
+ f32 player_radius_f = player_radius * BS;
+
+ if (player_radius_f < 0)
+ player_radius_f = 0;
+ /*
+ Go through the object list,
+ - discard m_removed objects,
+ - discard objects that are too far away,
+ - discard objects that are found in current_objects.
+ - add remaining objects to added_objects
+ */
+ for (ActiveObjectMap::iterator i = m_active_objects.begin();
+ i != m_active_objects.end(); ++i) {
+ u16 id = i->first;
+
+ // Get object
+ ServerActiveObject *object = i->second;
+ if (object == NULL)
+ continue;
+
+ // Discard if removed or deactivating
+ if(object->m_removed || object->m_pending_deactivation)
+ continue;
+
+ f32 distance_f = object->getBasePosition().
+ getDistanceFrom(playersao->getBasePosition());
+ if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
+ // Discard if too far
+ if (distance_f > player_radius_f && player_radius_f != 0)
+ continue;
+ } else if (distance_f > radius_f)
+ continue;
+
+ // Discard if already on current_objects
+ std::set<u16>::iterator n;
+ n = current_objects.find(id);
+ if(n != current_objects.end())
+ continue;
+ // Add to added_objects
+ added_objects.push(id);
+ }
+}
+
+/*
+ Finds out what objects have been removed from
+ inside a radius around a position
+*/
+void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius,
+ s16 player_radius,
+ std::set<u16> ¤t_objects,
+ std::queue<u16> &removed_objects)
+{
+ f32 radius_f = radius * BS;
+ f32 player_radius_f = player_radius * BS;
+
+ if (player_radius_f < 0)
+ player_radius_f = 0;
+ /*
+ Go through current_objects; object is removed if:
+ - object is not found in m_active_objects (this is actually an
+ error condition; objects should be set m_removed=true and removed
+ only after all clients have been informed about removal), or
+ - object has m_removed=true, or
+ - object is too far away
+ */
+ for(std::set<u16>::iterator
+ i = current_objects.begin();
+ i != current_objects.end(); ++i)
+ {
+ u16 id = *i;
+ ServerActiveObject *object = getActiveObject(id);
+
+ if (object == NULL) {
+ infostream << "ServerEnvironment::getRemovedActiveObjects():"
+ << " object in current_objects is NULL" << std::endl;
+ removed_objects.push(id);
+ continue;
+ }
+
+ if (object->m_removed || object->m_pending_deactivation) {
+ removed_objects.push(id);
+ continue;
+ }
+
+ f32 distance_f = object->getBasePosition().getDistanceFrom(playersao->getBasePosition());
+ if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
+ if (distance_f <= player_radius_f || player_radius_f == 0)
+ continue;
+ } else if (distance_f <= radius_f)
+ continue;
+
+ // Object is no longer visible
+ removed_objects.push(id);
+ }
+}
+
+void ServerEnvironment::setStaticForActiveObjectsInBlock(
+ v3s16 blockpos, bool static_exists, v3s16 static_block)
+{
+ MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos);
+ if (!block)
+ return;
+
+ for (std::map<u16, StaticObject>::iterator
+ so_it = block->m_static_objects.m_active.begin();
+ so_it != block->m_static_objects.m_active.end(); ++so_it) {
+ // Get the ServerActiveObject counterpart to this StaticObject
+ ActiveObjectMap::iterator ao_it = m_active_objects.find(so_it->first);
+ if (ao_it == m_active_objects.end()) {
+ // If this ever happens, there must be some kind of nasty bug.
+ errorstream << "ServerEnvironment::setStaticForObjectsInBlock(): "
+ "Object from MapBlock::m_static_objects::m_active not found "
+ "in m_active_objects";
+ continue;
+ }
+
+ ServerActiveObject *sao = ao_it->second;
+ sao->m_static_exists = static_exists;
+ sao->m_static_block = static_block;
+ }
+}
+
+ActiveObjectMessage ServerEnvironment::getActiveObjectMessage()
+{
+ if(m_active_object_messages.empty())
+ return ActiveObjectMessage(0);
+
+ ActiveObjectMessage message = m_active_object_messages.front();
+ m_active_object_messages.pop();
+ return message;
+}
+
+/*
+ ************ Private methods *************
+*/
+
+u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object,
+ bool set_changed, u32 dtime_s)
+{
+ assert(object); // Pre-condition
+ if(object->getId() == 0){
+ u16 new_id = getFreeServerActiveObjectId(m_active_objects);
+ if(new_id == 0)
+ {
+ errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
+ <<"no free ids available"<<std::endl;
+ if(object->environmentDeletes())
+ delete object;
+ return 0;
+ }
+ object->setId(new_id);
+ }
+ else{
+ verbosestream<<"ServerEnvironment::addActiveObjectRaw(): "
+ <<"supplied with id "<<object->getId()<<std::endl;
+ }
+
+ if(!isFreeServerActiveObjectId(object->getId(), m_active_objects)) {
+ errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
+ <<"id is not free ("<<object->getId()<<")"<<std::endl;
+ if(object->environmentDeletes())
+ delete object;
+ return 0;
+ }
+
+ if (objectpos_over_limit(object->getBasePosition())) {
+ v3f p = object->getBasePosition();
+ errorstream << "ServerEnvironment::addActiveObjectRaw(): "
+ << "object position (" << p.X << "," << p.Y << "," << p.Z
+ << ") outside maximum range" << std::endl;
+ if (object->environmentDeletes())
+ delete object;
+ return 0;
+ }
+
+ /*infostream<<"ServerEnvironment::addActiveObjectRaw(): "
+ <<"added (id="<<object->getId()<<")"<<std::endl;*/
+
+ m_active_objects[object->getId()] = object;
+
+ verbosestream<<"ServerEnvironment::addActiveObjectRaw(): "
+ <<"Added id="<<object->getId()<<"; there are now "
+ <<m_active_objects.size()<<" active objects."
+ <<std::endl;
+
+ // Register reference in scripting api (must be done before post-init)
+ m_script->addObjectReference(object);
+ // Post-initialize object
+ object->addedToEnvironment(dtime_s);
+
+ // Add static data to block
+ if(object->isStaticAllowed())
+ {
+ // Add static object to active static list of the block
+ v3f objectpos = object->getBasePosition();
+ std::string staticdata = object->getStaticData();
+ StaticObject s_obj(object->getType(), objectpos, staticdata);
+ // Add to the block where the object is located in
+ v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
+ MapBlock *block = m_map->emergeBlock(blockpos);
+ if(block){
+ block->m_static_objects.m_active[object->getId()] = s_obj;
+ object->m_static_exists = true;
+ object->m_static_block = blockpos;
+
+ if(set_changed)
+ block->raiseModified(MOD_STATE_WRITE_NEEDED,
+ MOD_REASON_ADD_ACTIVE_OBJECT_RAW);
+ } else {
+ v3s16 p = floatToInt(objectpos, BS);
+ errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
+ <<"could not emerge block for storing id="<<object->getId()
+ <<" statically (pos="<<PP(p)<<")"<<std::endl;
+ }
+ }
+
+ return object->getId();
+}
+
+/*
+ Remove objects that satisfy (m_removed && m_known_by_count==0)
+*/
+void ServerEnvironment::removeRemovedObjects()
+{
+ std::vector<u16> objects_to_remove;
+ for(ActiveObjectMap::iterator i = m_active_objects.begin();
+ i != m_active_objects.end(); ++i) {
+ u16 id = i->first;
+ ServerActiveObject* obj = i->second;
+ // This shouldn't happen but check it
+ if(obj == NULL)
+ {
+ infostream<<"NULL object found in ServerEnvironment"
+ <<" while finding removed objects. id="<<id<<std::endl;
+ // Id to be removed from m_active_objects
+ objects_to_remove.push_back(id);
+ continue;
+ }
+
+ /*
+ We will delete objects that are marked as removed or thatare
+ waiting for deletion after deactivation
+ */
+ if (!obj->m_removed && !obj->m_pending_deactivation)
+ continue;
+
+ /*
+ Delete static data from block if is marked as removed
+ */
+ if(obj->m_static_exists && obj->m_removed)
+ {
+ MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
+ if (block) {
+ block->m_static_objects.remove(id);
+ block->raiseModified(MOD_STATE_WRITE_NEEDED,
+ MOD_REASON_REMOVE_OBJECTS_REMOVE);
+ 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;
+ }
+ }
+
+ // If m_known_by_count > 0, don't actually remove. On some future
+ // invocation this will be 0, which is when removal will continue.
+ if(obj->m_known_by_count > 0)
+ continue;
+
+ /*
+ Move static data from active to stored if not marked as removed
+ */
+ if(obj->m_static_exists && !obj->m_removed){
+ MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
+ if (block) {
+ std::map<u16, StaticObject>::iterator i =
+ block->m_static_objects.m_active.find(id);
+ if(i != block->m_static_objects.m_active.end()){
+ block->m_static_objects.m_stored.push_back(i->second);
+ block->m_static_objects.m_active.erase(id);
+ block->raiseModified(MOD_STATE_WRITE_NEEDED,
+ MOD_REASON_REMOVE_OBJECTS_DEACTIVATE);
+ }
+ } else {
+ infostream<<"Failed to emerge block from which an object to "
+ <<"be deactivated was loaded from. id="<<id<<std::endl;
+ }
+ }
+
+ // Tell the object about removal
+ obj->removingFromEnvironment();
+ // Deregister in scripting api
+ m_script->removeObjectReference(obj);
+
+ // Delete
+ if(obj->environmentDeletes())
+ delete obj;
+
+ // Id to be removed from m_active_objects
+ objects_to_remove.push_back(id);
+ }
+ // Remove references from m_active_objects
+ for(std::vector<u16>::iterator i = objects_to_remove.begin();
+ i != objects_to_remove.end(); ++i) {
+ m_active_objects.erase(*i);
+ }
+}
+
+static void print_hexdump(std::ostream &o, const std::string &data)
+{
+ const int linelength = 16;
+ for(int l=0; ; l++){
+ int i0 = linelength * l;
+ bool at_end = false;
+ int thislinelength = linelength;
+ if(i0 + thislinelength > (int)data.size()){
+ thislinelength = data.size() - i0;
+ at_end = true;
+ }
+ for(int di=0; di<linelength; di++){
+ int i = i0 + di;
+ char buf[4];
+ if(di<thislinelength)
+ snprintf(buf, 4, "%.2x ", data[i]);
+ else
+ snprintf(buf, 4, " ");
+ o<<buf;
+ }
+ o<<" ";
+ for(int di=0; di<thislinelength; di++){
+ int i = i0 + di;
+ if(data[i] >= 32)
+ o<<data[i];
+ else
+ o<<".";
+ }
+ o<<std::endl;
+ if(at_end)
+ break;
+ }
+}
+
+/*
+ Convert stored objects from blocks near the players to active.
+*/
+void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s)
+{
+ if(block == NULL)
+ return;
+
+ // Ignore if no stored objects (to not set changed flag)
+ if(block->m_static_objects.m_stored.empty())
+ return;
+
+ verbosestream<<"ServerEnvironment::activateObjects(): "
+ <<"activating objects of block "<<PP(block->getPos())
+ <<" ("<<block->m_static_objects.m_stored.size()
+ <<" objects)"<<std::endl;
+ bool large_amount = (block->m_static_objects.m_stored.size() > g_settings->getU16("max_objects_per_block"));
+ if (large_amount) {
+ errorstream<<"suspiciously large amount of objects detected: "
+ <<block->m_static_objects.m_stored.size()<<" in "
+ <<PP(block->getPos())
+ <<"; removing all of them."<<std::endl;
+ // Clear stored list
+ block->m_static_objects.m_stored.clear();
+ block->raiseModified(MOD_STATE_WRITE_NEEDED,
+ MOD_REASON_TOO_MANY_OBJECTS);
+ return;
+ }
+
+ // Activate stored objects
+ std::vector<StaticObject> new_stored;
+ for (std::vector<StaticObject>::iterator
+ i = block->m_static_objects.m_stored.begin();
+ i != block->m_static_objects.m_stored.end(); ++i) {
+ StaticObject &s_obj = *i;
+
+ // Create an active object from the data
+ ServerActiveObject *obj = ServerActiveObject::create
+ ((ActiveObjectType) s_obj.type, this, 0, s_obj.pos, s_obj.data);
+ // If couldn't create object, store static data back.
+ if(obj == NULL) {
+ errorstream<<"ServerEnvironment::activateObjects(): "
+ <<"failed to create active object from static object "
+ <<"in block "<<PP(s_obj.pos/BS)
+ <<" type="<<(int)s_obj.type<<" data:"<<std::endl;
+ print_hexdump(verbosestream, s_obj.data);
+
+ new_stored.push_back(s_obj);
+ continue;
+ }
+ verbosestream<<"ServerEnvironment::activateObjects(): "
+ <<"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, dtime_s);
+ }
+ // Clear stored list
+ block->m_static_objects.m_stored.clear();
+ // Add leftover failed stuff to stored list
+ for(std::vector<StaticObject>::iterator
+ i = new_stored.begin();
+ i != new_stored.end(); ++i) {
+ StaticObject &s_obj = *i;
+ block->m_static_objects.m_stored.push_back(s_obj);
+ }
+
+ // Turn the active counterparts of activated objects not pending for
+ // deactivation
+ for(std::map<u16, StaticObject>::iterator
+ i = block->m_static_objects.m_active.begin();
+ i != block->m_static_objects.m_active.end(); ++i)
+ {
+ u16 id = i->first;
+ ServerActiveObject *object = getActiveObject(id);
+ assert(object);
+ object->m_pending_deactivation = false;
+ }
+
+ /*
+ Note: Block hasn't really been modified here.
+ The objects have just been activated and moved from the stored
+ static list to the active static list.
+ As such, the block is essentially the same.
+ Thus, do not call block->raiseModified(MOD_STATE_WRITE_NEEDED).
+ Otherwise there would be a huge amount of unnecessary I/O.
+ */
+}
+
+/*
+ Convert objects that are not standing inside active blocks to static.
+
+ If m_known_by_count != 0, active object is not deleted, but static
+ data is still updated.
+
+ 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)
+{
+ std::vector<u16> objects_to_remove;
+ for(ActiveObjectMap::iterator i = m_active_objects.begin();
+ i != m_active_objects.end(); ++i) {
+ ServerActiveObject* obj = i->second;
+ assert(obj);
+
+ // Do not deactivate if static data creation not allowed
+ if(!force_delete && !obj->isStaticAllowed())
+ continue;
+
+ // If pending deactivation, let removeRemovedObjects() do it
+ if(!force_delete && obj->m_pending_deactivation)
+ continue;
+
+ u16 id = i->first;
+ v3f objectpos = obj->getBasePosition();
+
+ // The block in which the object resides in
+ v3s16 blockpos_o = getNodeBlockPos(floatToInt(objectpos, BS));
+
+ // If object's static data is stored in a deactivated block and object
+ // is actually located in an active block, re-save to the block in
+ // which the object is actually located in.
+ if(!force_delete &&
+ obj->m_static_exists &&
+ !m_active_blocks.contains(obj->m_static_block) &&
+ m_active_blocks.contains(blockpos_o))
+ {
+ v3s16 old_static_block = obj->m_static_block;
+
+ // Save to block where object is located
+ MapBlock *block = m_map->emergeBlock(blockpos_o, false);
+ if(!block){
+ errorstream<<"ServerEnvironment::deactivateFarObjects(): "
+ <<"Could not save object id="<<id
+ <<" to it's current block "<<PP(blockpos_o)
+ <<std::endl;
+ continue;
+ }
+ std::string staticdata_new = obj->getStaticData();
+ StaticObject s_obj(obj->getType(), objectpos, staticdata_new);
+ block->m_static_objects.insert(id, s_obj);
+ obj->m_static_block = blockpos_o;
+ block->raiseModified(MOD_STATE_WRITE_NEEDED,
+ MOD_REASON_STATIC_DATA_ADDED);
+
+ // Delete from block where object was located
+ block = m_map->emergeBlock(old_static_block, false);
+ if(!block){
+ errorstream<<"ServerEnvironment::deactivateFarObjects(): "
+ <<"Could not delete object id="<<id
+ <<" from it's previous block "<<PP(old_static_block)
+ <<std::endl;
+ continue;
+ }
+ block->m_static_objects.remove(id);
+ block->raiseModified(MOD_STATE_WRITE_NEEDED,
+ MOD_REASON_STATIC_DATA_REMOVED);
+ continue;
+ }
+
+ // If block is active, don't remove
+ if(!force_delete && m_active_blocks.contains(blockpos_o))
+ continue;
+
+ verbosestream<<"ServerEnvironment::deactivateFarObjects(): "
+ <<"deactivating object id="<<id<<" on inactive block "
+ <<PP(blockpos_o)<<std::endl;
+
+ // If known by some client, don't immediately delete.
+ bool pending_delete = (obj->m_known_by_count > 0 && !force_delete);
+
+ /*
+ Update the static data
+ */
+
+ if(obj->isStaticAllowed())
+ {
+ // Create new static object
+ std::string staticdata_new = obj->getStaticData();
+ StaticObject s_obj(obj->getType(), objectpos, staticdata_new);
+
+ bool stays_in_same_block = false;
+ bool data_changed = true;
+
+ if (obj->m_static_exists) {
+ if (obj->m_static_block == blockpos_o)
+ stays_in_same_block = true;
+
+ MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
+
+ if (block) {
+ std::map<u16, StaticObject>::iterator n =
+ block->m_static_objects.m_active.find(id);
+ if (n != block->m_static_objects.m_active.end()) {
+ StaticObject static_old = n->second;
+
+ float save_movem = obj->getMinimumSavedMovement();
+
+ if (static_old.data == staticdata_new &&
+ (static_old.pos - objectpos).getLength() < save_movem)
+ data_changed = false;
+ } else {
+ errorstream<<"ServerEnvironment::deactivateFarObjects(): "
+ <<"id="<<id<<" m_static_exists=true but "
+ <<"static data doesn't actually exist in "
+ <<PP(obj->m_static_block)<<std::endl;
+ }
+ }
+ }
+
+ bool shall_be_written = (!stays_in_same_block || data_changed);
+
+ // Delete old static object
+ if(obj->m_static_exists)
+ {
+ MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
+ if(block)
+ {
+ block->m_static_objects.remove(id);
+ obj->m_static_exists = false;
+ // Only mark block as modified if data changed considerably
+ if(shall_be_written)
+ block->raiseModified(MOD_STATE_WRITE_NEEDED,
+ MOD_REASON_STATIC_DATA_CHANGED);
+ }
+ }
+
+ // Add to the block where the object is located in
+ v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
+ // Get or generate the block
+ MapBlock *block = NULL;
+ try{
+ block = m_map->emergeBlock(blockpos);
+ } catch(InvalidPositionException &e){
+ // Handled via NULL pointer
+ // NOTE: emergeBlock's failure is usually determined by it
+ // actually returning NULL
+ }
+
+ if(block)
+ {
+ if (block->m_static_objects.m_stored.size() >= g_settings->getU16("max_objects_per_block")) {
+ warningstream << "ServerEnv: Trying to store id = " << obj->getId()
+ << " statically but block " << PP(blockpos)
+ << " already contains "
+ << block->m_static_objects.m_stored.size()
+ << " objects."
+ << " Forcing delete." << std::endl;
+ force_delete = true;
+ } else {
+ // If static counterpart already exists in target block,
+ // remove it first.
+ // This shouldn't happen because the object is removed from
+ // the previous block before this according to
+ // obj->m_static_block, but happens rarely for some unknown
+ // reason. Unsuccessful attempts have been made to find
+ // said reason.
+ if(id && block->m_static_objects.m_active.find(id) != block->m_static_objects.m_active.end()){
+ warningstream<<"ServerEnv: Performing hack #83274"
+ <<std::endl;
+ block->m_static_objects.remove(id);
+ }
+ // Store static data
+ u16 store_id = pending_delete ? id : 0;
+ block->m_static_objects.insert(store_id, s_obj);
+
+ // Only mark block as modified if data changed considerably
+ if(shall_be_written)
+ block->raiseModified(MOD_STATE_WRITE_NEEDED,
+ MOD_REASON_STATIC_DATA_CHANGED);
+
+ obj->m_static_exists = true;
+ obj->m_static_block = block->getPos();
+ }
+ }
+ else{
+ if(!force_delete){
+ v3s16 p = floatToInt(objectpos, BS);
+ errorstream<<"ServerEnv: Could not find or generate "
+ <<"a block for storing id="<<obj->getId()
+ <<" statically (pos="<<PP(p)<<")"<<std::endl;
+ continue;
+ }
+ }
+ }
+
+ /*
+ If known by some client, set pending deactivation.
+ Otherwise delete it immediately.
+ */
+
+ if(pending_delete && !force_delete)
+ {
+ verbosestream<<"ServerEnvironment::deactivateFarObjects(): "
+ <<"object id="<<id<<" is known by clients"
+ <<"; not deleting yet"<<std::endl;
+
+ obj->m_pending_deactivation = true;
+ continue;
+ }
+
+ verbosestream<<"ServerEnvironment::deactivateFarObjects(): "
+ <<"object id="<<id<<" is not known by clients"
+ <<"; deleting"<<std::endl;
+
+ // Tell the object about removal
+ obj->removingFromEnvironment();
+ // Deregister in scripting api
+ m_script->removeObjectReference(obj);
+
+ // Delete active object
+ if(obj->environmentDeletes())
+ delete obj;
+ // Id to be removed from m_active_objects
+ objects_to_remove.push_back(id);
+ }
+
+ // Remove references from m_active_objects
+ for(std::vector<u16>::iterator i = objects_to_remove.begin();
+ i != objects_to_remove.end(); ++i) {
+ m_active_objects.erase(*i);
+ }
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2017 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 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 Lesser General Public License for more details.
+
+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.
+*/
+
+#ifndef SERVER_ENVIRONMENT_HEADER
+#define SERVER_ENVIRONMENT_HEADER
+
+#include "environment.h"
+
+class IGameDef;
+class ServerMap;
+class RemotePlayer;
+class PlayerSAO;
+class ServerEnvironment;
+class ActiveBlockModifier;
+class ServerActiveObject;
+
+/*
+ {Active, Loading} block modifier interface.
+
+ These are fed into ServerEnvironment at initialization time;
+ ServerEnvironment handles deleting them.
+*/
+
+class ActiveBlockModifier
+{
+public:
+ ActiveBlockModifier(){};
+ virtual ~ActiveBlockModifier(){};
+
+ // Set of contents to trigger on
+ virtual std::set<std::string> getTriggerContents()=0;
+ // Set of required neighbors (trigger doesn't happen if none are found)
+ // Empty = do not check neighbors
+ virtual std::set<std::string> getRequiredNeighbors()
+ { return std::set<std::string>(); }
+ // Trigger interval in seconds
+ virtual float getTriggerInterval() = 0;
+ // Random chance of (1 / return value), 0 is disallowed
+ virtual u32 getTriggerChance() = 0;
+ // Whether to modify chance to simulate time lost by an unnattended block
+ virtual bool getSimpleCatchUp() = 0;
+ // This is called usually at interval for 1/chance of the nodes
+ virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n){};
+ virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n,
+ u32 active_object_count, u32 active_object_count_wider){};
+};
+
+struct ABMWithState
+{
+ ActiveBlockModifier *abm;
+ float timer;
+
+ ABMWithState(ActiveBlockModifier *abm_);
+};
+
+struct LoadingBlockModifierDef
+{
+ // Set of contents to trigger on
+ std::set<std::string> trigger_contents;
+ std::string name;
+ bool run_at_every_load;
+
+ virtual ~LoadingBlockModifierDef() {}
+ virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n){};
+};
+
+struct LBMContentMapping
+{
+ typedef std::map<content_t, std::vector<LoadingBlockModifierDef *> > container_map;
+ container_map map;
+
+ std::vector<LoadingBlockModifierDef *> lbm_list;
+
+ // Needs to be separate method (not inside destructor),
+ // because the LBMContentMapping may be copied and destructed
+ // many times during operation in the lbm_lookup_map.
+ void deleteContents();
+ void addLBM(LoadingBlockModifierDef *lbm_def, IGameDef *gamedef);
+ const std::vector<LoadingBlockModifierDef *> *lookup(content_t c) const;
+};
+
+class LBMManager
+{
+public:
+ LBMManager():
+ m_query_mode(false)
+ {}
+
+ ~LBMManager();
+
+ // Don't call this after loadIntroductionTimes() ran.
+ void addLBMDef(LoadingBlockModifierDef *lbm_def);
+
+ void loadIntroductionTimes(const std::string ×,
+ IGameDef *gamedef, u32 now);
+
+ // Don't call this before loadIntroductionTimes() ran.
+ std::string createIntroductionTimesString();
+
+ // Don't call this before loadIntroductionTimes() ran.
+ void applyLBMs(ServerEnvironment *env, MapBlock *block, u32 stamp);
+
+ // Warning: do not make this std::unordered_map, order is relevant here
+ typedef std::map<u32, LBMContentMapping> lbm_lookup_map;
+
+private:
+ // Once we set this to true, we can only query,
+ // not modify
+ bool m_query_mode;
+
+ // For m_query_mode == false:
+ // The key of the map is the LBM def's name.
+ // TODO make this std::unordered_map
+ std::map<std::string, LoadingBlockModifierDef *> m_lbm_defs;
+
+ // For m_query_mode == true:
+ // The key of the map is the LBM def's first introduction time.
+ lbm_lookup_map m_lbm_lookup;
+
+ // Returns an iterator to the LBMs that were introduced
+ // after the given time. This is guaranteed to return
+ // valid values for everything
+ lbm_lookup_map::const_iterator getLBMsIntroducedAfter(u32 time)
+ { return m_lbm_lookup.lower_bound(time); }
+};
+
+/*
+ List of active blocks, used by ServerEnvironment
+*/
+
+class ActiveBlockList
+{
+public:
+ void update(std::vector<v3s16> &active_positions,
+ s16 radius,
+ std::set<v3s16> &blocks_removed,
+ std::set<v3s16> &blocks_added);
+
+ bool contains(v3s16 p){
+ return (m_list.find(p) != m_list.end());
+ }
+
+ void clear(){
+ m_list.clear();
+ }
+
+ std::set<v3s16> m_list;
+ std::set<v3s16> m_forceloaded_list;
+
+private:
+};
+
+/*
+ Operation mode for ServerEnvironment::clearObjects()
+*/
+enum ClearObjectsMode {
+ // Load and go through every mapblock, clearing objects
+ CLEAR_OBJECTS_MODE_FULL,
+
+ // Clear objects immediately in loaded mapblocks;
+ // clear objects in unloaded mapblocks only when the mapblocks are next activated.
+ CLEAR_OBJECTS_MODE_QUICK,
+};
+
+/*
+ The server-side environment.
+
+ This is not thread-safe. Server uses an environment mutex.
+*/
+
+typedef UNORDERED_MAP<u16, ServerActiveObject *> ActiveObjectMap;
+
+class ServerEnvironment : public Environment
+{
+public:
+ ServerEnvironment(ServerMap *map, GameScripting *scriptIface,
+ IGameDef *gamedef, const std::string &path_world);
+ ~ServerEnvironment();
+
+ Map & getMap();
+
+ ServerMap & getServerMap();
+
+ //TODO find way to remove this fct!
+ GameScripting* getScriptIface()
+ { return m_script; }
+
+ IGameDef *getGameDef()
+ { return m_gamedef; }
+
+ float getSendRecommendedInterval()
+ { return m_recommended_send_interval; }
+
+ void kickAllPlayers(AccessDeniedCode reason,
+ const std::string &str_reason, bool reconnect);
+ // Save players
+ void saveLoadedPlayers();
+ void savePlayer(RemotePlayer *player);
+ RemotePlayer *loadPlayer(const std::string &playername, PlayerSAO *sao);
+ void addPlayer(RemotePlayer *player);
+ void removePlayer(RemotePlayer *player);
+
+ /*
+ Save and load time of day and game timer
+ */
+ void saveMeta();
+ void loadMeta();
+ // to be called instead of loadMeta if
+ // env_meta.txt doesn't exist (e.g. new world)
+ void loadDefaultMeta();
+
+ u32 addParticleSpawner(float exptime);
+ u32 addParticleSpawner(float exptime, u16 attached_id);
+ void deleteParticleSpawner(u32 id, bool remove_from_object = true);
+
+ /*
+ External ActiveObject interface
+ -------------------------------------------
+ */
+
+ ServerActiveObject* getActiveObject(u16 id);
+
+ /*
+ Add an active object to the environment.
+ Environment handles deletion of object.
+ Object may be deleted by environment immediately.
+ If id of object is 0, assigns a free id to it.
+ Returns the id of the object.
+ Returns 0 if not added and thus deleted.
+ */
+ u16 addActiveObject(ServerActiveObject *object);
+
+ /*
+ Add an active object as a static object to the corresponding
+ MapBlock.
+ Caller allocates memory, ServerEnvironment frees memory.
+ Return value: true if succeeded, false if failed.
+ (note: not used, pending removal from engine)
+ */
+ //bool addActiveObjectAsStatic(ServerActiveObject *object);
+
+ /*
+ Find out what new objects have been added to
+ inside a radius around a position
+ */
+ void getAddedActiveObjects(PlayerSAO *playersao, s16 radius,
+ s16 player_radius,
+ std::set<u16> ¤t_objects,
+ std::queue<u16> &added_objects);
+
+ /*
+ Find out what new objects have been removed from
+ inside a radius around a position
+ */
+ void getRemovedActiveObjects(PlayerSAO *playersao, s16 radius,
+ s16 player_radius,
+ std::set<u16> ¤t_objects,
+ std::queue<u16> &removed_objects);
+
+ /*
+ Get the next message emitted by some active object.
+ Returns a message with id=0 if no messages are available.
+ */
+ ActiveObjectMessage getActiveObjectMessage();
+
+ /*
+ Activate objects and dynamically modify for the dtime determined
+ from timestamp and additional_dtime
+ */
+ void activateBlock(MapBlock *block, u32 additional_dtime=0);
+
+ /*
+ {Active,Loading}BlockModifiers
+ -------------------------------------------
+ */
+
+ void addActiveBlockModifier(ActiveBlockModifier *abm);
+ void addLoadingBlockModifierDef(LoadingBlockModifierDef *lbm);
+
+ /*
+ Other stuff
+ -------------------------------------------
+ */
+
+ // Script-aware node setters
+ bool setNode(v3s16 p, const MapNode &n);
+ bool removeNode(v3s16 p);
+ bool swapNode(v3s16 p, const MapNode &n);
+
+ // Find all active objects inside a radius around a point
+ void getObjectsInsideRadius(std::vector<u16> &objects, v3f pos, float radius);
+
+ // Clear objects, loading and going through every MapBlock
+ void clearObjects(ClearObjectsMode mode);
+
+ // This makes stuff happen
+ void step(f32 dtime);
+
+ //check if there's a line of sight between two positions
+ bool line_of_sight(v3f pos1, v3f pos2, float stepsize=1.0, v3s16 *p=NULL);
+
+ u32 getGameTime() { return m_game_time; }
+
+ void reportMaxLagEstimate(float f) { m_max_lag_estimate = f; }
+ float getMaxLagEstimate() { return m_max_lag_estimate; }
+
+ std::set<v3s16>* getForceloadedBlocks() { return &m_active_blocks.m_forceloaded_list; };
+
+ // Sets the static object status all the active objects in the specified block
+ // This is only really needed for deleting blocks from the map
+ void setStaticForActiveObjectsInBlock(v3s16 blockpos,
+ bool static_exists, v3s16 static_block=v3s16(0,0,0));
+
+ RemotePlayer *getPlayer(const u16 peer_id);
+ RemotePlayer *getPlayer(const char* name);
+private:
+
+ /*
+ Internal ActiveObject interface
+ -------------------------------------------
+ */
+
+ /*
+ Add an active object to the environment.
+
+ Called by addActiveObject.
+
+ Object may be deleted by environment immediately.
+ If id of object is 0, assigns a free id to it.
+ Returns the id of the object.
+ Returns 0 if not added and thus deleted.
+ */
+ u16 addActiveObjectRaw(ServerActiveObject *object, bool set_changed, u32 dtime_s);
+
+ /*
+ Remove all objects that satisfy (m_removed && m_known_by_count==0)
+ */
+ void removeRemovedObjects();
+
+ /*
+ Convert stored objects from block to active
+ */
+ void activateObjects(MapBlock *block, u32 dtime_s);
+
+ /*
+ Convert objects that are not in active blocks to static.
+
+ If m_known_by_count != 0, active object is not deleted, but static
+ data is still updated.
+
+ If force_delete is set, active object is deleted nevertheless. It
+ shall only be set so in the destructor of the environment.
+ */
+ void deactivateFarObjects(bool force_delete);
+
+ /*
+ Member variables
+ */
+
+ // The map
+ ServerMap *m_map;
+ // Lua state
+ GameScripting* m_script;
+ // Game definition
+ IGameDef *m_gamedef;
+ // World path
+ const std::string m_path_world;
+ // Active object list
+ ActiveObjectMap m_active_objects;
+ // Outgoing network message buffer for active objects
+ std::queue<ActiveObjectMessage> m_active_object_messages;
+ // Some timers
+ float m_send_recommended_timer;
+ IntervalLimiter m_object_management_interval;
+ // List of active blocks
+ ActiveBlockList m_active_blocks;
+ IntervalLimiter m_active_blocks_management_interval;
+ IntervalLimiter m_active_block_modifier_interval;
+ IntervalLimiter m_active_blocks_nodemetadata_interval;
+ int m_active_block_interval_overload_skip;
+ // Time from the beginning of the game in seconds.
+ // Incremented in step().
+ u32 m_game_time;
+ // A helper variable for incrementing the latter
+ float m_game_time_fraction_counter;
+ // Time of last clearObjects call (game time).
+ // When a mapblock older than this is loaded, its objects are cleared.
+ u32 m_last_clear_objects_time;
+ // Active block modifiers
+ std::vector<ABMWithState> m_abms;
+ LBMManager m_lbm_mgr;
+ // An interval for generally sending object positions and stuff
+ float m_recommended_send_interval;
+ // Estimate for general maximum lag as determined by server.
+ // Can raise to high values like 15s with eg. map generation mods.
+ float m_max_lag_estimate;
+
+ // peer_ids in here should be unique, except that there may be many 0s
+ std::vector<RemotePlayer*> m_players;
+
+ // Particles
+ IntervalLimiter m_particle_management_interval;
+ UNORDERED_MAP<u32, float> m_particle_spawners;
+ UNORDERED_MAP<u32, u16> m_particle_spawner_attachments;
+};
+
+#endif
#include <stack>
#include "util/pointer.h"
#include "util/numeric.h"
-#include "util/mathconstants.h"
#include "map.h"
-#include "environment.h"
+#include "serverenvironment.h"
#include "nodedef.h"
#include "treegen.h"