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);
1222 if (m_database_check_interval.step(dtime, 10.0f)) {
1223 m_auth_database->pingDatabase();
1224 m_player_database->pingDatabase();
1225 m_map->pingDatabase();
1228 Manage active block list
1230 if (m_active_blocks_management_interval.step(dtime, m_cache_active_block_mgmt_interval)) {
1231 ScopeProfiler sp(g_profiler, "ServerEnv: update active blocks", SPT_AVG);
1233 Get player block positions
1235 std::vector<PlayerSAO*> players;
1236 for (RemotePlayer *player: m_players) {
1237 // Ignore disconnected players
1238 if (player->getPeerId() == PEER_ID_INEXISTENT)
1241 PlayerSAO *playersao = player->getPlayerSAO();
1244 players.push_back(playersao);
1248 Update list of active blocks, collecting changes
1250 // use active_object_send_range_blocks since that is max distance
1251 // for active objects sent the client anyway
1252 static thread_local const s16 active_object_range =
1253 g_settings->getS16("active_object_send_range_blocks");
1254 static thread_local const s16 active_block_range =
1255 g_settings->getS16("active_block_range");
1256 std::set<v3s16> blocks_removed;
1257 std::set<v3s16> blocks_added;
1258 m_active_blocks.update(players, active_block_range, active_object_range,
1259 blocks_removed, blocks_added);
1262 Handle removed blocks
1265 // Convert active objects that are no more in active blocks to static
1266 deactivateFarObjects(false);
1268 for (const v3s16 &p: blocks_removed) {
1269 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1273 // Set current time as timestamp (and let it set ChangedFlag)
1274 block->setTimestamp(m_game_time);
1281 for (const v3s16 &p: blocks_added) {
1282 MapBlock *block = m_map->getBlockOrEmerge(p);
1284 m_active_blocks.m_list.erase(p);
1285 m_active_blocks.m_abm_list.erase(p);
1289 activateBlock(block);
1294 Mess around in active blocks
1296 if (m_active_blocks_nodemetadata_interval.step(dtime, m_cache_nodetimer_interval)) {
1297 ScopeProfiler sp(g_profiler, "ServerEnv: Run node timers", SPT_AVG);
1299 float dtime = m_cache_nodetimer_interval;
1301 for (const v3s16 &p: m_active_blocks.m_list) {
1302 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1306 // Reset block usage timer
1307 block->resetUsageTimer();
1309 // Set current time as timestamp
1310 block->setTimestampNoChangedFlag(m_game_time);
1311 // If time has changed much from the one on disk,
1312 // set block to be saved when it is unloaded
1313 if(block->getTimestamp() > block->getDiskTimestamp() + 60)
1314 block->raiseModified(MOD_STATE_WRITE_AT_UNLOAD,
1315 MOD_REASON_BLOCK_EXPIRED);
1318 std::vector<NodeTimer> elapsed_timers = block->m_node_timers.step(dtime);
1319 if (!elapsed_timers.empty()) {
1322 for (const NodeTimer &elapsed_timer: elapsed_timers) {
1323 n = block->getNodeNoEx(elapsed_timer.position);
1324 p2 = elapsed_timer.position + block->getPosRelative();
1325 if (m_script->node_on_timer(p2, n, elapsed_timer.elapsed)) {
1326 block->setNodeTimer(NodeTimer(
1327 elapsed_timer.timeout, 0, elapsed_timer.position));
1334 if (m_active_block_modifier_interval.step(dtime, m_cache_abm_interval)) {
1335 ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg per interval", SPT_AVG);
1336 TimeTaker timer("modify in active blocks per interval");
1338 // Initialize handling of ActiveBlockModifiers
1339 ABMHandler abmhandler(m_abms, m_cache_abm_interval, this, true);
1341 int blocks_scanned = 0;
1343 int blocks_cached = 0;
1345 std::vector<v3s16> output(m_active_blocks.m_abm_list.size());
1347 // Shuffle the active blocks so that each block gets an equal chance
1348 // of having its ABMs run.
1349 std::copy(m_active_blocks.m_abm_list.begin(), m_active_blocks.m_abm_list.end(), output.begin());
1350 std::shuffle(output.begin(), output.end(), m_rgen);
1353 // The time budget for ABMs is 20%.
1354 u32 max_time_ms = m_cache_abm_interval * 1000 / 5;
1355 for (const v3s16 &p : output) {
1356 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1362 // Set current time as timestamp
1363 block->setTimestampNoChangedFlag(m_game_time);
1365 /* Handle ActiveBlockModifiers */
1366 abmhandler.apply(block, blocks_scanned, abms_run, blocks_cached);
1368 u32 time_ms = timer.getTimerTime();
1370 if (time_ms > max_time_ms) {
1371 warningstream << "active block modifiers took "
1372 << time_ms << "ms (processed " << i << " of "
1373 << output.size() << " active blocks)" << std::endl;
1377 g_profiler->avg("ServerEnv: active blocks", m_active_blocks.m_abm_list.size());
1378 g_profiler->avg("ServerEnv: active blocks cached", blocks_cached);
1379 g_profiler->avg("ServerEnv: active blocks scanned for ABMs", blocks_scanned);
1380 g_profiler->avg("ServerEnv: ABMs run", abms_run);
1386 Step script environment (run global on_step())
1388 m_script->environment_Step(dtime);
1394 ScopeProfiler sp(g_profiler, "ServerEnv: Run SAO::step()", SPT_AVG);
1396 // This helps the objects to send data at the same time
1397 bool send_recommended = false;
1398 m_send_recommended_timer += dtime;
1399 if (m_send_recommended_timer > getSendRecommendedInterval()) {
1400 m_send_recommended_timer -= getSendRecommendedInterval();
1401 send_recommended = true;
1404 auto cb_state = [this, dtime, send_recommended] (ServerActiveObject *obj) {
1409 obj->step(dtime, send_recommended);
1410 // Read messages from object
1411 obj->dumpAOMessagesToQueue(m_active_object_messages);
1413 m_ao_manager.step(dtime, cb_state);
1417 Manage active objects
1419 if (m_object_management_interval.step(dtime, 0.5)) {
1420 removeRemovedObjects();
1424 Manage particle spawner expiration
1426 if (m_particle_management_interval.step(dtime, 1.0)) {
1427 for (std::unordered_map<u32, float>::iterator i = m_particle_spawners.begin();
1428 i != m_particle_spawners.end(); ) {
1429 //non expiring spawners
1430 if (i->second == PARTICLE_SPAWNER_NO_EXPIRY) {
1436 if (i->second <= 0.f)
1437 m_particle_spawners.erase(i++);
1443 // Send outdated player inventories
1444 for (RemotePlayer *player : m_players) {
1445 if (player->getPeerId() == PEER_ID_INEXISTENT)
1448 PlayerSAO *sao = player->getPlayerSAO();
1449 if (sao && player->inventory.checkModified())
1450 m_server->SendInventory(sao, true);
1453 // Send outdated detached inventories
1454 m_server->sendDetachedInventories(PEER_ID_INEXISTENT, true);
1457 u32 ServerEnvironment::addParticleSpawner(float exptime)
1459 // Timers with lifetime 0 do not expire
1460 float time = exptime > 0.f ? exptime : PARTICLE_SPAWNER_NO_EXPIRY;
1463 for (;;) { // look for unused particlespawner id
1465 std::unordered_map<u32, float>::iterator f = m_particle_spawners.find(id);
1466 if (f == m_particle_spawners.end()) {
1467 m_particle_spawners[id] = time;
1474 u32 ServerEnvironment::addParticleSpawner(float exptime, u16 attached_id)
1476 u32 id = addParticleSpawner(exptime);
1477 m_particle_spawner_attachments[id] = attached_id;
1478 if (ServerActiveObject *obj = getActiveObject(attached_id)) {
1479 obj->attachParticleSpawner(id);
1484 void ServerEnvironment::deleteParticleSpawner(u32 id, bool remove_from_object)
1486 m_particle_spawners.erase(id);
1487 const auto &it = m_particle_spawner_attachments.find(id);
1488 if (it != m_particle_spawner_attachments.end()) {
1489 u16 obj_id = it->second;
1490 ServerActiveObject *sao = getActiveObject(obj_id);
1491 if (sao != NULL && remove_from_object) {
1492 sao->detachParticleSpawner(id);
1494 m_particle_spawner_attachments.erase(id);
1498 u16 ServerEnvironment::addActiveObject(ServerActiveObject *object)
1500 assert(object); // Pre-condition
1502 u16 id = addActiveObjectRaw(object, true, 0);
1507 Finds out what new objects have been added to
1508 inside a radius around a position
1510 void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius,
1512 std::set<u16> ¤t_objects,
1513 std::queue<u16> &added_objects)
1515 f32 radius_f = radius * BS;
1516 f32 player_radius_f = player_radius * BS;
1518 if (player_radius_f < 0.0f)
1519 player_radius_f = 0.0f;
1521 m_ao_manager.getAddedActiveObjectsAroundPos(playersao->getBasePosition(), radius_f,
1522 player_radius_f, current_objects, added_objects);
1526 Finds out what objects have been removed from
1527 inside a radius around a position
1529 void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius,
1531 std::set<u16> ¤t_objects,
1532 std::queue<u16> &removed_objects)
1534 f32 radius_f = radius * BS;
1535 f32 player_radius_f = player_radius * BS;
1537 if (player_radius_f < 0)
1538 player_radius_f = 0;
1540 Go through current_objects; object is removed if:
1541 - object is not found in m_active_objects (this is actually an
1542 error condition; objects should be removed only after all clients
1543 have been informed about removal), or
1544 - object is to be removed or deactivated, or
1545 - object is too far away
1547 for (u16 id : current_objects) {
1548 ServerActiveObject *object = getActiveObject(id);
1550 if (object == NULL) {
1551 infostream << "ServerEnvironment::getRemovedActiveObjects():"
1552 << " object in current_objects is NULL" << std::endl;
1553 removed_objects.push(id);
1557 if (object->isGone()) {
1558 removed_objects.push(id);
1562 f32 distance_f = object->getBasePosition().getDistanceFrom(playersao->getBasePosition());
1563 if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1564 if (distance_f <= player_radius_f || player_radius_f == 0)
1566 } else if (distance_f <= radius_f)
1569 // Object is no longer visible
1570 removed_objects.push(id);
1574 void ServerEnvironment::setStaticForActiveObjectsInBlock(
1575 v3s16 blockpos, bool static_exists, v3s16 static_block)
1577 MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos);
1581 for (auto &so_it : block->m_static_objects.m_active) {
1582 // Get the ServerActiveObject counterpart to this StaticObject
1583 ServerActiveObject *sao = m_ao_manager.getActiveObject(so_it.first);
1585 // If this ever happens, there must be some kind of nasty bug.
1586 errorstream << "ServerEnvironment::setStaticForObjectsInBlock(): "
1587 "Object from MapBlock::m_static_objects::m_active not found "
1588 "in m_active_objects";
1592 sao->m_static_exists = static_exists;
1593 sao->m_static_block = static_block;
1597 ActiveObjectMessage ServerEnvironment::getActiveObjectMessage()
1599 if(m_active_object_messages.empty())
1600 return ActiveObjectMessage(0);
1602 ActiveObjectMessage message = m_active_object_messages.front();
1603 m_active_object_messages.pop();
1607 void ServerEnvironment::getSelectedActiveObjects(
1608 const core::line3d<f32> &shootline_on_map,
1609 std::vector<PointedThing> &objects)
1611 std::vector<u16> objectIds;
1612 getObjectsInsideRadius(objectIds, shootline_on_map.start,
1613 shootline_on_map.getLength() + 10.0f);
1614 const v3f line_vector = shootline_on_map.getVector();
1616 for (u16 objectId : objectIds) {
1617 ServerActiveObject* obj = getActiveObject(objectId);
1619 aabb3f selection_box;
1620 if (!obj->getSelectionBox(&selection_box))
1623 v3f pos = obj->getBasePosition();
1625 aabb3f offsetted_box(selection_box.MinEdge + pos,
1626 selection_box.MaxEdge + pos);
1628 v3f current_intersection;
1629 v3s16 current_normal;
1630 if (boxLineCollision(offsetted_box, shootline_on_map.start, line_vector,
1631 ¤t_intersection, ¤t_normal)) {
1632 objects.emplace_back(
1633 (s16) objectId, current_intersection, current_normal,
1634 (current_intersection - shootline_on_map.start).getLengthSQ());
1640 ************ Private methods *************
1643 u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object,
1644 bool set_changed, u32 dtime_s)
1646 if (!m_ao_manager.registerObject(object)) {
1650 // Register reference in scripting api (must be done before post-init)
1651 m_script->addObjectReference(object);
1652 // Post-initialize object
1653 object->addedToEnvironment(dtime_s);
1655 // Add static data to block
1656 if (object->isStaticAllowed()) {
1657 // Add static object to active static list of the block
1658 v3f objectpos = object->getBasePosition();
1659 StaticObject s_obj(object, objectpos);
1660 // Add to the block where the object is located in
1661 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
1662 MapBlock *block = m_map->emergeBlock(blockpos);
1664 block->m_static_objects.m_active[object->getId()] = s_obj;
1665 object->m_static_exists = true;
1666 object->m_static_block = blockpos;
1669 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1670 MOD_REASON_ADD_ACTIVE_OBJECT_RAW);
1672 v3s16 p = floatToInt(objectpos, BS);
1673 errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
1674 <<"could not emerge block for storing id="<<object->getId()
1675 <<" statically (pos="<<PP(p)<<")"<<std::endl;
1679 return object->getId();
1683 Remove objects that satisfy (isGone() && m_known_by_count==0)
1685 void ServerEnvironment::removeRemovedObjects()
1687 ScopeProfiler sp(g_profiler, "ServerEnvironment::removeRemovedObjects()", SPT_AVG);
1689 auto clear_cb = [this] (ServerActiveObject *obj, u16 id) {
1690 // This shouldn't happen but check it
1692 errorstream << "ServerEnvironment::removeRemovedObjects(): "
1693 << "NULL object found. id=" << id << std::endl;
1698 We will handle objects marked for removal or deactivation
1704 Delete static data from block if removed
1706 if (obj->m_pending_removal)
1707 deleteStaticFromBlock(obj, id, MOD_REASON_REMOVE_OBJECTS_REMOVE, false);
1709 // If still known by clients, don't actually remove. On some future
1710 // invocation this will be 0, which is when removal will continue.
1711 if(obj->m_known_by_count > 0)
1715 Move static data from active to stored if deactivated
1717 if (!obj->m_pending_removal && obj->m_static_exists) {
1718 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
1720 const auto i = block->m_static_objects.m_active.find(id);
1721 if (i != block->m_static_objects.m_active.end()) {
1722 block->m_static_objects.m_stored.push_back(i->second);
1723 block->m_static_objects.m_active.erase(id);
1724 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1725 MOD_REASON_REMOVE_OBJECTS_DEACTIVATE);
1727 warningstream << "ServerEnvironment::removeRemovedObjects(): "
1728 << "id=" << id << " m_static_exists=true but "
1729 << "static data doesn't actually exist in "
1730 << PP(obj->m_static_block) << std::endl;
1733 infostream << "Failed to emerge block from which an object to "
1734 << "be deactivated was loaded from. id=" << id << std::endl;
1738 // Tell the object about removal
1739 obj->removingFromEnvironment();
1740 // Deregister in scripting api
1741 m_script->removeObjectReference(obj);
1744 if (obj->environmentDeletes())
1750 m_ao_manager.clear(clear_cb);
1753 static void print_hexdump(std::ostream &o, const std::string &data)
1755 const int linelength = 16;
1756 for(int l=0; ; l++){
1757 int i0 = linelength * l;
1758 bool at_end = false;
1759 int thislinelength = linelength;
1760 if(i0 + thislinelength > (int)data.size()){
1761 thislinelength = data.size() - i0;
1764 for(int di=0; di<linelength; di++){
1767 if(di<thislinelength)
1768 porting::mt_snprintf(buf, sizeof(buf), "%.2x ", data[i]);
1770 porting::mt_snprintf(buf, sizeof(buf), " ");
1774 for(int di=0; di<thislinelength; di++){
1787 ServerActiveObject* ServerEnvironment::createSAO(ActiveObjectType type, v3f pos,
1788 const std::string &data)
1791 case ACTIVEOBJECT_TYPE_LUAENTITY:
1792 return new LuaEntitySAO(this, pos, data);
1794 warningstream << "ServerActiveObject: No factory for type=" << type << std::endl;
1800 Convert stored objects from blocks near the players to active.
1802 void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s)
1807 // Ignore if no stored objects (to not set changed flag)
1808 if(block->m_static_objects.m_stored.empty())
1811 verbosestream<<"ServerEnvironment::activateObjects(): "
1812 <<"activating objects of block "<<PP(block->getPos())
1813 <<" ("<<block->m_static_objects.m_stored.size()
1814 <<" objects)"<<std::endl;
1815 bool large_amount = (block->m_static_objects.m_stored.size() > g_settings->getU16("max_objects_per_block"));
1817 errorstream<<"suspiciously large amount of objects detected: "
1818 <<block->m_static_objects.m_stored.size()<<" in "
1819 <<PP(block->getPos())
1820 <<"; removing all of them."<<std::endl;
1821 // Clear stored list
1822 block->m_static_objects.m_stored.clear();
1823 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1824 MOD_REASON_TOO_MANY_OBJECTS);
1828 // Activate stored objects
1829 std::vector<StaticObject> new_stored;
1830 for (const StaticObject &s_obj : block->m_static_objects.m_stored) {
1831 // Create an active object from the data
1832 ServerActiveObject *obj = createSAO((ActiveObjectType) s_obj.type, s_obj.pos,
1834 // If couldn't create object, store static data back.
1836 errorstream<<"ServerEnvironment::activateObjects(): "
1837 <<"failed to create active object from static object "
1838 <<"in block "<<PP(s_obj.pos/BS)
1839 <<" type="<<(int)s_obj.type<<" data:"<<std::endl;
1840 print_hexdump(verbosestream, s_obj.data);
1842 new_stored.push_back(s_obj);
1845 verbosestream<<"ServerEnvironment::activateObjects(): "
1846 <<"activated static object pos="<<PP(s_obj.pos/BS)
1847 <<" type="<<(int)s_obj.type<<std::endl;
1848 // This will also add the object to the active static list
1849 addActiveObjectRaw(obj, false, dtime_s);
1852 // Clear stored list
1853 block->m_static_objects.m_stored.clear();
1854 // Add leftover failed stuff to stored list
1855 for (const StaticObject &s_obj : new_stored) {
1856 block->m_static_objects.m_stored.push_back(s_obj);
1860 Note: Block hasn't really been modified here.
1861 The objects have just been activated and moved from the stored
1862 static list to the active static list.
1863 As such, the block is essentially the same.
1864 Thus, do not call block->raiseModified(MOD_STATE_WRITE_NEEDED).
1865 Otherwise there would be a huge amount of unnecessary I/O.
1870 Convert objects that are not standing inside active blocks to static.
1872 If m_known_by_count != 0, active object is not deleted, but static
1873 data is still updated.
1875 If force_delete is set, active object is deleted nevertheless. It
1876 shall only be set so in the destructor of the environment.
1878 If block wasn't generated (not in memory or on disk),
1880 void ServerEnvironment::deactivateFarObjects(bool _force_delete)
1882 auto cb_deactivate = [this, _force_delete] (ServerActiveObject *obj, u16 id) {
1883 // force_delete might be overriden per object
1884 bool force_delete = _force_delete;
1886 // Do not deactivate if static data creation not allowed
1887 if (!force_delete && !obj->isStaticAllowed())
1890 // removeRemovedObjects() is responsible for these
1891 if (!force_delete && obj->isGone())
1894 const v3f &objectpos = obj->getBasePosition();
1896 // The block in which the object resides in
1897 v3s16 blockpos_o = getNodeBlockPos(floatToInt(objectpos, BS));
1899 // If object's static data is stored in a deactivated block and object
1900 // is actually located in an active block, re-save to the block in
1901 // which the object is actually located in.
1902 if (!force_delete && obj->m_static_exists &&
1903 !m_active_blocks.contains(obj->m_static_block) &&
1904 m_active_blocks.contains(blockpos_o)) {
1905 // Delete from block where object was located
1906 deleteStaticFromBlock(obj, id, MOD_REASON_STATIC_DATA_REMOVED, false);
1908 StaticObject s_obj(obj, objectpos);
1909 // Save to block where object is located
1910 saveStaticToBlock(blockpos_o, id, obj, s_obj, MOD_REASON_STATIC_DATA_ADDED);
1915 // If block is still active, don't remove
1916 if (!force_delete && m_active_blocks.contains(blockpos_o))
1919 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1920 << "deactivating object id=" << id << " on inactive block "
1921 << PP(blockpos_o) << std::endl;
1923 // If known by some client, don't immediately delete.
1924 bool pending_delete = (obj->m_known_by_count > 0 && !force_delete);
1927 Update the static data
1929 if (obj->isStaticAllowed()) {
1930 // Create new static object
1931 StaticObject s_obj(obj, objectpos);
1933 bool stays_in_same_block = false;
1934 bool data_changed = true;
1936 // Check if static data has changed considerably
1937 if (obj->m_static_exists) {
1938 if (obj->m_static_block == blockpos_o)
1939 stays_in_same_block = true;
1941 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
1944 const auto n = block->m_static_objects.m_active.find(id);
1945 if (n != block->m_static_objects.m_active.end()) {
1946 StaticObject static_old = n->second;
1948 float save_movem = obj->getMinimumSavedMovement();
1950 if (static_old.data == s_obj.data &&
1951 (static_old.pos - objectpos).getLength() < save_movem)
1952 data_changed = false;
1954 warningstream << "ServerEnvironment::deactivateFarObjects(): "
1955 << "id=" << id << " m_static_exists=true but "
1956 << "static data doesn't actually exist in "
1957 << PP(obj->m_static_block) << std::endl;
1963 While changes are always saved, blocks are only marked as modified
1964 if the object has moved or different staticdata. (see above)
1966 bool shall_be_written = (!stays_in_same_block || data_changed);
1967 u32 reason = shall_be_written ? MOD_REASON_STATIC_DATA_CHANGED : MOD_REASON_UNKNOWN;
1969 // Delete old static object
1970 deleteStaticFromBlock(obj, id, reason, false);
1972 // Add to the block where the object is located in
1973 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
1974 u16 store_id = pending_delete ? id : 0;
1975 if (!saveStaticToBlock(blockpos, store_id, obj, s_obj, reason))
1976 force_delete = true;
1980 If known by some client, set pending deactivation.
1981 Otherwise delete it immediately.
1983 if (pending_delete && !force_delete) {
1984 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1985 << "object id=" << id << " is known by clients"
1986 << "; not deleting yet" << std::endl;
1988 obj->m_pending_deactivation = true;
1992 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1993 << "object id=" << id << " is not known by clients"
1994 << "; deleting" << std::endl;
1996 // Tell the object about removal
1997 obj->removingFromEnvironment();
1998 // Deregister in scripting api
1999 m_script->removeObjectReference(obj);
2001 // Delete active object
2002 if (obj->environmentDeletes())
2008 m_ao_manager.clear(cb_deactivate);
2011 void ServerEnvironment::deleteStaticFromBlock(
2012 ServerActiveObject *obj, u16 id, u32 mod_reason, bool no_emerge)
2014 if (!obj->m_static_exists)
2019 block = m_map->getBlockNoCreateNoEx(obj->m_static_block);
2021 block = m_map->emergeBlock(obj->m_static_block, false);
2024 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2025 << " when deleting static data of object from it. id=" << id << std::endl;
2029 block->m_static_objects.remove(id);
2030 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2031 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2033 obj->m_static_exists = false;
2036 bool ServerEnvironment::saveStaticToBlock(
2037 v3s16 blockpos, u16 store_id,
2038 ServerActiveObject *obj, const StaticObject &s_obj,
2041 MapBlock *block = nullptr;
2043 block = m_map->emergeBlock(blockpos);
2044 } catch (InvalidPositionException &e) {
2045 // Handled via NULL pointer
2046 // NOTE: emergeBlock's failure is usually determined by it
2047 // actually returning NULL
2051 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2052 << " when saving static data of object to it. id=" << store_id << std::endl;
2055 if (block->m_static_objects.m_stored.size() >= g_settings->getU16("max_objects_per_block")) {
2056 warningstream << "ServerEnv: Trying to store id = " << store_id
2057 << " statically but block " << PP(blockpos)
2058 << " already contains "
2059 << block->m_static_objects.m_stored.size()
2060 << " objects." << std::endl;
2064 block->m_static_objects.insert(store_id, s_obj);
2065 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2066 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2068 obj->m_static_exists = true;
2069 obj->m_static_block = blockpos;
2074 PlayerDatabase *ServerEnvironment::openPlayerDatabase(const std::string &name,
2075 const std::string &savedir, const Settings &conf)
2078 if (name == "sqlite3")
2079 return new PlayerDatabaseSQLite3(savedir);
2081 if (name == "dummy")
2082 return new Database_Dummy();
2084 if (name == "postgresql") {
2085 std::string connect_string;
2086 conf.getNoEx("pgsql_player_connection", connect_string);
2087 return new PlayerDatabasePostgreSQL(connect_string);
2090 if (name == "files")
2091 return new PlayerDatabaseFiles(savedir + DIR_DELIM + "players");
2093 throw BaseException(std::string("Database backend ") + name + " not supported.");
2096 bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params,
2097 const Settings &cmd_args)
2099 std::string migrate_to = cmd_args.get("migrate-players");
2101 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2102 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2103 errorstream << "Cannot read world.mt!" << std::endl;
2107 if (!world_mt.exists("player_backend")) {
2108 errorstream << "Please specify your current backend in world.mt:"
2110 << " player_backend = {files|sqlite3|postgresql}"
2115 std::string backend = world_mt.get("player_backend");
2116 if (backend == migrate_to) {
2117 errorstream << "Cannot migrate: new backend is same"
2118 << " as the old one" << std::endl;
2122 const std::string players_backup_path = game_params.world_path + DIR_DELIM
2125 if (backend == "files") {
2126 // Create backup directory
2127 fs::CreateDir(players_backup_path);
2131 PlayerDatabase *srcdb = ServerEnvironment::openPlayerDatabase(backend,
2132 game_params.world_path, world_mt);
2133 PlayerDatabase *dstdb = ServerEnvironment::openPlayerDatabase(migrate_to,
2134 game_params.world_path, world_mt);
2136 std::vector<std::string> player_list;
2137 srcdb->listPlayers(player_list);
2138 for (std::vector<std::string>::const_iterator it = player_list.begin();
2139 it != player_list.end(); ++it) {
2140 actionstream << "Migrating player " << it->c_str() << std::endl;
2141 RemotePlayer player(it->c_str(), NULL);
2142 PlayerSAO playerSAO(NULL, &player, 15000, false);
2144 srcdb->loadPlayer(&player, &playerSAO);
2146 playerSAO.finalize(&player, std::set<std::string>());
2147 player.setPlayerSAO(&playerSAO);
2149 dstdb->savePlayer(&player);
2151 // For files source, move player files to backup dir
2152 if (backend == "files") {
2154 game_params.world_path + DIR_DELIM + "players" + DIR_DELIM + (*it),
2155 players_backup_path + DIR_DELIM + (*it));
2159 actionstream << "Successfully migrated " << player_list.size() << " players"
2161 world_mt.set("player_backend", migrate_to);
2162 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2163 errorstream << "Failed to update world.mt!" << std::endl;
2165 actionstream << "world.mt updated" << std::endl;
2167 // When migration is finished from file backend, remove players directory if empty
2168 if (backend == "files") {
2169 fs::DeleteSingleFileOrEmptyDirectory(game_params.world_path + DIR_DELIM
2176 } catch (BaseException &e) {
2177 errorstream << "An error occurred during migration: " << e.what() << std::endl;
2183 AuthDatabase *ServerEnvironment::openAuthDatabase(
2184 const std::string &name, const std::string &savedir, const Settings &conf)
2186 if (name == "sqlite3")
2187 return new AuthDatabaseSQLite3(savedir);
2189 if (name == "files")
2190 return new AuthDatabaseFiles(savedir);
2192 throw BaseException(std::string("Database backend ") + name + " not supported.");
2195 bool ServerEnvironment::migrateAuthDatabase(
2196 const GameParams &game_params, const Settings &cmd_args)
2198 std::string migrate_to = cmd_args.get("migrate-auth");
2200 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2201 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2202 errorstream << "Cannot read world.mt!" << std::endl;
2206 std::string backend = "files";
2207 if (world_mt.exists("auth_backend"))
2208 backend = world_mt.get("auth_backend");
2210 warningstream << "No auth_backend found in world.mt, "
2211 "assuming \"files\"." << std::endl;
2213 if (backend == migrate_to) {
2214 errorstream << "Cannot migrate: new backend is same"
2215 << " as the old one" << std::endl;
2220 const std::unique_ptr<AuthDatabase> srcdb(ServerEnvironment::openAuthDatabase(
2221 backend, game_params.world_path, world_mt));
2222 const std::unique_ptr<AuthDatabase> dstdb(ServerEnvironment::openAuthDatabase(
2223 migrate_to, game_params.world_path, world_mt));
2225 std::vector<std::string> names_list;
2226 srcdb->listNames(names_list);
2227 for (const std::string &name : names_list) {
2228 actionstream << "Migrating auth entry for " << name << std::endl;
2230 AuthEntry authEntry;
2231 success = srcdb->getAuth(name, authEntry);
2232 success = success && dstdb->createAuth(authEntry);
2234 errorstream << "Failed to migrate " << name << std::endl;
2237 actionstream << "Successfully migrated " << names_list.size()
2238 << " auth entries" << std::endl;
2239 world_mt.set("auth_backend", migrate_to);
2240 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2241 errorstream << "Failed to update world.mt!" << std::endl;
2243 actionstream << "world.mt updated" << std::endl;
2245 if (backend == "files") {
2246 // special-case files migration:
2247 // move auth.txt to auth.txt.bak if possible
2248 std::string auth_txt_path =
2249 game_params.world_path + DIR_DELIM + "auth.txt";
2250 std::string auth_bak_path = auth_txt_path + ".bak";
2251 if (!fs::PathExists(auth_bak_path))
2252 if (fs::Rename(auth_txt_path, auth_bak_path))
2253 actionstream << "Renamed auth.txt to auth.txt.bak"
2256 errorstream << "Could not rename auth.txt to "
2257 "auth.txt.bak" << std::endl;
2259 warningstream << "auth.txt.bak already exists, auth.txt "
2260 "not renamed" << std::endl;
2263 } catch (BaseException &e) {
2264 errorstream << "An error occurred during migration: " << e.what()