3 Copyright (C) 2010-2017 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "serverenvironment.h"
26 #include "nodemetadata.h"
32 #include "remoteplayer.h"
33 #include "scripting_server.h"
35 #include "util/serialize.h"
36 #include "util/basic_macros.h"
37 #include "util/pointedthing.h"
38 #include "threading/mutex_auto_lock.h"
40 #include "gameparams.h"
41 #include "database/database-dummy.h"
42 #include "database/database-files.h"
43 #include "database/database-sqlite3.h"
45 #include "database/database-postgresql.h"
47 #include "server/luaentity_sao.h"
48 #include "server/player_sao.h"
50 #define LBM_NAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_:"
52 // A number that is much smaller than the timeout for particle spawners should/could ever be
53 #define PARTICLE_SPAWNER_NO_EXPIRY -1024.f
59 ABMWithState::ABMWithState(ActiveBlockModifier *abm_):
62 // Initialize timer to random value to spread processing
63 float itv = abm->getTriggerInterval();
64 itv = MYMAX(0.001, itv); // No less than 1ms
65 int minval = MYMAX(-0.51*itv, -60); // Clamp to
66 int maxval = MYMIN(0.51*itv, 60); // +-60 seconds
67 timer = myrand_range(minval, maxval);
74 void LBMContentMapping::deleteContents()
76 for (auto &it : lbm_list) {
81 void LBMContentMapping::addLBM(LoadingBlockModifierDef *lbm_def, IGameDef *gamedef)
83 // Add the lbm_def to the LBMContentMapping.
84 // Unknown names get added to the global NameIdMapping.
85 const NodeDefManager *nodedef = gamedef->ndef();
87 lbm_list.push_back(lbm_def);
89 for (const std::string &nodeTrigger: lbm_def->trigger_contents) {
90 std::vector<content_t> c_ids;
91 bool found = nodedef->getIds(nodeTrigger, c_ids);
93 content_t c_id = gamedef->allocateUnknownNodeId(nodeTrigger);
94 if (c_id == CONTENT_IGNORE) {
95 // Seems it can't be allocated.
96 warningstream << "Could not internalize node name \"" << nodeTrigger
97 << "\" while loading LBM \"" << lbm_def->name << "\"." << std::endl;
100 c_ids.push_back(c_id);
103 for (content_t c_id : c_ids) {
104 map[c_id].push_back(lbm_def);
109 const std::vector<LoadingBlockModifierDef *> *
110 LBMContentMapping::lookup(content_t c) const
112 lbm_map::const_iterator it = map.find(c);
115 // This first dereferences the iterator, returning
116 // a std::vector<LoadingBlockModifierDef *>
117 // reference, then we convert it to a pointer.
118 return &(it->second);
121 LBMManager::~LBMManager()
123 for (auto &m_lbm_def : m_lbm_defs) {
124 delete m_lbm_def.second;
127 for (auto &it : m_lbm_lookup) {
128 (it.second).deleteContents();
132 void LBMManager::addLBMDef(LoadingBlockModifierDef *lbm_def)
134 // Precondition, in query mode the map isn't used anymore
135 FATAL_ERROR_IF(m_query_mode,
136 "attempted to modify LBMManager in query mode");
138 if (!string_allowed(lbm_def->name, LBM_NAME_ALLOWED_CHARS)) {
139 throw ModError("Error adding LBM \"" + lbm_def->name +
140 "\": Does not follow naming conventions: "
141 "Only characters [a-z0-9_:] are allowed.");
144 m_lbm_defs[lbm_def->name] = lbm_def;
147 void LBMManager::loadIntroductionTimes(const std::string ×,
148 IGameDef *gamedef, u32 now)
153 // Storing it in a map first instead of
154 // handling the stuff directly in the loop
155 // removes all duplicate entries.
156 // TODO make this std::unordered_map
157 std::map<std::string, u32> introduction_times;
160 The introduction times string consists of name~time entries,
161 with each entry terminated by a semicolon. The time is decimal.
166 while ((idx_new = times.find(';', idx)) != std::string::npos) {
167 std::string entry = times.substr(idx, idx_new - idx);
168 std::vector<std::string> components = str_split(entry, '~');
169 if (components.size() != 2)
170 throw SerializationError("Introduction times entry \""
171 + entry + "\" requires exactly one '~'!");
172 const std::string &name = components[0];
173 u32 time = from_string<u32>(components[1]);
174 introduction_times[name] = time;
178 // Put stuff from introduction_times into m_lbm_lookup
179 for (std::map<std::string, u32>::const_iterator it = introduction_times.begin();
180 it != introduction_times.end(); ++it) {
181 const std::string &name = it->first;
182 u32 time = it->second;
184 std::map<std::string, LoadingBlockModifierDef *>::iterator def_it =
185 m_lbm_defs.find(name);
186 if (def_it == m_lbm_defs.end()) {
187 // This seems to be an LBM entry for
188 // an LBM we haven't loaded. Discard it.
191 LoadingBlockModifierDef *lbm_def = def_it->second;
192 if (lbm_def->run_at_every_load) {
193 // This seems to be an LBM entry for
194 // an LBM that runs at every load.
195 // Don't add it just yet.
199 m_lbm_lookup[time].addLBM(lbm_def, gamedef);
201 // Erase the entry so that we know later
202 // what elements didn't get put into m_lbm_lookup
203 m_lbm_defs.erase(name);
206 // Now also add the elements from m_lbm_defs to m_lbm_lookup
207 // that weren't added in the previous step.
208 // They are introduced first time to this world,
209 // or are run at every load (introducement time hardcoded to U32_MAX).
211 LBMContentMapping &lbms_we_introduce_now = m_lbm_lookup[now];
212 LBMContentMapping &lbms_running_always = m_lbm_lookup[U32_MAX];
214 for (auto &m_lbm_def : m_lbm_defs) {
215 if (m_lbm_def.second->run_at_every_load) {
216 lbms_running_always.addLBM(m_lbm_def.second, gamedef);
218 lbms_we_introduce_now.addLBM(m_lbm_def.second, gamedef);
222 // Clear the list, so that we don't delete remaining elements
223 // twice in the destructor
227 std::string LBMManager::createIntroductionTimesString()
229 // Precondition, we must be in query mode
230 FATAL_ERROR_IF(!m_query_mode,
231 "attempted to query on non fully set up LBMManager");
233 std::ostringstream oss;
234 for (const auto &it : m_lbm_lookup) {
236 const std::vector<LoadingBlockModifierDef *> &lbm_list = it.second.lbm_list;
237 for (const auto &lbm_def : lbm_list) {
238 // Don't add if the LBM runs at every load,
239 // then introducement time is hardcoded
240 // and doesn't need to be stored
241 if (lbm_def->run_at_every_load)
243 oss << lbm_def->name << "~" << time << ";";
249 void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, u32 stamp)
251 // Precondition, we need m_lbm_lookup to be initialized
252 FATAL_ERROR_IF(!m_query_mode,
253 "attempted to query on non fully set up LBMManager");
254 v3s16 pos_of_block = block->getPosRelative();
258 lbm_lookup_map::const_iterator it = getLBMsIntroducedAfter(stamp);
259 for (; it != m_lbm_lookup.end(); ++it) {
260 // Cache previous version to speedup lookup which has a very high performance
261 // penalty on each call
262 content_t previous_c{};
263 std::vector<LoadingBlockModifierDef *> *lbm_list = nullptr;
265 for (pos.X = 0; pos.X < MAP_BLOCKSIZE; pos.X++)
266 for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++)
267 for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++) {
268 n = block->getNodeNoEx(pos);
271 // If content_t are not matching perform an LBM lookup
272 if (previous_c != c) {
273 lbm_list = (std::vector<LoadingBlockModifierDef *> *)
274 it->second.lookup(c);
280 for (auto lbmdef : *lbm_list) {
281 lbmdef->trigger(env, pos + pos_of_block, n);
291 void fillRadiusBlock(v3s16 p0, s16 r, std::set<v3s16> &list)
294 for(p.X=p0.X-r; p.X<=p0.X+r; p.X++)
295 for(p.Y=p0.Y-r; p.Y<=p0.Y+r; p.Y++)
296 for(p.Z=p0.Z-r; p.Z<=p0.Z+r; p.Z++)
299 if (p.getDistanceFrom(p0) <= r) {
306 void fillViewConeBlock(v3s16 p0,
308 const v3f camera_pos,
309 const v3f camera_dir,
310 const float camera_fov,
311 std::set<v3s16> &list)
314 const s16 r_nodes = r * BS * MAP_BLOCKSIZE;
315 for (p.X = p0.X - r; p.X <= p0.X+r; p.X++)
316 for (p.Y = p0.Y - r; p.Y <= p0.Y+r; p.Y++)
317 for (p.Z = p0.Z - r; p.Z <= p0.Z+r; p.Z++) {
318 if (isBlockInSight(p, camera_pos, camera_dir, camera_fov, r_nodes)) {
324 void ActiveBlockList::update(std::vector<PlayerSAO*> &active_players,
325 s16 active_block_range,
326 s16 active_object_range,
327 std::set<v3s16> &blocks_removed,
328 std::set<v3s16> &blocks_added)
333 std::set<v3s16> newlist = m_forceloaded_list;
334 m_abm_list = m_forceloaded_list;
335 for (const PlayerSAO *playersao : active_players) {
336 v3s16 pos = getNodeBlockPos(floatToInt(playersao->getBasePosition(), BS));
337 fillRadiusBlock(pos, active_block_range, m_abm_list);
338 fillRadiusBlock(pos, active_block_range, newlist);
340 s16 player_ao_range = std::min(active_object_range, playersao->getWantedRange());
341 // only do this if this would add blocks
342 if (player_ao_range > active_block_range) {
343 v3f camera_dir = v3f(0,0,1);
344 camera_dir.rotateYZBy(playersao->getLookPitch());
345 camera_dir.rotateXZBy(playersao->getRotation().Y);
346 fillViewConeBlock(pos,
348 playersao->getEyePosition(),
356 Find out which blocks on the old list are not on the new list
358 // Go through old list
359 for (v3s16 p : m_list) {
360 // If not on new list, it's been removed
361 if (newlist.find(p) == newlist.end())
362 blocks_removed.insert(p);
366 Find out which blocks on the new list are not on the old list
368 // Go through new list
369 for (v3s16 p : newlist) {
370 // If not on old list, it's been added
371 if(m_list.find(p) == m_list.end())
372 blocks_added.insert(p);
379 for (v3s16 p : newlist) {
388 // Random device to seed pseudo random generators.
389 static std::random_device seed;
391 ServerEnvironment::ServerEnvironment(ServerMap *map,
392 ServerScripting *scriptIface, Server *server,
393 const std::string &path_world):
396 m_script(scriptIface),
398 m_path_world(path_world),
401 // Determine which database backend to use
402 std::string conf_path = path_world + DIR_DELIM + "world.mt";
405 std::string player_backend_name = "sqlite3";
406 std::string auth_backend_name = "sqlite3";
408 bool succeeded = conf.readConfigFile(conf_path.c_str());
410 // If we open world.mt read the backend configurations.
412 // Read those values before setting defaults
413 bool player_backend_exists = conf.exists("player_backend");
414 bool auth_backend_exists = conf.exists("auth_backend");
416 // player backend is not set, assume it's legacy file backend.
417 if (!player_backend_exists) {
418 // fall back to files
419 conf.set("player_backend", "files");
420 player_backend_name = "files";
422 if (!conf.updateConfigFile(conf_path.c_str())) {
423 errorstream << "ServerEnvironment::ServerEnvironment(): "
424 << "Failed to update world.mt!" << std::endl;
427 conf.getNoEx("player_backend", player_backend_name);
430 // auth backend is not set, assume it's legacy file backend.
431 if (!auth_backend_exists) {
432 conf.set("auth_backend", "files");
433 auth_backend_name = "files";
435 if (!conf.updateConfigFile(conf_path.c_str())) {
436 errorstream << "ServerEnvironment::ServerEnvironment(): "
437 << "Failed to update world.mt!" << std::endl;
440 conf.getNoEx("auth_backend", auth_backend_name);
444 if (player_backend_name == "files") {
445 warningstream << "/!\\ You are using old player file backend. "
446 << "This backend is deprecated and will be removed in a future release /!\\"
447 << std::endl << "Switching to SQLite3 or PostgreSQL is advised, "
448 << "please read http://wiki.minetest.net/Database_backends." << std::endl;
451 if (auth_backend_name == "files") {
452 warningstream << "/!\\ You are using old auth file backend. "
453 << "This backend is deprecated and will be removed in a future release /!\\"
454 << std::endl << "Switching to SQLite3 is advised, "
455 << "please read http://wiki.minetest.net/Database_backends." << std::endl;
458 m_player_database = openPlayerDatabase(player_backend_name, path_world, conf);
459 m_auth_database = openAuthDatabase(auth_backend_name, path_world, conf);
462 ServerEnvironment::~ServerEnvironment()
464 // Clear active block list.
465 // This makes the next one delete all active objects.
466 m_active_blocks.clear();
468 // Convert all objects to static and delete the active objects
469 deactivateFarObjects(true);
474 // Delete ActiveBlockModifiers
475 for (ABMWithState &m_abm : m_abms) {
479 // Deallocate players
480 for (RemotePlayer *m_player : m_players) {
484 delete m_player_database;
485 delete m_auth_database;
488 Map & ServerEnvironment::getMap()
493 ServerMap & ServerEnvironment::getServerMap()
498 RemotePlayer *ServerEnvironment::getPlayer(const session_t peer_id)
500 for (RemotePlayer *player : m_players) {
501 if (player->getPeerId() == peer_id)
507 RemotePlayer *ServerEnvironment::getPlayer(const char* name)
509 for (RemotePlayer *player : m_players) {
510 if (strcmp(player->getName(), name) == 0)
516 void ServerEnvironment::addPlayer(RemotePlayer *player)
519 Check that peer_ids are unique.
520 Also check that names are unique.
521 Exception: there can be multiple players with peer_id=0
523 // If peer id is non-zero, it has to be unique.
524 if (player->getPeerId() != PEER_ID_INEXISTENT)
525 FATAL_ERROR_IF(getPlayer(player->getPeerId()) != NULL, "Peer id not unique");
526 // Name has to be unique.
527 FATAL_ERROR_IF(getPlayer(player->getName()) != NULL, "Player name not unique");
529 m_players.push_back(player);
532 void ServerEnvironment::removePlayer(RemotePlayer *player)
534 for (std::vector<RemotePlayer *>::iterator it = m_players.begin();
535 it != m_players.end(); ++it) {
536 if ((*it) == player) {
544 bool ServerEnvironment::removePlayerFromDatabase(const std::string &name)
546 return m_player_database->removePlayer(name);
549 void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason,
550 const std::string &str_reason, bool reconnect)
552 for (RemotePlayer *player : m_players) {
553 m_server->DenyAccessVerCompliant(player->getPeerId(),
554 player->protocol_version, reason, str_reason, reconnect);
558 void ServerEnvironment::saveLoadedPlayers(bool force)
560 for (RemotePlayer *player : m_players) {
561 if (force || player->checkModified() || (player->getPlayerSAO() &&
562 player->getPlayerSAO()->getMeta().isModified())) {
564 m_player_database->savePlayer(player);
565 } catch (DatabaseException &e) {
566 errorstream << "Failed to save player " << player->getName() << " exception: "
567 << e.what() << std::endl;
574 void ServerEnvironment::savePlayer(RemotePlayer *player)
577 m_player_database->savePlayer(player);
578 } catch (DatabaseException &e) {
579 errorstream << "Failed to save player " << player->getName() << " exception: "
580 << e.what() << std::endl;
585 PlayerSAO *ServerEnvironment::loadPlayer(RemotePlayer *player, bool *new_player,
586 session_t peer_id, bool is_singleplayer)
588 PlayerSAO *playersao = new PlayerSAO(this, player, peer_id, is_singleplayer);
589 // Create player if it doesn't exist
590 if (!m_player_database->loadPlayer(player, playersao)) {
592 // Set player position
593 infostream << "Server: Finding spawn place for player \""
594 << player->getName() << "\"" << std::endl;
595 playersao->setBasePosition(m_server->findSpawnPos());
597 // Make sure the player is saved
598 player->setModified(true);
600 // If the player exists, ensure that they respawn inside legal bounds
601 // This fixes an assert crash when the player can't be added
602 // to the environment
603 if (objectpos_over_limit(playersao->getBasePosition())) {
604 actionstream << "Respawn position for player \""
605 << player->getName() << "\" outside limits, resetting" << std::endl;
606 playersao->setBasePosition(m_server->findSpawnPos());
610 // Add player to environment
613 /* Clean up old HUD elements from previous sessions */
616 /* Add object to environment */
617 addActiveObject(playersao);
622 void ServerEnvironment::saveMeta()
624 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
626 // Open file and serialize
627 std::ostringstream ss(std::ios_base::binary);
630 args.setU64("game_time", m_game_time);
631 args.setU64("time_of_day", getTimeOfDay());
632 args.setU64("last_clear_objects_time", m_last_clear_objects_time);
633 args.setU64("lbm_introduction_times_version", 1);
634 args.set("lbm_introduction_times",
635 m_lbm_mgr.createIntroductionTimesString());
636 args.setU64("day_count", m_day_count);
640 if(!fs::safeWriteToFile(path, ss.str()))
642 infostream<<"ServerEnvironment::saveMeta(): Failed to write "
644 throw SerializationError("Couldn't save env meta");
648 void ServerEnvironment::loadMeta()
650 // If file doesn't exist, load default environment metadata
651 if (!fs::PathExists(m_path_world + DIR_DELIM "env_meta.txt")) {
652 infostream << "ServerEnvironment: Loading default environment metadata"
658 infostream << "ServerEnvironment: Loading environment metadata" << std::endl;
660 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
662 // Open file and deserialize
663 std::ifstream is(path.c_str(), std::ios_base::binary);
665 infostream << "ServerEnvironment::loadMeta(): Failed to open "
666 << path << std::endl;
667 throw SerializationError("Couldn't load env meta");
672 if (!args.parseConfigLines(is, "EnvArgsEnd")) {
673 throw SerializationError("ServerEnvironment::loadMeta(): "
674 "EnvArgsEnd not found!");
678 m_game_time = args.getU64("game_time");
679 } catch (SettingNotFoundException &e) {
680 // Getting this is crucial, otherwise timestamps are useless
681 throw SerializationError("Couldn't load env meta game_time");
684 setTimeOfDay(args.exists("time_of_day") ?
685 // set day to early morning by default
686 args.getU64("time_of_day") : 5250);
688 m_last_clear_objects_time = args.exists("last_clear_objects_time") ?
689 // If missing, do as if clearObjects was never called
690 args.getU64("last_clear_objects_time") : 0;
692 std::string lbm_introduction_times;
694 u64 ver = args.getU64("lbm_introduction_times_version");
696 lbm_introduction_times = args.get("lbm_introduction_times");
698 infostream << "ServerEnvironment::loadMeta(): Non-supported"
699 << " introduction time version " << ver << std::endl;
701 } catch (SettingNotFoundException &e) {
702 // No problem, this is expected. Just continue with an empty string
704 m_lbm_mgr.loadIntroductionTimes(lbm_introduction_times, m_server, m_game_time);
706 m_day_count = args.exists("day_count") ?
707 args.getU64("day_count") : 0;
711 * called if env_meta.txt doesn't exist (e.g. new world)
713 void ServerEnvironment::loadDefaultMeta()
715 m_lbm_mgr.loadIntroductionTimes("", m_server, m_game_time);
720 ActiveBlockModifier *abm;
722 std::vector<content_t> required_neighbors;
723 bool check_required_neighbors; // false if required_neighbors is known to be empty
729 ServerEnvironment *m_env;
730 std::vector<std::vector<ActiveABM> *> m_aabms;
732 ABMHandler(std::vector<ABMWithState> &abms,
733 float dtime_s, ServerEnvironment *env,
739 const NodeDefManager *ndef = env->getGameDef()->ndef();
740 for (ABMWithState &abmws : abms) {
741 ActiveBlockModifier *abm = abmws.abm;
742 float trigger_interval = abm->getTriggerInterval();
743 if(trigger_interval < 0.001)
744 trigger_interval = 0.001;
745 float actual_interval = dtime_s;
747 abmws.timer += dtime_s;
748 if(abmws.timer < trigger_interval)
750 abmws.timer -= trigger_interval;
751 actual_interval = trigger_interval;
753 float chance = abm->getTriggerChance();
758 if (abm->getSimpleCatchUp()) {
759 float intervals = actual_interval / trigger_interval;
762 aabm.chance = chance / intervals;
766 aabm.chance = chance;
770 const std::vector<std::string> &required_neighbors_s =
771 abm->getRequiredNeighbors();
772 for (const std::string &required_neighbor_s : required_neighbors_s) {
773 ndef->getIds(required_neighbor_s, aabm.required_neighbors);
775 aabm.check_required_neighbors = !required_neighbors_s.empty();
778 const std::vector<std::string> &contents_s = abm->getTriggerContents();
779 for (const std::string &content_s : contents_s) {
780 std::vector<content_t> ids;
781 ndef->getIds(content_s, ids);
782 for (content_t c : ids) {
783 if (c >= m_aabms.size())
784 m_aabms.resize(c + 256, NULL);
786 m_aabms[c] = new std::vector<ActiveABM>;
787 m_aabms[c]->push_back(aabm);
795 for (auto &aabms : m_aabms)
799 // Find out how many objects the given block and its neighbours contain.
800 // Returns the number of objects in the block, and also in 'wider' the
801 // number of objects in the block and all its neighbours. The latter
802 // may an estimate if any neighbours are unloaded.
803 u32 countObjects(MapBlock *block, ServerMap * map, u32 &wider)
806 u32 wider_unknown_count = 0;
807 for(s16 x=-1; x<=1; x++)
808 for(s16 y=-1; y<=1; y++)
809 for(s16 z=-1; z<=1; z++)
811 MapBlock *block2 = map->getBlockNoCreateNoEx(
812 block->getPos() + v3s16(x,y,z));
814 wider_unknown_count++;
817 wider += block2->m_static_objects.m_active.size()
818 + block2->m_static_objects.m_stored.size();
821 u32 active_object_count = block->m_static_objects.m_active.size();
822 u32 wider_known_count = 3*3*3 - wider_unknown_count;
823 wider += wider_unknown_count * wider / wider_known_count;
824 return active_object_count;
827 void apply(MapBlock *block, int &blocks_scanned, int &abms_run, int &blocks_cached)
829 if(m_aabms.empty() || block->isDummy())
832 // Check the content type cache first
833 // to see whether there are any ABMs
834 // to be run at all for this block.
835 if (block->contents_cached) {
837 bool run_abms = false;
838 for (content_t c : block->contents) {
839 if (c < m_aabms.size() && m_aabms[c]) {
848 block->contents.clear();
852 ServerMap *map = &m_env->getServerMap();
854 u32 active_object_count_wider;
855 u32 active_object_count = this->countObjects(block, map, active_object_count_wider);
856 m_env->m_added_objects = 0;
859 for(p0.X=0; p0.X<MAP_BLOCKSIZE; p0.X++)
860 for(p0.Y=0; p0.Y<MAP_BLOCKSIZE; p0.Y++)
861 for(p0.Z=0; p0.Z<MAP_BLOCKSIZE; p0.Z++)
863 const MapNode &n = block->getNodeUnsafe(p0);
864 content_t c = n.getContent();
865 // Cache content types as we go
866 if (!block->contents_cached && !block->do_not_cache_contents) {
867 block->contents.insert(c);
868 if (block->contents.size() > 64) {
869 // Too many different nodes... don't try to cache
870 block->do_not_cache_contents = true;
871 block->contents.clear();
875 if (c >= m_aabms.size() || !m_aabms[c])
878 v3s16 p = p0 + block->getPosRelative();
879 for (ActiveABM &aabm : *m_aabms[c]) {
880 if (myrand() % aabm.chance != 0)
884 if (aabm.check_required_neighbors) {
886 for(p1.X = p0.X-1; p1.X <= p0.X+1; p1.X++)
887 for(p1.Y = p0.Y-1; p1.Y <= p0.Y+1; p1.Y++)
888 for(p1.Z = p0.Z-1; p1.Z <= p0.Z+1; p1.Z++)
893 if (block->isValidPosition(p1)) {
894 // if the neighbor is found on the same map block
895 // get it straight from there
896 const MapNode &n = block->getNodeUnsafe(p1);
899 // otherwise consult the map
900 MapNode n = map->getNode(p1 + block->getPosRelative());
903 if (CONTAINS(aabm.required_neighbors, c))
906 // No required neighbor found
912 // Call all the trigger variations
913 aabm.abm->trigger(m_env, p, n);
914 aabm.abm->trigger(m_env, p, n,
915 active_object_count, active_object_count_wider);
917 // Count surrounding objects again if the abms added any
918 if(m_env->m_added_objects > 0) {
919 active_object_count = countObjects(block, map, active_object_count_wider);
920 m_env->m_added_objects = 0;
924 block->contents_cached = !block->do_not_cache_contents;
928 void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime)
930 // Reset usage timer immediately, otherwise a block that becomes active
931 // again at around the same time as it would normally be unloaded will
932 // get unloaded incorrectly. (I think this still leaves a small possibility
933 // of a race condition between this and server::AsyncRunStep, which only
934 // some kind of synchronisation will fix, but it at least reduces the window
935 // of opportunity for it to break from seconds to nanoseconds)
936 block->resetUsageTimer();
938 // Get time difference
940 u32 stamp = block->getTimestamp();
941 if (m_game_time > stamp && stamp != BLOCK_TIMESTAMP_UNDEFINED)
942 dtime_s = m_game_time - stamp;
943 dtime_s += additional_dtime;
945 /*infostream<<"ServerEnvironment::activateBlock(): block timestamp: "
946 <<stamp<<", game time: "<<m_game_time<<std::endl;*/
948 // Remove stored static objects if clearObjects was called since block's timestamp
949 if (stamp == BLOCK_TIMESTAMP_UNDEFINED || stamp < m_last_clear_objects_time) {
950 block->m_static_objects.m_stored.clear();
951 // do not set changed flag to avoid unnecessary mapblock writes
954 // Set current time as timestamp
955 block->setTimestampNoChangedFlag(m_game_time);
957 /*infostream<<"ServerEnvironment::activateBlock(): block is "
958 <<dtime_s<<" seconds old."<<std::endl;*/
960 // Activate stored objects
961 activateObjects(block, dtime_s);
963 /* Handle LoadingBlockModifiers */
964 m_lbm_mgr.applyLBMs(this, block, stamp);
967 std::vector<NodeTimer> elapsed_timers =
968 block->m_node_timers.step((float)dtime_s);
969 if (!elapsed_timers.empty()) {
971 for (const NodeTimer &elapsed_timer : elapsed_timers) {
972 n = block->getNodeNoEx(elapsed_timer.position);
973 v3s16 p = elapsed_timer.position + block->getPosRelative();
974 if (m_script->node_on_timer(p, n, elapsed_timer.elapsed))
975 block->setNodeTimer(NodeTimer(elapsed_timer.timeout, 0,
976 elapsed_timer.position));
981 void ServerEnvironment::addActiveBlockModifier(ActiveBlockModifier *abm)
983 m_abms.emplace_back(abm);
986 void ServerEnvironment::addLoadingBlockModifierDef(LoadingBlockModifierDef *lbm)
988 m_lbm_mgr.addLBMDef(lbm);
991 bool ServerEnvironment::setNode(v3s16 p, const MapNode &n)
993 const NodeDefManager *ndef = m_server->ndef();
994 MapNode n_old = m_map->getNode(p);
996 const ContentFeatures &cf_old = ndef->get(n_old);
999 if (cf_old.has_on_destruct)
1000 m_script->node_on_destruct(p, n_old);
1003 if (!m_map->addNodeWithEvent(p, n))
1006 // Update active VoxelManipulator if a mapgen thread
1007 m_map->updateVManip(p);
1009 // Call post-destructor
1010 if (cf_old.has_after_destruct)
1011 m_script->node_after_destruct(p, n_old);
1013 // Retrieve node content features
1014 // if new node is same as old, reuse old definition to prevent a lookup
1015 const ContentFeatures &cf_new = n_old == n ? cf_old : ndef->get(n);
1018 if (cf_new.has_on_construct)
1019 m_script->node_on_construct(p, n);
1024 bool ServerEnvironment::removeNode(v3s16 p)
1026 const NodeDefManager *ndef = m_server->ndef();
1027 MapNode n_old = m_map->getNode(p);
1030 if (ndef->get(n_old).has_on_destruct)
1031 m_script->node_on_destruct(p, n_old);
1034 // This is slightly optimized compared to addNodeWithEvent(air)
1035 if (!m_map->removeNodeWithEvent(p))
1038 // Update active VoxelManipulator if a mapgen thread
1039 m_map->updateVManip(p);
1041 // Call post-destructor
1042 if (ndef->get(n_old).has_after_destruct)
1043 m_script->node_after_destruct(p, n_old);
1045 // Air doesn't require constructor
1049 bool ServerEnvironment::swapNode(v3s16 p, const MapNode &n)
1051 if (!m_map->addNodeWithEvent(p, n, false))
1054 // Update active VoxelManipulator if a mapgen thread
1055 m_map->updateVManip(p);
1060 void ServerEnvironment::clearObjects(ClearObjectsMode mode)
1062 infostream << "ServerEnvironment::clearObjects(): "
1063 << "Removing all active objects" << std::endl;
1064 auto cb_removal = [this] (ServerActiveObject *obj, u16 id) {
1065 if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER)
1068 // Delete static object if block is loaded
1069 deleteStaticFromBlock(obj, id, MOD_REASON_CLEAR_ALL_OBJECTS, true);
1071 // If known by some client, don't delete immediately
1072 if (obj->m_known_by_count > 0) {
1073 obj->m_pending_removal = true;
1077 // Tell the object about removal
1078 obj->removingFromEnvironment();
1079 // Deregister in scripting api
1080 m_script->removeObjectReference(obj);
1082 // Delete active object
1083 if (obj->environmentDeletes())
1089 m_ao_manager.clear(cb_removal);
1091 // Get list of loaded blocks
1092 std::vector<v3s16> loaded_blocks;
1093 infostream << "ServerEnvironment::clearObjects(): "
1094 << "Listing all loaded blocks" << std::endl;
1095 m_map->listAllLoadedBlocks(loaded_blocks);
1096 infostream << "ServerEnvironment::clearObjects(): "
1097 << "Done listing all loaded blocks: "
1098 << loaded_blocks.size()<<std::endl;
1100 // Get list of loadable blocks
1101 std::vector<v3s16> loadable_blocks;
1102 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1103 infostream << "ServerEnvironment::clearObjects(): "
1104 << "Listing all loadable blocks" << std::endl;
1105 m_map->listAllLoadableBlocks(loadable_blocks);
1106 infostream << "ServerEnvironment::clearObjects(): "
1107 << "Done listing all loadable blocks: "
1108 << loadable_blocks.size() << std::endl;
1110 loadable_blocks = loaded_blocks;
1113 actionstream << "ServerEnvironment::clearObjects(): "
1114 << "Now clearing objects in " << loadable_blocks.size()
1115 << " blocks" << std::endl;
1117 // Grab a reference on each loaded block to avoid unloading it
1118 for (v3s16 p : loaded_blocks) {
1119 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1120 assert(block != NULL);
1124 // Remove objects in all loadable blocks
1125 u32 unload_interval = U32_MAX;
1126 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1127 unload_interval = g_settings->getS32("max_clearobjects_extra_loaded_blocks");
1128 unload_interval = MYMAX(unload_interval, 1);
1130 u32 report_interval = loadable_blocks.size() / 10;
1131 u32 num_blocks_checked = 0;
1132 u32 num_blocks_cleared = 0;
1133 u32 num_objs_cleared = 0;
1134 for (auto i = loadable_blocks.begin();
1135 i != loadable_blocks.end(); ++i) {
1137 MapBlock *block = m_map->emergeBlock(p, false);
1139 errorstream << "ServerEnvironment::clearObjects(): "
1140 << "Failed to emerge block " << PP(p) << std::endl;
1143 u32 num_stored = block->m_static_objects.m_stored.size();
1144 u32 num_active = block->m_static_objects.m_active.size();
1145 if (num_stored != 0 || num_active != 0) {
1146 block->m_static_objects.m_stored.clear();
1147 block->m_static_objects.m_active.clear();
1148 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1149 MOD_REASON_CLEAR_ALL_OBJECTS);
1150 num_objs_cleared += num_stored + num_active;
1151 num_blocks_cleared++;
1153 num_blocks_checked++;
1155 if (report_interval != 0 &&
1156 num_blocks_checked % report_interval == 0) {
1157 float percent = 100.0 * (float)num_blocks_checked /
1158 loadable_blocks.size();
1159 actionstream << "ServerEnvironment::clearObjects(): "
1160 << "Cleared " << num_objs_cleared << " objects"
1161 << " in " << num_blocks_cleared << " blocks ("
1162 << percent << "%)" << std::endl;
1164 if (num_blocks_checked % unload_interval == 0) {
1165 m_map->unloadUnreferencedBlocks();
1168 m_map->unloadUnreferencedBlocks();
1170 // Drop references that were added above
1171 for (v3s16 p : loaded_blocks) {
1172 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1177 m_last_clear_objects_time = m_game_time;
1179 actionstream << "ServerEnvironment::clearObjects(): "
1180 << "Finished: Cleared " << num_objs_cleared << " objects"
1181 << " in " << num_blocks_cleared << " blocks" << std::endl;
1184 void ServerEnvironment::step(float dtime)
1186 ScopeProfiler sp2(g_profiler, "ServerEnv::step()", SPT_AVG);
1187 /* Step time of day */
1188 stepTimeOfDay(dtime);
1191 // NOTE: This is kind of funny on a singleplayer game, but doesn't
1192 // really matter that much.
1193 static thread_local const float server_step =
1194 g_settings->getFloat("dedicated_server_step");
1195 m_recommended_send_interval = server_step;
1201 m_game_time_fraction_counter += dtime;
1202 u32 inc_i = (u32)m_game_time_fraction_counter;
1203 m_game_time += inc_i;
1204 m_game_time_fraction_counter -= (float)inc_i;
1211 ScopeProfiler sp(g_profiler, "ServerEnv: move players", SPT_AVG);
1212 for (RemotePlayer *player : m_players) {
1213 // Ignore disconnected players
1214 if (player->getPeerId() == PEER_ID_INEXISTENT)
1218 player->move(dtime, this, 100 * BS);
1223 Manage active block list
1225 if (m_active_blocks_management_interval.step(dtime, m_cache_active_block_mgmt_interval)) {
1226 ScopeProfiler sp(g_profiler, "ServerEnv: update active blocks", SPT_AVG);
1228 Get player block positions
1230 std::vector<PlayerSAO*> players;
1231 for (RemotePlayer *player: m_players) {
1232 // Ignore disconnected players
1233 if (player->getPeerId() == PEER_ID_INEXISTENT)
1236 PlayerSAO *playersao = player->getPlayerSAO();
1239 players.push_back(playersao);
1243 Update list of active blocks, collecting changes
1245 // use active_object_send_range_blocks since that is max distance
1246 // for active objects sent the client anyway
1247 static thread_local const s16 active_object_range =
1248 g_settings->getS16("active_object_send_range_blocks");
1249 static thread_local const s16 active_block_range =
1250 g_settings->getS16("active_block_range");
1251 std::set<v3s16> blocks_removed;
1252 std::set<v3s16> blocks_added;
1253 m_active_blocks.update(players, active_block_range, active_object_range,
1254 blocks_removed, blocks_added);
1257 Handle removed blocks
1260 // Convert active objects that are no more in active blocks to static
1261 deactivateFarObjects(false);
1263 for (const v3s16 &p: blocks_removed) {
1264 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1268 // Set current time as timestamp (and let it set ChangedFlag)
1269 block->setTimestamp(m_game_time);
1276 for (const v3s16 &p: blocks_added) {
1277 MapBlock *block = m_map->getBlockOrEmerge(p);
1279 m_active_blocks.m_list.erase(p);
1280 m_active_blocks.m_abm_list.erase(p);
1284 activateBlock(block);
1289 Mess around in active blocks
1291 if (m_active_blocks_nodemetadata_interval.step(dtime, m_cache_nodetimer_interval)) {
1292 ScopeProfiler sp(g_profiler, "ServerEnv: Run node timers", SPT_AVG);
1294 float dtime = m_cache_nodetimer_interval;
1296 for (const v3s16 &p: m_active_blocks.m_list) {
1297 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1301 // Reset block usage timer
1302 block->resetUsageTimer();
1304 // Set current time as timestamp
1305 block->setTimestampNoChangedFlag(m_game_time);
1306 // If time has changed much from the one on disk,
1307 // set block to be saved when it is unloaded
1308 if(block->getTimestamp() > block->getDiskTimestamp() + 60)
1309 block->raiseModified(MOD_STATE_WRITE_AT_UNLOAD,
1310 MOD_REASON_BLOCK_EXPIRED);
1313 std::vector<NodeTimer> elapsed_timers = block->m_node_timers.step(dtime);
1314 if (!elapsed_timers.empty()) {
1317 for (const NodeTimer &elapsed_timer: elapsed_timers) {
1318 n = block->getNodeNoEx(elapsed_timer.position);
1319 p2 = elapsed_timer.position + block->getPosRelative();
1320 if (m_script->node_on_timer(p2, n, elapsed_timer.elapsed)) {
1321 block->setNodeTimer(NodeTimer(
1322 elapsed_timer.timeout, 0, elapsed_timer.position));
1329 if (m_active_block_modifier_interval.step(dtime, m_cache_abm_interval)) {
1330 ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg per interval", SPT_AVG);
1331 TimeTaker timer("modify in active blocks per interval");
1333 // Initialize handling of ActiveBlockModifiers
1334 ABMHandler abmhandler(m_abms, m_cache_abm_interval, this, true);
1336 int blocks_scanned = 0;
1338 int blocks_cached = 0;
1340 std::vector<v3s16> output(m_active_blocks.m_abm_list.size());
1342 // Shuffle the active blocks so that each block gets an equal chance
1343 // of having its ABMs run.
1344 std::copy(m_active_blocks.m_abm_list.begin(), m_active_blocks.m_abm_list.end(), output.begin());
1345 std::shuffle(output.begin(), output.end(), m_rgen);
1348 // The time budget for ABMs is 20%.
1349 u32 max_time_ms = m_cache_abm_interval * 1000 / 5;
1350 for (const v3s16 &p : output) {
1351 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1357 // Set current time as timestamp
1358 block->setTimestampNoChangedFlag(m_game_time);
1360 /* Handle ActiveBlockModifiers */
1361 abmhandler.apply(block, blocks_scanned, abms_run, blocks_cached);
1363 u32 time_ms = timer.getTimerTime();
1365 if (time_ms > max_time_ms) {
1366 warningstream << "active block modifiers took "
1367 << time_ms << "ms (processed " << i << " of "
1368 << output.size() << " active blocks)" << std::endl;
1372 g_profiler->avg("ServerEnv: active blocks", m_active_blocks.m_abm_list.size());
1373 g_profiler->avg("ServerEnv: active blocks cached", blocks_cached);
1374 g_profiler->avg("ServerEnv: active blocks scanned for ABMs", blocks_scanned);
1375 g_profiler->avg("ServerEnv: ABMs run", abms_run);
1381 Step script environment (run global on_step())
1383 m_script->environment_Step(dtime);
1389 ScopeProfiler sp(g_profiler, "ServerEnv: Run SAO::step()", SPT_AVG);
1391 // This helps the objects to send data at the same time
1392 bool send_recommended = false;
1393 m_send_recommended_timer += dtime;
1394 if (m_send_recommended_timer > getSendRecommendedInterval()) {
1395 m_send_recommended_timer -= getSendRecommendedInterval();
1396 send_recommended = true;
1399 auto cb_state = [this, dtime, send_recommended] (ServerActiveObject *obj) {
1404 obj->step(dtime, send_recommended);
1405 // Read messages from object
1406 obj->dumpAOMessagesToQueue(m_active_object_messages);
1408 m_ao_manager.step(dtime, cb_state);
1412 Manage active objects
1414 if (m_object_management_interval.step(dtime, 0.5)) {
1415 removeRemovedObjects();
1419 Manage particle spawner expiration
1421 if (m_particle_management_interval.step(dtime, 1.0)) {
1422 for (std::unordered_map<u32, float>::iterator i = m_particle_spawners.begin();
1423 i != m_particle_spawners.end(); ) {
1424 //non expiring spawners
1425 if (i->second == PARTICLE_SPAWNER_NO_EXPIRY) {
1431 if (i->second <= 0.f)
1432 m_particle_spawners.erase(i++);
1438 // Send outdated player inventories
1439 for (RemotePlayer *player : m_players) {
1440 if (player->getPeerId() == PEER_ID_INEXISTENT)
1443 PlayerSAO *sao = player->getPlayerSAO();
1444 if (sao && player->inventory.checkModified())
1445 m_server->SendInventory(sao, true);
1448 // Send outdated detached inventories
1449 m_server->sendDetachedInventories(PEER_ID_INEXISTENT, true);
1452 u32 ServerEnvironment::addParticleSpawner(float exptime)
1454 // Timers with lifetime 0 do not expire
1455 float time = exptime > 0.f ? exptime : PARTICLE_SPAWNER_NO_EXPIRY;
1458 for (;;) { // look for unused particlespawner id
1460 std::unordered_map<u32, float>::iterator f = m_particle_spawners.find(id);
1461 if (f == m_particle_spawners.end()) {
1462 m_particle_spawners[id] = time;
1469 u32 ServerEnvironment::addParticleSpawner(float exptime, u16 attached_id)
1471 u32 id = addParticleSpawner(exptime);
1472 m_particle_spawner_attachments[id] = attached_id;
1473 if (ServerActiveObject *obj = getActiveObject(attached_id)) {
1474 obj->attachParticleSpawner(id);
1479 void ServerEnvironment::deleteParticleSpawner(u32 id, bool remove_from_object)
1481 m_particle_spawners.erase(id);
1482 const auto &it = m_particle_spawner_attachments.find(id);
1483 if (it != m_particle_spawner_attachments.end()) {
1484 u16 obj_id = it->second;
1485 ServerActiveObject *sao = getActiveObject(obj_id);
1486 if (sao != NULL && remove_from_object) {
1487 sao->detachParticleSpawner(id);
1489 m_particle_spawner_attachments.erase(id);
1493 u16 ServerEnvironment::addActiveObject(ServerActiveObject *object)
1495 assert(object); // Pre-condition
1497 u16 id = addActiveObjectRaw(object, true, 0);
1502 Finds out what new objects have been added to
1503 inside a radius around a position
1505 void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius,
1507 std::set<u16> ¤t_objects,
1508 std::queue<u16> &added_objects)
1510 f32 radius_f = radius * BS;
1511 f32 player_radius_f = player_radius * BS;
1513 if (player_radius_f < 0.0f)
1514 player_radius_f = 0.0f;
1516 m_ao_manager.getAddedActiveObjectsAroundPos(playersao->getBasePosition(), radius_f,
1517 player_radius_f, current_objects, added_objects);
1521 Finds out what objects have been removed from
1522 inside a radius around a position
1524 void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius,
1526 std::set<u16> ¤t_objects,
1527 std::queue<u16> &removed_objects)
1529 f32 radius_f = radius * BS;
1530 f32 player_radius_f = player_radius * BS;
1532 if (player_radius_f < 0)
1533 player_radius_f = 0;
1535 Go through current_objects; object is removed if:
1536 - object is not found in m_active_objects (this is actually an
1537 error condition; objects should be removed only after all clients
1538 have been informed about removal), or
1539 - object is to be removed or deactivated, or
1540 - object is too far away
1542 for (u16 id : current_objects) {
1543 ServerActiveObject *object = getActiveObject(id);
1545 if (object == NULL) {
1546 infostream << "ServerEnvironment::getRemovedActiveObjects():"
1547 << " object in current_objects is NULL" << std::endl;
1548 removed_objects.push(id);
1552 if (object->isGone()) {
1553 removed_objects.push(id);
1557 f32 distance_f = object->getBasePosition().getDistanceFrom(playersao->getBasePosition());
1558 if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1559 if (distance_f <= player_radius_f || player_radius_f == 0)
1561 } else if (distance_f <= radius_f)
1564 // Object is no longer visible
1565 removed_objects.push(id);
1569 void ServerEnvironment::setStaticForActiveObjectsInBlock(
1570 v3s16 blockpos, bool static_exists, v3s16 static_block)
1572 MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos);
1576 for (auto &so_it : block->m_static_objects.m_active) {
1577 // Get the ServerActiveObject counterpart to this StaticObject
1578 ServerActiveObject *sao = m_ao_manager.getActiveObject(so_it.first);
1580 // If this ever happens, there must be some kind of nasty bug.
1581 errorstream << "ServerEnvironment::setStaticForObjectsInBlock(): "
1582 "Object from MapBlock::m_static_objects::m_active not found "
1583 "in m_active_objects";
1587 sao->m_static_exists = static_exists;
1588 sao->m_static_block = static_block;
1592 ActiveObjectMessage ServerEnvironment::getActiveObjectMessage()
1594 if(m_active_object_messages.empty())
1595 return ActiveObjectMessage(0);
1597 ActiveObjectMessage message = m_active_object_messages.front();
1598 m_active_object_messages.pop();
1602 void ServerEnvironment::getSelectedActiveObjects(
1603 const core::line3d<f32> &shootline_on_map,
1604 std::vector<PointedThing> &objects)
1606 std::vector<u16> objectIds;
1607 getObjectsInsideRadius(objectIds, shootline_on_map.start,
1608 shootline_on_map.getLength() + 10.0f);
1609 const v3f line_vector = shootline_on_map.getVector();
1611 for (u16 objectId : objectIds) {
1612 ServerActiveObject* obj = getActiveObject(objectId);
1614 aabb3f selection_box;
1615 if (!obj->getSelectionBox(&selection_box))
1618 v3f pos = obj->getBasePosition();
1620 aabb3f offsetted_box(selection_box.MinEdge + pos,
1621 selection_box.MaxEdge + pos);
1623 v3f current_intersection;
1624 v3s16 current_normal;
1625 if (boxLineCollision(offsetted_box, shootline_on_map.start, line_vector,
1626 ¤t_intersection, ¤t_normal)) {
1627 objects.emplace_back(
1628 (s16) objectId, current_intersection, current_normal,
1629 (current_intersection - shootline_on_map.start).getLengthSQ());
1635 ************ Private methods *************
1638 u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object,
1639 bool set_changed, u32 dtime_s)
1641 if (!m_ao_manager.registerObject(object)) {
1645 // Register reference in scripting api (must be done before post-init)
1646 m_script->addObjectReference(object);
1647 // Post-initialize object
1648 object->addedToEnvironment(dtime_s);
1650 // Add static data to block
1651 if (object->isStaticAllowed()) {
1652 // Add static object to active static list of the block
1653 v3f objectpos = object->getBasePosition();
1654 StaticObject s_obj(object, objectpos);
1655 // Add to the block where the object is located in
1656 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
1657 MapBlock *block = m_map->emergeBlock(blockpos);
1659 block->m_static_objects.m_active[object->getId()] = s_obj;
1660 object->m_static_exists = true;
1661 object->m_static_block = blockpos;
1664 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1665 MOD_REASON_ADD_ACTIVE_OBJECT_RAW);
1667 v3s16 p = floatToInt(objectpos, BS);
1668 errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
1669 <<"could not emerge block for storing id="<<object->getId()
1670 <<" statically (pos="<<PP(p)<<")"<<std::endl;
1674 return object->getId();
1678 Remove objects that satisfy (isGone() && m_known_by_count==0)
1680 void ServerEnvironment::removeRemovedObjects()
1682 ScopeProfiler sp(g_profiler, "ServerEnvironment::removeRemovedObjects()", SPT_AVG);
1684 auto clear_cb = [this] (ServerActiveObject *obj, u16 id) {
1685 // This shouldn't happen but check it
1687 errorstream << "ServerEnvironment::removeRemovedObjects(): "
1688 << "NULL object found. id=" << id << std::endl;
1693 We will handle objects marked for removal or deactivation
1699 Delete static data from block if removed
1701 if (obj->m_pending_removal)
1702 deleteStaticFromBlock(obj, id, MOD_REASON_REMOVE_OBJECTS_REMOVE, false);
1704 // If still known by clients, don't actually remove. On some future
1705 // invocation this will be 0, which is when removal will continue.
1706 if(obj->m_known_by_count > 0)
1710 Move static data from active to stored if deactivated
1712 if (!obj->m_pending_removal && obj->m_static_exists) {
1713 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
1715 const auto i = block->m_static_objects.m_active.find(id);
1716 if (i != block->m_static_objects.m_active.end()) {
1717 block->m_static_objects.m_stored.push_back(i->second);
1718 block->m_static_objects.m_active.erase(id);
1719 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1720 MOD_REASON_REMOVE_OBJECTS_DEACTIVATE);
1722 warningstream << "ServerEnvironment::removeRemovedObjects(): "
1723 << "id=" << id << " m_static_exists=true but "
1724 << "static data doesn't actually exist in "
1725 << PP(obj->m_static_block) << std::endl;
1728 infostream << "Failed to emerge block from which an object to "
1729 << "be deactivated was loaded from. id=" << id << std::endl;
1733 // Tell the object about removal
1734 obj->removingFromEnvironment();
1735 // Deregister in scripting api
1736 m_script->removeObjectReference(obj);
1739 if (obj->environmentDeletes())
1745 m_ao_manager.clear(clear_cb);
1748 static void print_hexdump(std::ostream &o, const std::string &data)
1750 const int linelength = 16;
1751 for(int l=0; ; l++){
1752 int i0 = linelength * l;
1753 bool at_end = false;
1754 int thislinelength = linelength;
1755 if(i0 + thislinelength > (int)data.size()){
1756 thislinelength = data.size() - i0;
1759 for(int di=0; di<linelength; di++){
1762 if(di<thislinelength)
1763 porting::mt_snprintf(buf, sizeof(buf), "%.2x ", data[i]);
1765 porting::mt_snprintf(buf, sizeof(buf), " ");
1769 for(int di=0; di<thislinelength; di++){
1782 ServerActiveObject* ServerEnvironment::createSAO(ActiveObjectType type, v3f pos,
1783 const std::string &data)
1786 case ACTIVEOBJECT_TYPE_LUAENTITY:
1787 return new LuaEntitySAO(this, pos, data);
1789 warningstream << "ServerActiveObject: No factory for type=" << type << std::endl;
1795 Convert stored objects from blocks near the players to active.
1797 void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s)
1802 // Ignore if no stored objects (to not set changed flag)
1803 if(block->m_static_objects.m_stored.empty())
1806 verbosestream<<"ServerEnvironment::activateObjects(): "
1807 <<"activating objects of block "<<PP(block->getPos())
1808 <<" ("<<block->m_static_objects.m_stored.size()
1809 <<" objects)"<<std::endl;
1810 bool large_amount = (block->m_static_objects.m_stored.size() > g_settings->getU16("max_objects_per_block"));
1812 errorstream<<"suspiciously large amount of objects detected: "
1813 <<block->m_static_objects.m_stored.size()<<" in "
1814 <<PP(block->getPos())
1815 <<"; removing all of them."<<std::endl;
1816 // Clear stored list
1817 block->m_static_objects.m_stored.clear();
1818 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1819 MOD_REASON_TOO_MANY_OBJECTS);
1823 // Activate stored objects
1824 std::vector<StaticObject> new_stored;
1825 for (const StaticObject &s_obj : block->m_static_objects.m_stored) {
1826 // Create an active object from the data
1827 ServerActiveObject *obj = createSAO((ActiveObjectType) s_obj.type, s_obj.pos,
1829 // If couldn't create object, store static data back.
1831 errorstream<<"ServerEnvironment::activateObjects(): "
1832 <<"failed to create active object from static object "
1833 <<"in block "<<PP(s_obj.pos/BS)
1834 <<" type="<<(int)s_obj.type<<" data:"<<std::endl;
1835 print_hexdump(verbosestream, s_obj.data);
1837 new_stored.push_back(s_obj);
1840 verbosestream<<"ServerEnvironment::activateObjects(): "
1841 <<"activated static object pos="<<PP(s_obj.pos/BS)
1842 <<" type="<<(int)s_obj.type<<std::endl;
1843 // This will also add the object to the active static list
1844 addActiveObjectRaw(obj, false, dtime_s);
1847 // Clear stored list
1848 block->m_static_objects.m_stored.clear();
1849 // Add leftover failed stuff to stored list
1850 for (const StaticObject &s_obj : new_stored) {
1851 block->m_static_objects.m_stored.push_back(s_obj);
1855 Note: Block hasn't really been modified here.
1856 The objects have just been activated and moved from the stored
1857 static list to the active static list.
1858 As such, the block is essentially the same.
1859 Thus, do not call block->raiseModified(MOD_STATE_WRITE_NEEDED).
1860 Otherwise there would be a huge amount of unnecessary I/O.
1865 Convert objects that are not standing inside active blocks to static.
1867 If m_known_by_count != 0, active object is not deleted, but static
1868 data is still updated.
1870 If force_delete is set, active object is deleted nevertheless. It
1871 shall only be set so in the destructor of the environment.
1873 If block wasn't generated (not in memory or on disk),
1875 void ServerEnvironment::deactivateFarObjects(bool _force_delete)
1877 auto cb_deactivate = [this, _force_delete] (ServerActiveObject *obj, u16 id) {
1878 // force_delete might be overriden per object
1879 bool force_delete = _force_delete;
1881 // Do not deactivate if static data creation not allowed
1882 if (!force_delete && !obj->isStaticAllowed())
1885 // removeRemovedObjects() is responsible for these
1886 if (!force_delete && obj->isGone())
1889 const v3f &objectpos = obj->getBasePosition();
1891 // The block in which the object resides in
1892 v3s16 blockpos_o = getNodeBlockPos(floatToInt(objectpos, BS));
1894 // If object's static data is stored in a deactivated block and object
1895 // is actually located in an active block, re-save to the block in
1896 // which the object is actually located in.
1897 if (!force_delete && obj->m_static_exists &&
1898 !m_active_blocks.contains(obj->m_static_block) &&
1899 m_active_blocks.contains(blockpos_o)) {
1900 // Delete from block where object was located
1901 deleteStaticFromBlock(obj, id, MOD_REASON_STATIC_DATA_REMOVED, false);
1903 StaticObject s_obj(obj, objectpos);
1904 // Save to block where object is located
1905 saveStaticToBlock(blockpos_o, id, obj, s_obj, MOD_REASON_STATIC_DATA_ADDED);
1910 // If block is still active, don't remove
1911 if (!force_delete && m_active_blocks.contains(blockpos_o))
1914 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1915 << "deactivating object id=" << id << " on inactive block "
1916 << PP(blockpos_o) << std::endl;
1918 // If known by some client, don't immediately delete.
1919 bool pending_delete = (obj->m_known_by_count > 0 && !force_delete);
1922 Update the static data
1924 if (obj->isStaticAllowed()) {
1925 // Create new static object
1926 StaticObject s_obj(obj, objectpos);
1928 bool stays_in_same_block = false;
1929 bool data_changed = true;
1931 // Check if static data has changed considerably
1932 if (obj->m_static_exists) {
1933 if (obj->m_static_block == blockpos_o)
1934 stays_in_same_block = true;
1936 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
1939 const auto n = block->m_static_objects.m_active.find(id);
1940 if (n != block->m_static_objects.m_active.end()) {
1941 StaticObject static_old = n->second;
1943 float save_movem = obj->getMinimumSavedMovement();
1945 if (static_old.data == s_obj.data &&
1946 (static_old.pos - objectpos).getLength() < save_movem)
1947 data_changed = false;
1949 warningstream << "ServerEnvironment::deactivateFarObjects(): "
1950 << "id=" << id << " m_static_exists=true but "
1951 << "static data doesn't actually exist in "
1952 << PP(obj->m_static_block) << std::endl;
1958 While changes are always saved, blocks are only marked as modified
1959 if the object has moved or different staticdata. (see above)
1961 bool shall_be_written = (!stays_in_same_block || data_changed);
1962 u32 reason = shall_be_written ? MOD_REASON_STATIC_DATA_CHANGED : MOD_REASON_UNKNOWN;
1964 // Delete old static object
1965 deleteStaticFromBlock(obj, id, reason, false);
1967 // Add to the block where the object is located in
1968 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
1969 u16 store_id = pending_delete ? id : 0;
1970 if (!saveStaticToBlock(blockpos, store_id, obj, s_obj, reason))
1971 force_delete = true;
1975 If known by some client, set pending deactivation.
1976 Otherwise delete it immediately.
1978 if (pending_delete && !force_delete) {
1979 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1980 << "object id=" << id << " is known by clients"
1981 << "; not deleting yet" << std::endl;
1983 obj->m_pending_deactivation = true;
1987 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1988 << "object id=" << id << " is not known by clients"
1989 << "; deleting" << std::endl;
1991 // Tell the object about removal
1992 obj->removingFromEnvironment();
1993 // Deregister in scripting api
1994 m_script->removeObjectReference(obj);
1996 // Delete active object
1997 if (obj->environmentDeletes())
2003 m_ao_manager.clear(cb_deactivate);
2006 void ServerEnvironment::deleteStaticFromBlock(
2007 ServerActiveObject *obj, u16 id, u32 mod_reason, bool no_emerge)
2009 if (!obj->m_static_exists)
2014 block = m_map->getBlockNoCreateNoEx(obj->m_static_block);
2016 block = m_map->emergeBlock(obj->m_static_block, false);
2019 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2020 << " when deleting static data of object from it. id=" << id << std::endl;
2024 block->m_static_objects.remove(id);
2025 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2026 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2028 obj->m_static_exists = false;
2031 bool ServerEnvironment::saveStaticToBlock(
2032 v3s16 blockpos, u16 store_id,
2033 ServerActiveObject *obj, const StaticObject &s_obj,
2036 MapBlock *block = nullptr;
2038 block = m_map->emergeBlock(blockpos);
2039 } catch (InvalidPositionException &e) {
2040 // Handled via NULL pointer
2041 // NOTE: emergeBlock's failure is usually determined by it
2042 // actually returning NULL
2046 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2047 << " when saving static data of object to it. id=" << store_id << std::endl;
2050 if (block->m_static_objects.m_stored.size() >= g_settings->getU16("max_objects_per_block")) {
2051 warningstream << "ServerEnv: Trying to store id = " << store_id
2052 << " statically but block " << PP(blockpos)
2053 << " already contains "
2054 << block->m_static_objects.m_stored.size()
2055 << " objects." << std::endl;
2059 block->m_static_objects.insert(store_id, s_obj);
2060 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2061 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2063 obj->m_static_exists = true;
2064 obj->m_static_block = blockpos;
2069 PlayerDatabase *ServerEnvironment::openPlayerDatabase(const std::string &name,
2070 const std::string &savedir, const Settings &conf)
2073 if (name == "sqlite3")
2074 return new PlayerDatabaseSQLite3(savedir);
2076 if (name == "dummy")
2077 return new Database_Dummy();
2079 if (name == "postgresql") {
2080 std::string connect_string;
2081 conf.getNoEx("pgsql_player_connection", connect_string);
2082 return new PlayerDatabasePostgreSQL(connect_string);
2085 if (name == "files")
2086 return new PlayerDatabaseFiles(savedir + DIR_DELIM + "players");
2088 throw BaseException(std::string("Database backend ") + name + " not supported.");
2091 bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params,
2092 const Settings &cmd_args)
2094 std::string migrate_to = cmd_args.get("migrate-players");
2096 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2097 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2098 errorstream << "Cannot read world.mt!" << std::endl;
2102 if (!world_mt.exists("player_backend")) {
2103 errorstream << "Please specify your current backend in world.mt:"
2105 << " player_backend = {files|sqlite3|postgresql}"
2110 std::string backend = world_mt.get("player_backend");
2111 if (backend == migrate_to) {
2112 errorstream << "Cannot migrate: new backend is same"
2113 << " as the old one" << std::endl;
2117 const std::string players_backup_path = game_params.world_path + DIR_DELIM
2120 if (backend == "files") {
2121 // Create backup directory
2122 fs::CreateDir(players_backup_path);
2126 PlayerDatabase *srcdb = ServerEnvironment::openPlayerDatabase(backend,
2127 game_params.world_path, world_mt);
2128 PlayerDatabase *dstdb = ServerEnvironment::openPlayerDatabase(migrate_to,
2129 game_params.world_path, world_mt);
2131 std::vector<std::string> player_list;
2132 srcdb->listPlayers(player_list);
2133 for (std::vector<std::string>::const_iterator it = player_list.begin();
2134 it != player_list.end(); ++it) {
2135 actionstream << "Migrating player " << it->c_str() << std::endl;
2136 RemotePlayer player(it->c_str(), NULL);
2137 PlayerSAO playerSAO(NULL, &player, 15000, false);
2139 srcdb->loadPlayer(&player, &playerSAO);
2141 playerSAO.finalize(&player, std::set<std::string>());
2142 player.setPlayerSAO(&playerSAO);
2144 dstdb->savePlayer(&player);
2146 // For files source, move player files to backup dir
2147 if (backend == "files") {
2149 game_params.world_path + DIR_DELIM + "players" + DIR_DELIM + (*it),
2150 players_backup_path + DIR_DELIM + (*it));
2154 actionstream << "Successfully migrated " << player_list.size() << " players"
2156 world_mt.set("player_backend", migrate_to);
2157 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2158 errorstream << "Failed to update world.mt!" << std::endl;
2160 actionstream << "world.mt updated" << std::endl;
2162 // When migration is finished from file backend, remove players directory if empty
2163 if (backend == "files") {
2164 fs::DeleteSingleFileOrEmptyDirectory(game_params.world_path + DIR_DELIM
2171 } catch (BaseException &e) {
2172 errorstream << "An error occurred during migration: " << e.what() << std::endl;
2178 AuthDatabase *ServerEnvironment::openAuthDatabase(
2179 const std::string &name, const std::string &savedir, const Settings &conf)
2181 if (name == "sqlite3")
2182 return new AuthDatabaseSQLite3(savedir);
2184 if (name == "files")
2185 return new AuthDatabaseFiles(savedir);
2187 throw BaseException(std::string("Database backend ") + name + " not supported.");
2190 bool ServerEnvironment::migrateAuthDatabase(
2191 const GameParams &game_params, const Settings &cmd_args)
2193 std::string migrate_to = cmd_args.get("migrate-auth");
2195 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2196 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2197 errorstream << "Cannot read world.mt!" << std::endl;
2201 std::string backend = "files";
2202 if (world_mt.exists("auth_backend"))
2203 backend = world_mt.get("auth_backend");
2205 warningstream << "No auth_backend found in world.mt, "
2206 "assuming \"files\"." << std::endl;
2208 if (backend == migrate_to) {
2209 errorstream << "Cannot migrate: new backend is same"
2210 << " as the old one" << std::endl;
2215 const std::unique_ptr<AuthDatabase> srcdb(ServerEnvironment::openAuthDatabase(
2216 backend, game_params.world_path, world_mt));
2217 const std::unique_ptr<AuthDatabase> dstdb(ServerEnvironment::openAuthDatabase(
2218 migrate_to, game_params.world_path, world_mt));
2220 std::vector<std::string> names_list;
2221 srcdb->listNames(names_list);
2222 for (const std::string &name : names_list) {
2223 actionstream << "Migrating auth entry for " << name << std::endl;
2225 AuthEntry authEntry;
2226 success = srcdb->getAuth(name, authEntry);
2227 success = success && dstdb->createAuth(authEntry);
2229 errorstream << "Failed to migrate " << name << std::endl;
2232 actionstream << "Successfully migrated " << names_list.size()
2233 << " auth entries" << std::endl;
2234 world_mt.set("auth_backend", migrate_to);
2235 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2236 errorstream << "Failed to update world.mt!" << std::endl;
2238 actionstream << "world.mt updated" << std::endl;
2240 if (backend == "files") {
2241 // special-case files migration:
2242 // move auth.txt to auth.txt.bak if possible
2243 std::string auth_txt_path =
2244 game_params.world_path + DIR_DELIM + "auth.txt";
2245 std::string auth_bak_path = auth_txt_path + ".bak";
2246 if (!fs::PathExists(auth_bak_path))
2247 if (fs::Rename(auth_txt_path, auth_bak_path))
2248 actionstream << "Renamed auth.txt to auth.txt.bak"
2251 errorstream << "Could not rename auth.txt to "
2252 "auth.txt.bak" << std::endl;
2254 warningstream << "auth.txt.bak already exists, auth.txt "
2255 "not renamed" << std::endl;
2258 } catch (BaseException &e) {
2259 errorstream << "An error occurred during migration: " << e.what()