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"
48 #include "database/database-leveldb.h"
50 #include "server/luaentity_sao.h"
51 #include "server/player_sao.h"
53 #define LBM_NAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_:"
55 // A number that is much smaller than the timeout for particle spawners should/could ever be
56 #define PARTICLE_SPAWNER_NO_EXPIRY -1024.f
62 ABMWithState::ABMWithState(ActiveBlockModifier *abm_):
65 // Initialize timer to random value to spread processing
66 float itv = abm->getTriggerInterval();
67 itv = MYMAX(0.001, itv); // No less than 1ms
68 int minval = MYMAX(-0.51*itv, -60); // Clamp to
69 int maxval = MYMIN(0.51*itv, 60); // +-60 seconds
70 timer = myrand_range(minval, maxval);
77 void LBMContentMapping::deleteContents()
79 for (auto &it : lbm_list) {
84 void LBMContentMapping::addLBM(LoadingBlockModifierDef *lbm_def, IGameDef *gamedef)
86 // Add the lbm_def to the LBMContentMapping.
87 // Unknown names get added to the global NameIdMapping.
88 const NodeDefManager *nodedef = gamedef->ndef();
90 lbm_list.push_back(lbm_def);
92 for (const std::string &nodeTrigger: lbm_def->trigger_contents) {
93 std::vector<content_t> c_ids;
94 bool found = nodedef->getIds(nodeTrigger, c_ids);
96 content_t c_id = gamedef->allocateUnknownNodeId(nodeTrigger);
97 if (c_id == CONTENT_IGNORE) {
98 // Seems it can't be allocated.
99 warningstream << "Could not internalize node name \"" << nodeTrigger
100 << "\" while loading LBM \"" << lbm_def->name << "\"." << std::endl;
103 c_ids.push_back(c_id);
106 for (content_t c_id : c_ids) {
107 map[c_id].push_back(lbm_def);
112 const std::vector<LoadingBlockModifierDef *> *
113 LBMContentMapping::lookup(content_t c) const
115 lbm_map::const_iterator it = map.find(c);
118 // This first dereferences the iterator, returning
119 // a std::vector<LoadingBlockModifierDef *>
120 // reference, then we convert it to a pointer.
121 return &(it->second);
124 LBMManager::~LBMManager()
126 for (auto &m_lbm_def : m_lbm_defs) {
127 delete m_lbm_def.second;
130 for (auto &it : m_lbm_lookup) {
131 (it.second).deleteContents();
135 void LBMManager::addLBMDef(LoadingBlockModifierDef *lbm_def)
137 // Precondition, in query mode the map isn't used anymore
138 FATAL_ERROR_IF(m_query_mode,
139 "attempted to modify LBMManager in query mode");
141 if (!string_allowed(lbm_def->name, LBM_NAME_ALLOWED_CHARS)) {
142 throw ModError("Error adding LBM \"" + lbm_def->name +
143 "\": Does not follow naming conventions: "
144 "Only characters [a-z0-9_:] are allowed.");
147 m_lbm_defs[lbm_def->name] = lbm_def;
150 void LBMManager::loadIntroductionTimes(const std::string ×,
151 IGameDef *gamedef, u32 now)
156 // Storing it in a map first instead of
157 // handling the stuff directly in the loop
158 // removes all duplicate entries.
159 // TODO make this std::unordered_map
160 std::map<std::string, u32> introduction_times;
163 The introduction times string consists of name~time entries,
164 with each entry terminated by a semicolon. The time is decimal.
169 while ((idx_new = times.find(';', idx)) != std::string::npos) {
170 std::string entry = times.substr(idx, idx_new - idx);
171 std::vector<std::string> components = str_split(entry, '~');
172 if (components.size() != 2)
173 throw SerializationError("Introduction times entry \""
174 + entry + "\" requires exactly one '~'!");
175 const std::string &name = components[0];
176 u32 time = from_string<u32>(components[1]);
177 introduction_times[name] = time;
181 // Put stuff from introduction_times into m_lbm_lookup
182 for (std::map<std::string, u32>::const_iterator it = introduction_times.begin();
183 it != introduction_times.end(); ++it) {
184 const std::string &name = it->first;
185 u32 time = it->second;
187 std::map<std::string, LoadingBlockModifierDef *>::iterator def_it =
188 m_lbm_defs.find(name);
189 if (def_it == m_lbm_defs.end()) {
190 // This seems to be an LBM entry for
191 // an LBM we haven't loaded. Discard it.
194 LoadingBlockModifierDef *lbm_def = def_it->second;
195 if (lbm_def->run_at_every_load) {
196 // This seems to be an LBM entry for
197 // an LBM that runs at every load.
198 // Don't add it just yet.
202 m_lbm_lookup[time].addLBM(lbm_def, gamedef);
204 // Erase the entry so that we know later
205 // what elements didn't get put into m_lbm_lookup
206 m_lbm_defs.erase(name);
209 // Now also add the elements from m_lbm_defs to m_lbm_lookup
210 // that weren't added in the previous step.
211 // They are introduced first time to this world,
212 // or are run at every load (introducement time hardcoded to U32_MAX).
214 LBMContentMapping &lbms_we_introduce_now = m_lbm_lookup[now];
215 LBMContentMapping &lbms_running_always = m_lbm_lookup[U32_MAX];
217 for (auto &m_lbm_def : m_lbm_defs) {
218 if (m_lbm_def.second->run_at_every_load) {
219 lbms_running_always.addLBM(m_lbm_def.second, gamedef);
221 lbms_we_introduce_now.addLBM(m_lbm_def.second, gamedef);
225 // Clear the list, so that we don't delete remaining elements
226 // twice in the destructor
230 std::string LBMManager::createIntroductionTimesString()
232 // Precondition, we must be in query mode
233 FATAL_ERROR_IF(!m_query_mode,
234 "attempted to query on non fully set up LBMManager");
236 std::ostringstream oss;
237 for (const auto &it : m_lbm_lookup) {
239 const std::vector<LoadingBlockModifierDef *> &lbm_list = it.second.lbm_list;
240 for (const auto &lbm_def : lbm_list) {
241 // Don't add if the LBM runs at every load,
242 // then introducement time is hardcoded
243 // and doesn't need to be stored
244 if (lbm_def->run_at_every_load)
246 oss << lbm_def->name << "~" << time << ";";
252 void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, u32 stamp)
254 // Precondition, we need m_lbm_lookup to be initialized
255 FATAL_ERROR_IF(!m_query_mode,
256 "attempted to query on non fully set up LBMManager");
257 v3s16 pos_of_block = block->getPosRelative();
261 bool pos_valid; // dummy, we know it's valid
262 auto it = getLBMsIntroducedAfter(stamp);
263 for (; it != m_lbm_lookup.end(); ++it) {
264 // Cache previous version to speedup lookup which has a very high performance
265 // penalty on each call
266 content_t previous_c = CONTENT_IGNORE;
267 const std::vector<LoadingBlockModifierDef *> *lbm_list = nullptr;
269 for (pos.X = 0; pos.X < MAP_BLOCKSIZE; pos.X++)
270 for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++)
271 for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++) {
272 n = block->getNodeNoCheck(pos, &pos_valid);
275 // If content_t are not matching perform an LBM lookup
276 if (previous_c != c) {
277 lbm_list = it->second.lookup(c);
283 for (auto lbmdef : *lbm_list) {
284 lbmdef->trigger(env, pos + pos_of_block, n);
294 void fillRadiusBlock(v3s16 p0, s16 r, std::set<v3s16> &list)
297 for(p.X=p0.X-r; p.X<=p0.X+r; p.X++)
298 for(p.Y=p0.Y-r; p.Y<=p0.Y+r; p.Y++)
299 for(p.Z=p0.Z-r; p.Z<=p0.Z+r; p.Z++)
302 if (p.getDistanceFrom(p0) <= r) {
309 void fillViewConeBlock(v3s16 p0,
311 const v3f camera_pos,
312 const v3f camera_dir,
313 const float camera_fov,
314 std::set<v3s16> &list)
317 const s16 r_nodes = r * BS * MAP_BLOCKSIZE;
318 for (p.X = p0.X - r; p.X <= p0.X+r; p.X++)
319 for (p.Y = p0.Y - r; p.Y <= p0.Y+r; p.Y++)
320 for (p.Z = p0.Z - r; p.Z <= p0.Z+r; p.Z++) {
321 if (isBlockInSight(p, camera_pos, camera_dir, camera_fov, r_nodes)) {
327 void ActiveBlockList::update(std::vector<PlayerSAO*> &active_players,
328 s16 active_block_range,
329 s16 active_object_range,
330 std::set<v3s16> &blocks_removed,
331 std::set<v3s16> &blocks_added)
336 std::set<v3s16> newlist = m_forceloaded_list;
337 m_abm_list = m_forceloaded_list;
338 for (const PlayerSAO *playersao : active_players) {
339 v3s16 pos = getNodeBlockPos(floatToInt(playersao->getBasePosition(), BS));
340 fillRadiusBlock(pos, active_block_range, m_abm_list);
341 fillRadiusBlock(pos, active_block_range, newlist);
343 s16 player_ao_range = std::min(active_object_range, playersao->getWantedRange());
344 // only do this if this would add blocks
345 if (player_ao_range > active_block_range) {
346 v3f camera_dir = v3f(0,0,1);
347 camera_dir.rotateYZBy(playersao->getLookPitch());
348 camera_dir.rotateXZBy(playersao->getRotation().Y);
349 fillViewConeBlock(pos,
351 playersao->getEyePosition(),
359 Find out which blocks on the old list are not on the new list
361 // Go through old list
362 for (v3s16 p : m_list) {
363 // If not on new list, it's been removed
364 if (newlist.find(p) == newlist.end())
365 blocks_removed.insert(p);
369 Find out which blocks on the new list are not on the old list
371 // Go through new list
372 for (v3s16 p : newlist) {
373 // If not on old list, it's been added
374 if(m_list.find(p) == m_list.end())
375 blocks_added.insert(p);
381 m_list = std::move(newlist);
388 // Random device to seed pseudo random generators.
389 static std::random_device seed;
391 ServerEnvironment::ServerEnvironment(ServerMap *map,
392 ServerScripting *script_iface, Server *server,
393 const std::string &path_world, MetricsBackend *mb):
396 m_script(script_iface),
398 m_path_world(path_world),
401 m_step_time_counter = mb->addCounter(
402 "minetest_env_step_time", "Time spent in environment step (in microseconds)");
404 m_active_block_gauge = mb->addGauge(
405 "minetest_env_active_blocks", "Number of active blocks");
407 m_active_object_gauge = mb->addGauge(
408 "minetest_env_active_objects", "Number of active objects");
411 void ServerEnvironment::init()
413 // Determine which database backend to use
414 std::string conf_path = m_path_world + DIR_DELIM + "world.mt";
417 std::string player_backend_name = "sqlite3";
418 std::string auth_backend_name = "sqlite3";
420 bool succeeded = conf.readConfigFile(conf_path.c_str());
422 // If we open world.mt read the backend configurations.
424 // Read those values before setting defaults
425 bool player_backend_exists = conf.exists("player_backend");
426 bool auth_backend_exists = conf.exists("auth_backend");
428 // player backend is not set, assume it's legacy file backend.
429 if (!player_backend_exists) {
430 // fall back to files
431 conf.set("player_backend", "files");
432 player_backend_name = "files";
434 if (!conf.updateConfigFile(conf_path.c_str())) {
435 errorstream << "ServerEnvironment::ServerEnvironment(): "
436 << "Failed to update world.mt!" << std::endl;
439 conf.getNoEx("player_backend", player_backend_name);
442 // auth backend is not set, assume it's legacy file backend.
443 if (!auth_backend_exists) {
444 conf.set("auth_backend", "files");
445 auth_backend_name = "files";
447 if (!conf.updateConfigFile(conf_path.c_str())) {
448 errorstream << "ServerEnvironment::ServerEnvironment(): "
449 << "Failed to update world.mt!" << std::endl;
452 conf.getNoEx("auth_backend", auth_backend_name);
456 if (player_backend_name == "files") {
457 warningstream << "/!\\ You are using old player file backend. "
458 << "This backend is deprecated and will be removed in a future release /!\\"
459 << std::endl << "Switching to SQLite3 or PostgreSQL is advised, "
460 << "please read http://wiki.minetest.net/Database_backends." << std::endl;
463 if (auth_backend_name == "files") {
464 warningstream << "/!\\ You are using old auth file backend. "
465 << "This backend is deprecated and will be removed in a future release /!\\"
466 << std::endl << "Switching to SQLite3 is advised, "
467 << "please read http://wiki.minetest.net/Database_backends." << std::endl;
470 m_player_database = openPlayerDatabase(player_backend_name, m_path_world, conf);
471 m_auth_database = openAuthDatabase(auth_backend_name, m_path_world, conf);
474 ServerEnvironment::~ServerEnvironment()
476 // Clear active block list.
477 // This makes the next one delete all active objects.
478 m_active_blocks.clear();
480 // Convert all objects to static and delete the active objects
481 deactivateFarObjects(true);
487 // Delete ActiveBlockModifiers
488 for (ABMWithState &m_abm : m_abms) {
492 // Deallocate players
493 for (RemotePlayer *m_player : m_players) {
497 delete m_player_database;
498 delete m_auth_database;
501 Map & ServerEnvironment::getMap()
506 ServerMap & ServerEnvironment::getServerMap()
511 RemotePlayer *ServerEnvironment::getPlayer(const session_t peer_id)
513 for (RemotePlayer *player : m_players) {
514 if (player->getPeerId() == peer_id)
520 RemotePlayer *ServerEnvironment::getPlayer(const char* name)
522 for (RemotePlayer *player : m_players) {
523 if (strcmp(player->getName(), name) == 0)
529 void ServerEnvironment::addPlayer(RemotePlayer *player)
532 Check that peer_ids are unique.
533 Also check that names are unique.
534 Exception: there can be multiple players with peer_id=0
536 // If peer id is non-zero, it has to be unique.
537 if (player->getPeerId() != PEER_ID_INEXISTENT)
538 FATAL_ERROR_IF(getPlayer(player->getPeerId()) != NULL, "Peer id not unique");
539 // Name has to be unique.
540 FATAL_ERROR_IF(getPlayer(player->getName()) != NULL, "Player name not unique");
542 m_players.push_back(player);
545 void ServerEnvironment::removePlayer(RemotePlayer *player)
547 for (std::vector<RemotePlayer *>::iterator it = m_players.begin();
548 it != m_players.end(); ++it) {
549 if ((*it) == player) {
557 bool ServerEnvironment::removePlayerFromDatabase(const std::string &name)
559 return m_player_database->removePlayer(name);
562 void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason,
563 const std::string &str_reason, bool reconnect)
565 for (RemotePlayer *player : m_players)
566 m_server->DenyAccess(player->getPeerId(), reason, str_reason, reconnect);
569 void ServerEnvironment::saveLoadedPlayers(bool force)
571 for (RemotePlayer *player : m_players) {
572 if (force || player->checkModified() || (player->getPlayerSAO() &&
573 player->getPlayerSAO()->getMeta().isModified())) {
575 m_player_database->savePlayer(player);
576 } catch (DatabaseException &e) {
577 errorstream << "Failed to save player " << player->getName() << " exception: "
578 << e.what() << std::endl;
585 void ServerEnvironment::savePlayer(RemotePlayer *player)
588 m_player_database->savePlayer(player);
589 } catch (DatabaseException &e) {
590 errorstream << "Failed to save player " << player->getName() << " exception: "
591 << e.what() << std::endl;
596 PlayerSAO *ServerEnvironment::loadPlayer(RemotePlayer *player, bool *new_player,
597 session_t peer_id, bool is_singleplayer)
599 PlayerSAO *playersao = new PlayerSAO(this, player, peer_id, is_singleplayer);
600 // Create player if it doesn't exist
601 if (!m_player_database->loadPlayer(player, playersao)) {
603 // Set player position
604 infostream << "Server: Finding spawn place for player \""
605 << player->getName() << "\"" << std::endl;
606 playersao->setBasePosition(m_server->findSpawnPos());
608 // Make sure the player is saved
609 player->setModified(true);
611 // If the player exists, ensure that they respawn inside legal bounds
612 // This fixes an assert crash when the player can't be added
613 // to the environment
614 if (objectpos_over_limit(playersao->getBasePosition())) {
615 actionstream << "Respawn position for player \""
616 << player->getName() << "\" outside limits, resetting" << std::endl;
617 playersao->setBasePosition(m_server->findSpawnPos());
621 // Add player to environment
624 /* Clean up old HUD elements from previous sessions */
627 /* Add object to environment */
628 addActiveObject(playersao);
630 // Update active blocks asap so objects in those blocks appear on the client
631 m_force_update_active_blocks = true;
636 void ServerEnvironment::saveMeta()
641 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
643 // Open file and serialize
644 std::ostringstream ss(std::ios_base::binary);
646 Settings args("EnvArgsEnd");
647 args.setU64("game_time", m_game_time);
648 args.setU64("time_of_day", getTimeOfDay());
649 args.setU64("last_clear_objects_time", m_last_clear_objects_time);
650 args.setU64("lbm_introduction_times_version", 1);
651 args.set("lbm_introduction_times",
652 m_lbm_mgr.createIntroductionTimesString());
653 args.setU64("day_count", m_day_count);
656 if(!fs::safeWriteToFile(path, ss.str()))
658 infostream<<"ServerEnvironment::saveMeta(): Failed to write "
660 throw SerializationError("Couldn't save env meta");
664 void ServerEnvironment::loadMeta()
666 SANITY_CHECK(!m_meta_loaded);
667 m_meta_loaded = true;
669 // If file doesn't exist, load default environment metadata
670 if (!fs::PathExists(m_path_world + DIR_DELIM "env_meta.txt")) {
671 infostream << "ServerEnvironment: Loading default environment metadata"
677 infostream << "ServerEnvironment: Loading environment metadata" << std::endl;
679 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
681 // Open file and deserialize
682 std::ifstream is(path.c_str(), std::ios_base::binary);
684 infostream << "ServerEnvironment::loadMeta(): Failed to open "
685 << path << std::endl;
686 throw SerializationError("Couldn't load env meta");
689 Settings args("EnvArgsEnd");
691 if (!args.parseConfigLines(is)) {
692 throw SerializationError("ServerEnvironment::loadMeta(): "
693 "EnvArgsEnd not found!");
697 m_game_time = args.getU64("game_time");
698 } catch (SettingNotFoundException &e) {
699 // Getting this is crucial, otherwise timestamps are useless
700 throw SerializationError("Couldn't load env meta game_time");
703 setTimeOfDay(args.exists("time_of_day") ?
704 // set day to early morning by default
705 args.getU64("time_of_day") : 5250);
707 m_last_clear_objects_time = args.exists("last_clear_objects_time") ?
708 // If missing, do as if clearObjects was never called
709 args.getU64("last_clear_objects_time") : 0;
711 std::string lbm_introduction_times;
713 u64 ver = args.getU64("lbm_introduction_times_version");
715 lbm_introduction_times = args.get("lbm_introduction_times");
717 infostream << "ServerEnvironment::loadMeta(): Non-supported"
718 << " introduction time version " << ver << std::endl;
720 } catch (SettingNotFoundException &e) {
721 // No problem, this is expected. Just continue with an empty string
723 m_lbm_mgr.loadIntroductionTimes(lbm_introduction_times, m_server, m_game_time);
725 m_day_count = args.exists("day_count") ?
726 args.getU64("day_count") : 0;
730 * called if env_meta.txt doesn't exist (e.g. new world)
732 void ServerEnvironment::loadDefaultMeta()
734 m_lbm_mgr.loadIntroductionTimes("", m_server, m_game_time);
739 ActiveBlockModifier *abm;
741 std::vector<content_t> required_neighbors;
742 bool check_required_neighbors; // false if required_neighbors is known to be empty
750 ServerEnvironment *m_env;
751 std::vector<std::vector<ActiveABM> *> m_aabms;
753 ABMHandler(std::vector<ABMWithState> &abms,
754 float dtime_s, ServerEnvironment *env,
760 const NodeDefManager *ndef = env->getGameDef()->ndef();
761 for (ABMWithState &abmws : abms) {
762 ActiveBlockModifier *abm = abmws.abm;
763 float trigger_interval = abm->getTriggerInterval();
764 if(trigger_interval < 0.001)
765 trigger_interval = 0.001;
766 float actual_interval = dtime_s;
768 abmws.timer += dtime_s;
769 if(abmws.timer < trigger_interval)
771 abmws.timer -= trigger_interval;
772 actual_interval = trigger_interval;
774 float chance = abm->getTriggerChance();
779 if (abm->getSimpleCatchUp()) {
780 float intervals = actual_interval / trigger_interval;
783 aabm.chance = chance / intervals;
787 aabm.chance = chance;
790 aabm.min_y = abm->getMinY();
791 aabm.max_y = abm->getMaxY();
794 const std::vector<std::string> &required_neighbors_s =
795 abm->getRequiredNeighbors();
796 for (const std::string &required_neighbor_s : required_neighbors_s) {
797 ndef->getIds(required_neighbor_s, aabm.required_neighbors);
799 aabm.check_required_neighbors = !required_neighbors_s.empty();
802 const std::vector<std::string> &contents_s = abm->getTriggerContents();
803 for (const std::string &content_s : contents_s) {
804 std::vector<content_t> ids;
805 ndef->getIds(content_s, ids);
806 for (content_t c : ids) {
807 if (c >= m_aabms.size())
808 m_aabms.resize(c + 256, NULL);
810 m_aabms[c] = new std::vector<ActiveABM>;
811 m_aabms[c]->push_back(aabm);
819 for (auto &aabms : m_aabms)
823 // Find out how many objects the given block and its neighbours contain.
824 // Returns the number of objects in the block, and also in 'wider' the
825 // number of objects in the block and all its neighbours. The latter
826 // may an estimate if any neighbours are unloaded.
827 u32 countObjects(MapBlock *block, ServerMap * map, u32 &wider)
830 u32 wider_unknown_count = 0;
831 for(s16 x=-1; x<=1; x++)
832 for(s16 y=-1; y<=1; y++)
833 for(s16 z=-1; z<=1; z++)
835 MapBlock *block2 = map->getBlockNoCreateNoEx(
836 block->getPos() + v3s16(x,y,z));
838 wider_unknown_count++;
841 wider += block2->m_static_objects.m_active.size()
842 + block2->m_static_objects.m_stored.size();
845 u32 active_object_count = block->m_static_objects.m_active.size();
846 u32 wider_known_count = 3*3*3 - wider_unknown_count;
847 wider += wider_unknown_count * wider / wider_known_count;
848 return active_object_count;
851 void apply(MapBlock *block, int &blocks_scanned, int &abms_run, int &blocks_cached)
853 if(m_aabms.empty() || block->isDummy())
856 // Check the content type cache first
857 // to see whether there are any ABMs
858 // to be run at all for this block.
859 if (block->contents_cached) {
861 bool run_abms = false;
862 for (content_t c : block->contents) {
863 if (c < m_aabms.size() && m_aabms[c]) {
872 block->contents.clear();
876 ServerMap *map = &m_env->getServerMap();
878 u32 active_object_count_wider;
879 u32 active_object_count = this->countObjects(block, map, active_object_count_wider);
880 m_env->m_added_objects = 0;
883 for(p0.X=0; p0.X<MAP_BLOCKSIZE; p0.X++)
884 for(p0.Y=0; p0.Y<MAP_BLOCKSIZE; p0.Y++)
885 for(p0.Z=0; p0.Z<MAP_BLOCKSIZE; p0.Z++)
887 const MapNode &n = block->getNodeUnsafe(p0);
888 content_t c = n.getContent();
889 // Cache content types as we go
890 if (!block->contents_cached && !block->do_not_cache_contents) {
891 block->contents.insert(c);
892 if (block->contents.size() > 64) {
893 // Too many different nodes... don't try to cache
894 block->do_not_cache_contents = true;
895 block->contents.clear();
899 if (c >= m_aabms.size() || !m_aabms[c])
902 v3s16 p = p0 + block->getPosRelative();
903 for (ActiveABM &aabm : *m_aabms[c]) {
904 if ((p.Y < aabm.min_y) || (p.Y > aabm.max_y))
907 if (myrand() % aabm.chance != 0)
911 if (aabm.check_required_neighbors) {
913 for(p1.X = p0.X-1; p1.X <= p0.X+1; p1.X++)
914 for(p1.Y = p0.Y-1; p1.Y <= p0.Y+1; p1.Y++)
915 for(p1.Z = p0.Z-1; p1.Z <= p0.Z+1; p1.Z++)
920 if (block->isValidPosition(p1)) {
921 // if the neighbor is found on the same map block
922 // get it straight from there
923 const MapNode &n = block->getNodeUnsafe(p1);
926 // otherwise consult the map
927 MapNode n = map->getNode(p1 + block->getPosRelative());
930 if (CONTAINS(aabm.required_neighbors, c))
933 // No required neighbor found
939 // Call all the trigger variations
940 aabm.abm->trigger(m_env, p, n);
941 aabm.abm->trigger(m_env, p, n,
942 active_object_count, active_object_count_wider);
944 // Count surrounding objects again if the abms added any
945 if(m_env->m_added_objects > 0) {
946 active_object_count = countObjects(block, map, active_object_count_wider);
947 m_env->m_added_objects = 0;
951 block->contents_cached = !block->do_not_cache_contents;
955 void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime)
957 // Reset usage timer immediately, otherwise a block that becomes active
958 // again at around the same time as it would normally be unloaded will
959 // get unloaded incorrectly. (I think this still leaves a small possibility
960 // of a race condition between this and server::AsyncRunStep, which only
961 // some kind of synchronisation will fix, but it at least reduces the window
962 // of opportunity for it to break from seconds to nanoseconds)
963 block->resetUsageTimer();
965 // Get time difference
967 u32 stamp = block->getTimestamp();
968 if (m_game_time > stamp && stamp != BLOCK_TIMESTAMP_UNDEFINED)
969 dtime_s = m_game_time - stamp;
970 dtime_s += additional_dtime;
972 /*infostream<<"ServerEnvironment::activateBlock(): block timestamp: "
973 <<stamp<<", game time: "<<m_game_time<<std::endl;*/
975 // Remove stored static objects if clearObjects was called since block's timestamp
976 if (stamp == BLOCK_TIMESTAMP_UNDEFINED || stamp < m_last_clear_objects_time) {
977 block->m_static_objects.m_stored.clear();
978 // do not set changed flag to avoid unnecessary mapblock writes
981 // Set current time as timestamp
982 block->setTimestampNoChangedFlag(m_game_time);
984 /*infostream<<"ServerEnvironment::activateBlock(): block is "
985 <<dtime_s<<" seconds old."<<std::endl;*/
987 // Activate stored objects
988 activateObjects(block, dtime_s);
990 /* Handle LoadingBlockModifiers */
991 m_lbm_mgr.applyLBMs(this, block, stamp);
994 std::vector<NodeTimer> elapsed_timers =
995 block->m_node_timers.step((float)dtime_s);
996 if (!elapsed_timers.empty()) {
998 for (const NodeTimer &elapsed_timer : elapsed_timers) {
999 n = block->getNodeNoEx(elapsed_timer.position);
1000 v3s16 p = elapsed_timer.position + block->getPosRelative();
1001 if (m_script->node_on_timer(p, n, elapsed_timer.elapsed))
1002 block->setNodeTimer(NodeTimer(elapsed_timer.timeout, 0,
1003 elapsed_timer.position));
1008 void ServerEnvironment::addActiveBlockModifier(ActiveBlockModifier *abm)
1010 m_abms.emplace_back(abm);
1013 void ServerEnvironment::addLoadingBlockModifierDef(LoadingBlockModifierDef *lbm)
1015 m_lbm_mgr.addLBMDef(lbm);
1018 bool ServerEnvironment::setNode(v3s16 p, const MapNode &n)
1020 const NodeDefManager *ndef = m_server->ndef();
1021 MapNode n_old = m_map->getNode(p);
1023 const ContentFeatures &cf_old = ndef->get(n_old);
1026 if (cf_old.has_on_destruct)
1027 m_script->node_on_destruct(p, n_old);
1030 if (!m_map->addNodeWithEvent(p, n))
1033 // Update active VoxelManipulator if a mapgen thread
1034 m_map->updateVManip(p);
1036 // Call post-destructor
1037 if (cf_old.has_after_destruct)
1038 m_script->node_after_destruct(p, n_old);
1040 // Retrieve node content features
1041 // if new node is same as old, reuse old definition to prevent a lookup
1042 const ContentFeatures &cf_new = n_old == n ? cf_old : ndef->get(n);
1045 if (cf_new.has_on_construct)
1046 m_script->node_on_construct(p, n);
1051 bool ServerEnvironment::removeNode(v3s16 p)
1053 const NodeDefManager *ndef = m_server->ndef();
1054 MapNode n_old = m_map->getNode(p);
1057 if (ndef->get(n_old).has_on_destruct)
1058 m_script->node_on_destruct(p, n_old);
1061 // This is slightly optimized compared to addNodeWithEvent(air)
1062 if (!m_map->removeNodeWithEvent(p))
1065 // Update active VoxelManipulator if a mapgen thread
1066 m_map->updateVManip(p);
1068 // Call post-destructor
1069 if (ndef->get(n_old).has_after_destruct)
1070 m_script->node_after_destruct(p, n_old);
1072 // Air doesn't require constructor
1076 bool ServerEnvironment::swapNode(v3s16 p, const MapNode &n)
1078 if (!m_map->addNodeWithEvent(p, n, false))
1081 // Update active VoxelManipulator if a mapgen thread
1082 m_map->updateVManip(p);
1087 u8 ServerEnvironment::findSunlight(v3s16 pos) const
1089 // Directions for neighbouring nodes with specified order
1090 static const v3s16 dirs[] = {
1091 v3s16(-1, 0, 0), v3s16(1, 0, 0), v3s16(0, 0, -1), v3s16(0, 0, 1),
1092 v3s16(0, -1, 0), v3s16(0, 1, 0)
1095 const NodeDefManager *ndef = m_server->ndef();
1097 // found_light remembers the highest known sunlight value at pos
1100 struct stack_entry {
1104 std::stack<stack_entry> stack;
1105 stack.push({pos, 0});
1107 std::unordered_map<s64, s8> dists;
1108 dists[MapDatabase::getBlockAsInteger(pos)] = 0;
1110 while (!stack.empty()) {
1111 struct stack_entry e = stack.top();
1114 v3s16 currentPos = e.pos;
1115 s8 dist = e.dist + 1;
1117 for (const v3s16& off : dirs) {
1118 v3s16 neighborPos = currentPos + off;
1119 s64 neighborHash = MapDatabase::getBlockAsInteger(neighborPos);
1121 // Do not walk neighborPos multiple times unless the distance to the start
1122 // position is shorter
1123 auto it = dists.find(neighborHash);
1124 if (it != dists.end() && dist >= it->second)
1128 bool is_position_ok;
1129 MapNode node = m_map->getNode(neighborPos, &is_position_ok);
1130 if (!is_position_ok) {
1131 // This happens very rarely because the map at currentPos is loaded
1132 m_map->emergeBlock(neighborPos, false);
1133 node = m_map->getNode(neighborPos, &is_position_ok);
1134 if (!is_position_ok)
1135 continue; // not generated
1138 const ContentFeatures &def = ndef->get(node);
1139 if (!def.sunlight_propagates) {
1140 // Do not test propagation here again
1141 dists[neighborHash] = -1;
1145 // Sunlight could have come from here
1146 dists[neighborHash] = dist;
1147 u8 daylight = node.param1 & 0x0f;
1149 // In the special case where sunlight shines from above and thus
1150 // does not decrease with upwards distance, daylight is always
1151 // bigger than nightlight, which never reaches 15
1152 int possible_finlight = daylight - dist;
1153 if (possible_finlight <= found_light) {
1154 // Light from here cannot make a brighter light at currentPos than
1159 u8 nightlight = node.param1 >> 4;
1160 if (daylight > nightlight) {
1161 // Found a valid daylight
1162 found_light = possible_finlight;
1164 // Sunlight may be darker, so walk the neighbours
1165 stack.push({neighborPos, dist});
1172 void ServerEnvironment::clearObjects(ClearObjectsMode mode)
1174 infostream << "ServerEnvironment::clearObjects(): "
1175 << "Removing all active objects" << std::endl;
1176 auto cb_removal = [this] (ServerActiveObject *obj, u16 id) {
1177 if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER)
1180 // Delete static object if block is loaded
1181 deleteStaticFromBlock(obj, id, MOD_REASON_CLEAR_ALL_OBJECTS, true);
1183 // If known by some client, don't delete immediately
1184 if (obj->m_known_by_count > 0) {
1185 obj->markForRemoval();
1189 // Tell the object about removal
1190 obj->removingFromEnvironment();
1191 // Deregister in scripting api
1192 m_script->removeObjectReference(obj);
1194 // Delete active object
1195 if (obj->environmentDeletes())
1201 m_ao_manager.clear(cb_removal);
1203 // Get list of loaded blocks
1204 std::vector<v3s16> loaded_blocks;
1205 infostream << "ServerEnvironment::clearObjects(): "
1206 << "Listing all loaded blocks" << std::endl;
1207 m_map->listAllLoadedBlocks(loaded_blocks);
1208 infostream << "ServerEnvironment::clearObjects(): "
1209 << "Done listing all loaded blocks: "
1210 << loaded_blocks.size()<<std::endl;
1212 // Get list of loadable blocks
1213 std::vector<v3s16> loadable_blocks;
1214 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1215 infostream << "ServerEnvironment::clearObjects(): "
1216 << "Listing all loadable blocks" << std::endl;
1217 m_map->listAllLoadableBlocks(loadable_blocks);
1218 infostream << "ServerEnvironment::clearObjects(): "
1219 << "Done listing all loadable blocks: "
1220 << loadable_blocks.size() << std::endl;
1222 loadable_blocks = loaded_blocks;
1225 actionstream << "ServerEnvironment::clearObjects(): "
1226 << "Now clearing objects in " << loadable_blocks.size()
1227 << " blocks" << std::endl;
1229 // Grab a reference on each loaded block to avoid unloading it
1230 for (v3s16 p : loaded_blocks) {
1231 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1232 assert(block != NULL);
1236 // Remove objects in all loadable blocks
1237 u32 unload_interval = U32_MAX;
1238 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1239 unload_interval = g_settings->getS32("max_clearobjects_extra_loaded_blocks");
1240 unload_interval = MYMAX(unload_interval, 1);
1242 u32 report_interval = loadable_blocks.size() / 10;
1243 u32 num_blocks_checked = 0;
1244 u32 num_blocks_cleared = 0;
1245 u32 num_objs_cleared = 0;
1246 for (auto i = loadable_blocks.begin();
1247 i != loadable_blocks.end(); ++i) {
1249 MapBlock *block = m_map->emergeBlock(p, false);
1251 errorstream << "ServerEnvironment::clearObjects(): "
1252 << "Failed to emerge block " << PP(p) << std::endl;
1255 u32 num_stored = block->m_static_objects.m_stored.size();
1256 u32 num_active = block->m_static_objects.m_active.size();
1257 if (num_stored != 0 || num_active != 0) {
1258 block->m_static_objects.m_stored.clear();
1259 block->m_static_objects.m_active.clear();
1260 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1261 MOD_REASON_CLEAR_ALL_OBJECTS);
1262 num_objs_cleared += num_stored + num_active;
1263 num_blocks_cleared++;
1265 num_blocks_checked++;
1267 if (report_interval != 0 &&
1268 num_blocks_checked % report_interval == 0) {
1269 float percent = 100.0 * (float)num_blocks_checked /
1270 loadable_blocks.size();
1271 actionstream << "ServerEnvironment::clearObjects(): "
1272 << "Cleared " << num_objs_cleared << " objects"
1273 << " in " << num_blocks_cleared << " blocks ("
1274 << percent << "%)" << std::endl;
1276 if (num_blocks_checked % unload_interval == 0) {
1277 m_map->unloadUnreferencedBlocks();
1280 m_map->unloadUnreferencedBlocks();
1282 // Drop references that were added above
1283 for (v3s16 p : loaded_blocks) {
1284 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1289 m_last_clear_objects_time = m_game_time;
1291 actionstream << "ServerEnvironment::clearObjects(): "
1292 << "Finished: Cleared " << num_objs_cleared << " objects"
1293 << " in " << num_blocks_cleared << " blocks" << std::endl;
1296 void ServerEnvironment::step(float dtime)
1298 ScopeProfiler sp2(g_profiler, "ServerEnv::step()", SPT_AVG);
1299 const auto start_time = porting::getTimeUs();
1301 /* Step time of day */
1302 stepTimeOfDay(dtime);
1305 // NOTE: This is kind of funny on a singleplayer game, but doesn't
1306 // really matter that much.
1307 static thread_local const float server_step =
1308 g_settings->getFloat("dedicated_server_step");
1309 m_recommended_send_interval = server_step;
1315 m_game_time_fraction_counter += dtime;
1316 u32 inc_i = (u32)m_game_time_fraction_counter;
1317 m_game_time += inc_i;
1318 m_game_time_fraction_counter -= (float)inc_i;
1325 ScopeProfiler sp(g_profiler, "ServerEnv: move players", SPT_AVG);
1326 for (RemotePlayer *player : m_players) {
1327 // Ignore disconnected players
1328 if (player->getPeerId() == PEER_ID_INEXISTENT)
1332 player->move(dtime, this, 100 * BS);
1337 Manage active block list
1339 if (m_active_blocks_mgmt_interval.step(dtime, m_cache_active_block_mgmt_interval) ||
1340 m_force_update_active_blocks) {
1341 ScopeProfiler sp(g_profiler, "ServerEnv: update active blocks", SPT_AVG);
1344 Get player block positions
1346 std::vector<PlayerSAO*> players;
1347 players.reserve(m_players.size());
1348 for (RemotePlayer *player : m_players) {
1349 // Ignore disconnected players
1350 if (player->getPeerId() == PEER_ID_INEXISTENT)
1353 PlayerSAO *playersao = player->getPlayerSAO();
1356 players.push_back(playersao);
1360 Update list of active blocks, collecting changes
1362 // use active_object_send_range_blocks since that is max distance
1363 // for active objects sent the client anyway
1364 static thread_local const s16 active_object_range =
1365 g_settings->getS16("active_object_send_range_blocks");
1366 static thread_local const s16 active_block_range =
1367 g_settings->getS16("active_block_range");
1368 std::set<v3s16> blocks_removed;
1369 std::set<v3s16> blocks_added;
1370 m_active_blocks.update(players, active_block_range, active_object_range,
1371 blocks_removed, blocks_added);
1374 Handle removed blocks
1377 // Convert active objects that are no more in active blocks to static
1378 deactivateFarObjects(false);
1380 for (const v3s16 &p: blocks_removed) {
1381 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1385 // Set current time as timestamp (and let it set ChangedFlag)
1386 block->setTimestamp(m_game_time);
1393 for (const v3s16 &p: blocks_added) {
1394 MapBlock *block = m_map->getBlockOrEmerge(p);
1396 // TODO: The blocks removed here will only be picked up again
1397 // on the next cycle. To minimize the latency of objects being
1398 // activated we could remember the blocks pending activating
1399 // and activate them instantly as soon as they're loaded.
1400 m_active_blocks.remove(p);
1404 activateBlock(block);
1407 // Some blocks may be removed again by the code above so do this here
1408 m_active_block_gauge->set(m_active_blocks.size());
1410 m_force_update_active_blocks = false;
1413 Mess around in active blocks
1415 if (m_active_blocks_nodemetadata_interval.step(dtime, m_cache_nodetimer_interval)) {
1416 ScopeProfiler sp(g_profiler, "ServerEnv: Run node timers", SPT_AVG);
1418 float dtime = m_cache_nodetimer_interval;
1420 for (const v3s16 &p: m_active_blocks.m_list) {
1421 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1425 // Reset block usage timer
1426 block->resetUsageTimer();
1428 // Set current time as timestamp
1429 block->setTimestampNoChangedFlag(m_game_time);
1430 // If time has changed much from the one on disk,
1431 // set block to be saved when it is unloaded
1432 if(block->getTimestamp() > block->getDiskTimestamp() + 60)
1433 block->raiseModified(MOD_STATE_WRITE_AT_UNLOAD,
1434 MOD_REASON_BLOCK_EXPIRED);
1437 std::vector<NodeTimer> elapsed_timers = block->m_node_timers.step(dtime);
1438 if (!elapsed_timers.empty()) {
1441 for (const NodeTimer &elapsed_timer: elapsed_timers) {
1442 n = block->getNodeNoEx(elapsed_timer.position);
1443 p2 = elapsed_timer.position + block->getPosRelative();
1444 if (m_script->node_on_timer(p2, n, elapsed_timer.elapsed)) {
1445 block->setNodeTimer(NodeTimer(
1446 elapsed_timer.timeout, 0, elapsed_timer.position));
1453 if (m_active_block_modifier_interval.step(dtime, m_cache_abm_interval)) {
1454 ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg per interval", SPT_AVG);
1455 TimeTaker timer("modify in active blocks per interval");
1457 // Initialize handling of ActiveBlockModifiers
1458 ABMHandler abmhandler(m_abms, m_cache_abm_interval, this, true);
1460 int blocks_scanned = 0;
1462 int blocks_cached = 0;
1464 std::vector<v3s16> output(m_active_blocks.m_abm_list.size());
1466 // Shuffle the active blocks so that each block gets an equal chance
1467 // of having its ABMs run.
1468 std::copy(m_active_blocks.m_abm_list.begin(), m_active_blocks.m_abm_list.end(), output.begin());
1469 std::shuffle(output.begin(), output.end(), m_rgen);
1472 // determine the time budget for ABMs
1473 u32 max_time_ms = m_cache_abm_interval * 1000 * m_cache_abm_time_budget;
1474 for (const v3s16 &p : output) {
1475 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1481 // Set current time as timestamp
1482 block->setTimestampNoChangedFlag(m_game_time);
1484 /* Handle ActiveBlockModifiers */
1485 abmhandler.apply(block, blocks_scanned, abms_run, blocks_cached);
1487 u32 time_ms = timer.getTimerTime();
1489 if (time_ms > max_time_ms) {
1490 warningstream << "active block modifiers took "
1491 << time_ms << "ms (processed " << i << " of "
1492 << output.size() << " active blocks)" << std::endl;
1496 g_profiler->avg("ServerEnv: active blocks", m_active_blocks.m_abm_list.size());
1497 g_profiler->avg("ServerEnv: active blocks cached", blocks_cached);
1498 g_profiler->avg("ServerEnv: active blocks scanned for ABMs", blocks_scanned);
1499 g_profiler->avg("ServerEnv: ABMs run", abms_run);
1505 Step script environment (run global on_step())
1507 m_script->environment_Step(dtime);
1509 m_script->stepAsync();
1515 ScopeProfiler sp(g_profiler, "ServerEnv: Run SAO::step()", SPT_AVG);
1517 // This helps the objects to send data at the same time
1518 bool send_recommended = false;
1519 m_send_recommended_timer += dtime;
1520 if (m_send_recommended_timer > getSendRecommendedInterval()) {
1521 m_send_recommended_timer -= getSendRecommendedInterval();
1522 send_recommended = true;
1525 u32 object_count = 0;
1527 auto cb_state = [&] (ServerActiveObject *obj) {
1533 obj->step(dtime, send_recommended);
1534 // Read messages from object
1535 obj->dumpAOMessagesToQueue(m_active_object_messages);
1537 m_ao_manager.step(dtime, cb_state);
1539 m_active_object_gauge->set(object_count);
1543 Manage active objects
1545 if (m_object_management_interval.step(dtime, 0.5)) {
1546 removeRemovedObjects();
1550 Manage particle spawner expiration
1552 if (m_particle_management_interval.step(dtime, 1.0)) {
1553 for (std::unordered_map<u32, float>::iterator i = m_particle_spawners.begin();
1554 i != m_particle_spawners.end(); ) {
1555 //non expiring spawners
1556 if (i->second == PARTICLE_SPAWNER_NO_EXPIRY) {
1562 if (i->second <= 0.f)
1563 m_particle_spawners.erase(i++);
1569 // Send outdated player inventories
1570 for (RemotePlayer *player : m_players) {
1571 if (player->getPeerId() == PEER_ID_INEXISTENT)
1574 PlayerSAO *sao = player->getPlayerSAO();
1575 if (sao && player->inventory.checkModified())
1576 m_server->SendInventory(sao, true);
1579 // Send outdated detached inventories
1580 m_server->sendDetachedInventories(PEER_ID_INEXISTENT, true);
1582 const auto end_time = porting::getTimeUs();
1583 m_step_time_counter->increment(end_time - start_time);
1586 ServerEnvironment::BlockStatus ServerEnvironment::getBlockStatus(v3s16 blockpos)
1588 if (m_active_blocks.contains(blockpos))
1591 const MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos);
1592 if (block && !block->isDummy())
1595 if (m_map->isBlockInQueue(blockpos))
1601 u32 ServerEnvironment::addParticleSpawner(float exptime)
1603 // Timers with lifetime 0 do not expire
1604 float time = exptime > 0.f ? exptime : PARTICLE_SPAWNER_NO_EXPIRY;
1607 for (;;) { // look for unused particlespawner id
1609 std::unordered_map<u32, float>::iterator f = m_particle_spawners.find(id);
1610 if (f == m_particle_spawners.end()) {
1611 m_particle_spawners[id] = time;
1618 u32 ServerEnvironment::addParticleSpawner(float exptime, u16 attached_id)
1620 u32 id = addParticleSpawner(exptime);
1621 m_particle_spawner_attachments[id] = attached_id;
1622 if (ServerActiveObject *obj = getActiveObject(attached_id)) {
1623 obj->attachParticleSpawner(id);
1628 void ServerEnvironment::deleteParticleSpawner(u32 id, bool remove_from_object)
1630 m_particle_spawners.erase(id);
1631 const auto &it = m_particle_spawner_attachments.find(id);
1632 if (it != m_particle_spawner_attachments.end()) {
1633 u16 obj_id = it->second;
1634 ServerActiveObject *sao = getActiveObject(obj_id);
1635 if (sao != NULL && remove_from_object) {
1636 sao->detachParticleSpawner(id);
1638 m_particle_spawner_attachments.erase(id);
1642 u16 ServerEnvironment::addActiveObject(ServerActiveObject *object)
1644 assert(object); // Pre-condition
1646 u16 id = addActiveObjectRaw(object, true, 0);
1651 Finds out what new objects have been added to
1652 inside a radius around a position
1654 void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius,
1656 std::set<u16> ¤t_objects,
1657 std::queue<u16> &added_objects)
1659 f32 radius_f = radius * BS;
1660 f32 player_radius_f = player_radius * BS;
1662 if (player_radius_f < 0.0f)
1663 player_radius_f = 0.0f;
1665 m_ao_manager.getAddedActiveObjectsAroundPos(playersao->getBasePosition(), radius_f,
1666 player_radius_f, current_objects, added_objects);
1670 Finds out what objects have been removed from
1671 inside a radius around a position
1673 void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius,
1675 std::set<u16> ¤t_objects,
1676 std::queue<u16> &removed_objects)
1678 f32 radius_f = radius * BS;
1679 f32 player_radius_f = player_radius * BS;
1681 if (player_radius_f < 0)
1682 player_radius_f = 0;
1684 Go through current_objects; object is removed if:
1685 - object is not found in m_active_objects (this is actually an
1686 error condition; objects should be removed only after all clients
1687 have been informed about removal), or
1688 - object is to be removed or deactivated, or
1689 - object is too far away
1691 for (u16 id : current_objects) {
1692 ServerActiveObject *object = getActiveObject(id);
1694 if (object == NULL) {
1695 infostream << "ServerEnvironment::getRemovedActiveObjects():"
1696 << " object in current_objects is NULL" << std::endl;
1697 removed_objects.push(id);
1701 if (object->isGone()) {
1702 removed_objects.push(id);
1706 f32 distance_f = object->getBasePosition().getDistanceFrom(playersao->getBasePosition());
1707 if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1708 if (distance_f <= player_radius_f || player_radius_f == 0)
1710 } else if (distance_f <= radius_f)
1713 // Object is no longer visible
1714 removed_objects.push(id);
1718 void ServerEnvironment::setStaticForActiveObjectsInBlock(
1719 v3s16 blockpos, bool static_exists, v3s16 static_block)
1721 MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos);
1725 for (auto &so_it : block->m_static_objects.m_active) {
1726 // Get the ServerActiveObject counterpart to this StaticObject
1727 ServerActiveObject *sao = m_ao_manager.getActiveObject(so_it.first);
1729 // If this ever happens, there must be some kind of nasty bug.
1730 errorstream << "ServerEnvironment::setStaticForObjectsInBlock(): "
1731 "Object from MapBlock::m_static_objects::m_active not found "
1732 "in m_active_objects";
1736 sao->m_static_exists = static_exists;
1737 sao->m_static_block = static_block;
1741 bool ServerEnvironment::getActiveObjectMessage(ActiveObjectMessage *dest)
1743 if(m_active_object_messages.empty())
1746 *dest = std::move(m_active_object_messages.front());
1747 m_active_object_messages.pop();
1751 void ServerEnvironment::getSelectedActiveObjects(
1752 const core::line3d<f32> &shootline_on_map,
1753 std::vector<PointedThing> &objects)
1755 std::vector<ServerActiveObject *> objs;
1756 getObjectsInsideRadius(objs, shootline_on_map.start,
1757 shootline_on_map.getLength() + 10.0f, nullptr);
1758 const v3f line_vector = shootline_on_map.getVector();
1760 for (auto obj : objs) {
1763 aabb3f selection_box;
1764 if (!obj->getSelectionBox(&selection_box))
1767 v3f pos = obj->getBasePosition();
1769 aabb3f offsetted_box(selection_box.MinEdge + pos,
1770 selection_box.MaxEdge + pos);
1772 v3f current_intersection;
1773 v3s16 current_normal;
1774 if (boxLineCollision(offsetted_box, shootline_on_map.start, line_vector,
1775 ¤t_intersection, ¤t_normal)) {
1776 objects.emplace_back(
1777 (s16) obj->getId(), current_intersection, current_normal,
1778 (current_intersection - shootline_on_map.start).getLengthSQ());
1784 ************ Private methods *************
1787 u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object,
1788 bool set_changed, u32 dtime_s)
1790 if (!m_ao_manager.registerObject(object)) {
1794 // Register reference in scripting api (must be done before post-init)
1795 m_script->addObjectReference(object);
1796 // Post-initialize object
1797 object->addedToEnvironment(dtime_s);
1799 // Add static data to block
1800 if (object->isStaticAllowed()) {
1801 // Add static object to active static list of the block
1802 v3f objectpos = object->getBasePosition();
1803 StaticObject s_obj(object, objectpos);
1804 // Add to the block where the object is located in
1805 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
1806 MapBlock *block = m_map->emergeBlock(blockpos);
1808 block->m_static_objects.m_active[object->getId()] = s_obj;
1809 object->m_static_exists = true;
1810 object->m_static_block = blockpos;
1813 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1814 MOD_REASON_ADD_ACTIVE_OBJECT_RAW);
1816 v3s16 p = floatToInt(objectpos, BS);
1817 errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
1818 <<"could not emerge block for storing id="<<object->getId()
1819 <<" statically (pos="<<PP(p)<<")"<<std::endl;
1823 return object->getId();
1827 Remove objects that satisfy (isGone() && m_known_by_count==0)
1829 void ServerEnvironment::removeRemovedObjects()
1831 ScopeProfiler sp(g_profiler, "ServerEnvironment::removeRemovedObjects()", SPT_AVG);
1833 auto clear_cb = [this] (ServerActiveObject *obj, u16 id) {
1834 // This shouldn't happen but check it
1836 errorstream << "ServerEnvironment::removeRemovedObjects(): "
1837 << "NULL object found. id=" << id << std::endl;
1842 We will handle objects marked for removal or deactivation
1848 Delete static data from block if removed
1850 if (obj->isPendingRemoval())
1851 deleteStaticFromBlock(obj, id, MOD_REASON_REMOVE_OBJECTS_REMOVE, false);
1853 // If still known by clients, don't actually remove. On some future
1854 // invocation this will be 0, which is when removal will continue.
1855 if(obj->m_known_by_count > 0)
1859 Move static data from active to stored if deactivated
1861 if (!obj->isPendingRemoval() && obj->m_static_exists) {
1862 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
1864 const auto i = block->m_static_objects.m_active.find(id);
1865 if (i != block->m_static_objects.m_active.end()) {
1866 block->m_static_objects.m_stored.push_back(i->second);
1867 block->m_static_objects.m_active.erase(id);
1868 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1869 MOD_REASON_REMOVE_OBJECTS_DEACTIVATE);
1871 warningstream << "ServerEnvironment::removeRemovedObjects(): "
1872 << "id=" << id << " m_static_exists=true but "
1873 << "static data doesn't actually exist in "
1874 << PP(obj->m_static_block) << std::endl;
1877 infostream << "Failed to emerge block from which an object to "
1878 << "be deactivated was loaded from. id=" << id << std::endl;
1882 // Tell the object about removal
1883 obj->removingFromEnvironment();
1884 // Deregister in scripting api
1885 m_script->removeObjectReference(obj);
1888 if (obj->environmentDeletes())
1894 m_ao_manager.clear(clear_cb);
1897 static void print_hexdump(std::ostream &o, const std::string &data)
1899 const int linelength = 16;
1900 for(int l=0; ; l++){
1901 int i0 = linelength * l;
1902 bool at_end = false;
1903 int thislinelength = linelength;
1904 if(i0 + thislinelength > (int)data.size()){
1905 thislinelength = data.size() - i0;
1908 for(int di=0; di<linelength; di++){
1911 if(di<thislinelength)
1912 porting::mt_snprintf(buf, sizeof(buf), "%.2x ", data[i]);
1914 porting::mt_snprintf(buf, sizeof(buf), " ");
1918 for(int di=0; di<thislinelength; di++){
1931 ServerActiveObject* ServerEnvironment::createSAO(ActiveObjectType type, v3f pos,
1932 const std::string &data)
1935 case ACTIVEOBJECT_TYPE_LUAENTITY:
1936 return new LuaEntitySAO(this, pos, data);
1938 warningstream << "ServerActiveObject: No factory for type=" << type << std::endl;
1944 Convert stored objects from blocks near the players to active.
1946 void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s)
1951 // Ignore if no stored objects (to not set changed flag)
1952 if(block->m_static_objects.m_stored.empty())
1955 verbosestream<<"ServerEnvironment::activateObjects(): "
1956 <<"activating objects of block "<<PP(block->getPos())
1957 <<" ("<<block->m_static_objects.m_stored.size()
1958 <<" objects)"<<std::endl;
1959 bool large_amount = (block->m_static_objects.m_stored.size() > g_settings->getU16("max_objects_per_block"));
1961 errorstream<<"suspiciously large amount of objects detected: "
1962 <<block->m_static_objects.m_stored.size()<<" in "
1963 <<PP(block->getPos())
1964 <<"; removing all of them."<<std::endl;
1965 // Clear stored list
1966 block->m_static_objects.m_stored.clear();
1967 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1968 MOD_REASON_TOO_MANY_OBJECTS);
1972 // Activate stored objects
1973 std::vector<StaticObject> new_stored;
1974 for (const StaticObject &s_obj : block->m_static_objects.m_stored) {
1975 // Create an active object from the data
1976 ServerActiveObject *obj = createSAO((ActiveObjectType) s_obj.type, s_obj.pos,
1978 // If couldn't create object, store static data back.
1980 errorstream<<"ServerEnvironment::activateObjects(): "
1981 <<"failed to create active object from static object "
1982 <<"in block "<<PP(s_obj.pos/BS)
1983 <<" type="<<(int)s_obj.type<<" data:"<<std::endl;
1984 print_hexdump(verbosestream, s_obj.data);
1986 new_stored.push_back(s_obj);
1989 verbosestream<<"ServerEnvironment::activateObjects(): "
1990 <<"activated static object pos="<<PP(s_obj.pos/BS)
1991 <<" type="<<(int)s_obj.type<<std::endl;
1992 // This will also add the object to the active static list
1993 addActiveObjectRaw(obj, false, dtime_s);
1996 // Clear stored list
1997 block->m_static_objects.m_stored.clear();
1998 // Add leftover failed stuff to stored list
1999 for (const StaticObject &s_obj : new_stored) {
2000 block->m_static_objects.m_stored.push_back(s_obj);
2004 Note: Block hasn't really been modified here.
2005 The objects have just been activated and moved from the stored
2006 static list to the active static list.
2007 As such, the block is essentially the same.
2008 Thus, do not call block->raiseModified(MOD_STATE_WRITE_NEEDED).
2009 Otherwise there would be a huge amount of unnecessary I/O.
2014 Convert objects that are not standing inside active blocks to static.
2016 If m_known_by_count != 0, active object is not deleted, but static
2017 data is still updated.
2019 If force_delete is set, active object is deleted nevertheless. It
2020 shall only be set so in the destructor of the environment.
2022 If block wasn't generated (not in memory or on disk),
2024 void ServerEnvironment::deactivateFarObjects(bool _force_delete)
2026 auto cb_deactivate = [this, _force_delete] (ServerActiveObject *obj, u16 id) {
2027 // force_delete might be overriden per object
2028 bool force_delete = _force_delete;
2030 // Do not deactivate if disallowed
2031 if (!force_delete && !obj->shouldUnload())
2034 // removeRemovedObjects() is responsible for these
2035 if (!force_delete && obj->isGone())
2038 const v3f &objectpos = obj->getBasePosition();
2040 // The block in which the object resides in
2041 v3s16 blockpos_o = getNodeBlockPos(floatToInt(objectpos, BS));
2043 // If object's static data is stored in a deactivated block and object
2044 // is actually located in an active block, re-save to the block in
2045 // which the object is actually located in.
2046 if (!force_delete && obj->m_static_exists &&
2047 !m_active_blocks.contains(obj->m_static_block) &&
2048 m_active_blocks.contains(blockpos_o)) {
2050 // Delete from block where object was located
2051 deleteStaticFromBlock(obj, id, MOD_REASON_STATIC_DATA_REMOVED, false);
2053 StaticObject s_obj(obj, objectpos);
2054 // Save to block where object is located
2055 saveStaticToBlock(blockpos_o, id, obj, s_obj, MOD_REASON_STATIC_DATA_ADDED);
2060 // If block is still active, don't remove
2061 bool still_active = obj->isStaticAllowed() ?
2062 m_active_blocks.contains(blockpos_o) :
2063 getMap().getBlockNoCreateNoEx(blockpos_o) != nullptr;
2064 if (!force_delete && still_active)
2067 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
2068 << "deactivating object id=" << id << " on inactive block "
2069 << PP(blockpos_o) << std::endl;
2071 // If known by some client, don't immediately delete.
2072 bool pending_delete = (obj->m_known_by_count > 0 && !force_delete);
2075 Update the static data
2077 if (obj->isStaticAllowed()) {
2078 // Create new static object
2079 StaticObject s_obj(obj, objectpos);
2081 bool stays_in_same_block = false;
2082 bool data_changed = true;
2084 // Check if static data has changed considerably
2085 if (obj->m_static_exists) {
2086 if (obj->m_static_block == blockpos_o)
2087 stays_in_same_block = true;
2089 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
2092 const auto n = block->m_static_objects.m_active.find(id);
2093 if (n != block->m_static_objects.m_active.end()) {
2094 StaticObject static_old = n->second;
2096 float save_movem = obj->getMinimumSavedMovement();
2098 if (static_old.data == s_obj.data &&
2099 (static_old.pos - objectpos).getLength() < save_movem)
2100 data_changed = false;
2102 warningstream << "ServerEnvironment::deactivateFarObjects(): "
2103 << "id=" << id << " m_static_exists=true but "
2104 << "static data doesn't actually exist in "
2105 << PP(obj->m_static_block) << std::endl;
2111 While changes are always saved, blocks are only marked as modified
2112 if the object has moved or different staticdata. (see above)
2114 bool shall_be_written = (!stays_in_same_block || data_changed);
2115 u32 reason = shall_be_written ? MOD_REASON_STATIC_DATA_CHANGED : MOD_REASON_UNKNOWN;
2117 // Delete old static object
2118 deleteStaticFromBlock(obj, id, reason, false);
2120 // Add to the block where the object is located in
2121 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
2122 u16 store_id = pending_delete ? id : 0;
2123 if (!saveStaticToBlock(blockpos, store_id, obj, s_obj, reason))
2124 force_delete = true;
2127 // Regardless of what happens to the object at this point, deactivate it first.
2128 // This ensures that LuaEntity on_deactivate is always called.
2129 obj->markForDeactivation();
2132 If known by some client, set pending deactivation.
2133 Otherwise delete it immediately.
2135 if (pending_delete && !force_delete) {
2136 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
2137 << "object id=" << id << " is known by clients"
2138 << "; not deleting yet" << std::endl;
2143 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
2144 << "object id=" << id << " is not known by clients"
2145 << "; deleting" << std::endl;
2147 // Tell the object about removal
2148 obj->removingFromEnvironment();
2149 // Deregister in scripting api
2150 m_script->removeObjectReference(obj);
2152 // Delete active object
2153 if (obj->environmentDeletes())
2159 m_ao_manager.clear(cb_deactivate);
2162 void ServerEnvironment::deleteStaticFromBlock(
2163 ServerActiveObject *obj, u16 id, u32 mod_reason, bool no_emerge)
2165 if (!obj->m_static_exists)
2170 block = m_map->getBlockNoCreateNoEx(obj->m_static_block);
2172 block = m_map->emergeBlock(obj->m_static_block, false);
2175 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2176 << " when deleting static data of object from it. id=" << id << std::endl;
2180 block->m_static_objects.remove(id);
2181 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2182 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2184 obj->m_static_exists = false;
2187 bool ServerEnvironment::saveStaticToBlock(
2188 v3s16 blockpos, u16 store_id,
2189 ServerActiveObject *obj, const StaticObject &s_obj,
2192 MapBlock *block = nullptr;
2194 block = m_map->emergeBlock(blockpos);
2195 } catch (InvalidPositionException &e) {
2196 // Handled via NULL pointer
2197 // NOTE: emergeBlock's failure is usually determined by it
2198 // actually returning NULL
2202 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2203 << " when saving static data of object to it. id=" << store_id << std::endl;
2206 if (block->m_static_objects.m_stored.size() >= g_settings->getU16("max_objects_per_block")) {
2207 warningstream << "ServerEnv: Trying to store id = " << store_id
2208 << " statically but block " << PP(blockpos)
2209 << " already contains "
2210 << block->m_static_objects.m_stored.size()
2211 << " objects." << std::endl;
2215 block->m_static_objects.insert(store_id, s_obj);
2216 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2217 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2219 obj->m_static_exists = true;
2220 obj->m_static_block = blockpos;
2225 PlayerDatabase *ServerEnvironment::openPlayerDatabase(const std::string &name,
2226 const std::string &savedir, const Settings &conf)
2229 if (name == "sqlite3")
2230 return new PlayerDatabaseSQLite3(savedir);
2232 if (name == "dummy")
2233 return new Database_Dummy();
2236 if (name == "postgresql") {
2237 std::string connect_string;
2238 conf.getNoEx("pgsql_player_connection", connect_string);
2239 return new PlayerDatabasePostgreSQL(connect_string);
2244 if (name == "leveldb")
2245 return new PlayerDatabaseLevelDB(savedir);
2248 if (name == "files")
2249 return new PlayerDatabaseFiles(savedir + DIR_DELIM + "players");
2251 throw BaseException(std::string("Database backend ") + name + " not supported.");
2254 bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params,
2255 const Settings &cmd_args)
2257 std::string migrate_to = cmd_args.get("migrate-players");
2259 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2260 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2261 errorstream << "Cannot read world.mt!" << std::endl;
2265 if (!world_mt.exists("player_backend")) {
2266 errorstream << "Please specify your current backend in world.mt:"
2268 << " player_backend = {files|sqlite3|leveldb|postgresql}"
2273 std::string backend = world_mt.get("player_backend");
2274 if (backend == migrate_to) {
2275 errorstream << "Cannot migrate: new backend is same"
2276 << " as the old one" << std::endl;
2280 const std::string players_backup_path = game_params.world_path + DIR_DELIM
2283 if (backend == "files") {
2284 // Create backup directory
2285 fs::CreateDir(players_backup_path);
2289 PlayerDatabase *srcdb = ServerEnvironment::openPlayerDatabase(backend,
2290 game_params.world_path, world_mt);
2291 PlayerDatabase *dstdb = ServerEnvironment::openPlayerDatabase(migrate_to,
2292 game_params.world_path, world_mt);
2294 std::vector<std::string> player_list;
2295 srcdb->listPlayers(player_list);
2296 for (std::vector<std::string>::const_iterator it = player_list.begin();
2297 it != player_list.end(); ++it) {
2298 actionstream << "Migrating player " << it->c_str() << std::endl;
2299 RemotePlayer player(it->c_str(), NULL);
2300 PlayerSAO playerSAO(NULL, &player, 15000, false);
2302 srcdb->loadPlayer(&player, &playerSAO);
2304 playerSAO.finalize(&player, std::set<std::string>());
2305 player.setPlayerSAO(&playerSAO);
2307 dstdb->savePlayer(&player);
2309 // For files source, move player files to backup dir
2310 if (backend == "files") {
2312 game_params.world_path + DIR_DELIM + "players" + DIR_DELIM + (*it),
2313 players_backup_path + DIR_DELIM + (*it));
2317 actionstream << "Successfully migrated " << player_list.size() << " players"
2319 world_mt.set("player_backend", migrate_to);
2320 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2321 errorstream << "Failed to update world.mt!" << std::endl;
2323 actionstream << "world.mt updated" << std::endl;
2325 // When migration is finished from file backend, remove players directory if empty
2326 if (backend == "files") {
2327 fs::DeleteSingleFileOrEmptyDirectory(game_params.world_path + DIR_DELIM
2334 } catch (BaseException &e) {
2335 errorstream << "An error occurred during migration: " << e.what() << std::endl;
2341 AuthDatabase *ServerEnvironment::openAuthDatabase(
2342 const std::string &name, const std::string &savedir, const Settings &conf)
2344 if (name == "sqlite3")
2345 return new AuthDatabaseSQLite3(savedir);
2348 if (name == "postgresql") {
2349 std::string connect_string;
2350 conf.getNoEx("pgsql_auth_connection", connect_string);
2351 return new AuthDatabasePostgreSQL(connect_string);
2355 if (name == "files")
2356 return new AuthDatabaseFiles(savedir);
2359 if (name == "leveldb")
2360 return new AuthDatabaseLevelDB(savedir);
2363 throw BaseException(std::string("Database backend ") + name + " not supported.");
2366 bool ServerEnvironment::migrateAuthDatabase(
2367 const GameParams &game_params, const Settings &cmd_args)
2369 std::string migrate_to = cmd_args.get("migrate-auth");
2371 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2372 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2373 errorstream << "Cannot read world.mt!" << std::endl;
2377 std::string backend = "files";
2378 if (world_mt.exists("auth_backend"))
2379 backend = world_mt.get("auth_backend");
2381 warningstream << "No auth_backend found in world.mt, "
2382 "assuming \"files\"." << std::endl;
2384 if (backend == migrate_to) {
2385 errorstream << "Cannot migrate: new backend is same"
2386 << " as the old one" << std::endl;
2391 const std::unique_ptr<AuthDatabase> srcdb(ServerEnvironment::openAuthDatabase(
2392 backend, game_params.world_path, world_mt));
2393 const std::unique_ptr<AuthDatabase> dstdb(ServerEnvironment::openAuthDatabase(
2394 migrate_to, game_params.world_path, world_mt));
2396 std::vector<std::string> names_list;
2397 srcdb->listNames(names_list);
2398 for (const std::string &name : names_list) {
2399 actionstream << "Migrating auth entry for " << name << std::endl;
2401 AuthEntry authEntry;
2402 success = srcdb->getAuth(name, authEntry);
2403 success = success && dstdb->createAuth(authEntry);
2405 errorstream << "Failed to migrate " << name << std::endl;
2408 actionstream << "Successfully migrated " << names_list.size()
2409 << " auth entries" << std::endl;
2410 world_mt.set("auth_backend", migrate_to);
2411 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2412 errorstream << "Failed to update world.mt!" << std::endl;
2414 actionstream << "world.mt updated" << std::endl;
2416 if (backend == "files") {
2417 // special-case files migration:
2418 // move auth.txt to auth.txt.bak if possible
2419 std::string auth_txt_path =
2420 game_params.world_path + DIR_DELIM + "auth.txt";
2421 std::string auth_bak_path = auth_txt_path + ".bak";
2422 if (!fs::PathExists(auth_bak_path))
2423 if (fs::Rename(auth_txt_path, auth_bak_path))
2424 actionstream << "Renamed auth.txt to auth.txt.bak"
2427 errorstream << "Could not rename auth.txt to "
2428 "auth.txt.bak" << std::endl;
2430 warningstream << "auth.txt.bak already exists, auth.txt "
2431 "not renamed" << std::endl;
2434 } catch (BaseException &e) {
2435 errorstream << "An error occurred during migration: " << e.what()