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 *scriptIface, Server *server,
393 const std::string &path_world, MetricsBackend *mb):
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);
461 m_step_time_counter = mb->addCounter(
462 "minetest_env_step_time", "Time spent in environment step (in microseconds)");
464 m_active_block_gauge = mb->addGauge(
465 "minetest_env_active_blocks", "Number of active blocks");
467 m_active_object_gauge = mb->addGauge(
468 "minetest_env_active_objects", "Number of active objects");
471 ServerEnvironment::~ServerEnvironment()
473 // Clear active block list.
474 // This makes the next one delete all active objects.
475 m_active_blocks.clear();
477 // Convert all objects to static and delete the active objects
478 deactivateFarObjects(true);
483 // Delete ActiveBlockModifiers
484 for (ABMWithState &m_abm : m_abms) {
488 // Deallocate players
489 for (RemotePlayer *m_player : m_players) {
493 delete m_player_database;
494 delete m_auth_database;
497 Map & ServerEnvironment::getMap()
502 ServerMap & ServerEnvironment::getServerMap()
507 RemotePlayer *ServerEnvironment::getPlayer(const session_t peer_id)
509 for (RemotePlayer *player : m_players) {
510 if (player->getPeerId() == peer_id)
516 RemotePlayer *ServerEnvironment::getPlayer(const char* name)
518 for (RemotePlayer *player : m_players) {
519 if (strcmp(player->getName(), name) == 0)
525 void ServerEnvironment::addPlayer(RemotePlayer *player)
528 Check that peer_ids are unique.
529 Also check that names are unique.
530 Exception: there can be multiple players with peer_id=0
532 // If peer id is non-zero, it has to be unique.
533 if (player->getPeerId() != PEER_ID_INEXISTENT)
534 FATAL_ERROR_IF(getPlayer(player->getPeerId()) != NULL, "Peer id not unique");
535 // Name has to be unique.
536 FATAL_ERROR_IF(getPlayer(player->getName()) != NULL, "Player name not unique");
538 m_players.push_back(player);
541 void ServerEnvironment::removePlayer(RemotePlayer *player)
543 for (std::vector<RemotePlayer *>::iterator it = m_players.begin();
544 it != m_players.end(); ++it) {
545 if ((*it) == player) {
553 bool ServerEnvironment::removePlayerFromDatabase(const std::string &name)
555 return m_player_database->removePlayer(name);
558 void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason,
559 const std::string &str_reason, bool reconnect)
561 for (RemotePlayer *player : m_players)
562 m_server->DenyAccess(player->getPeerId(), reason, str_reason, reconnect);
565 void ServerEnvironment::saveLoadedPlayers(bool force)
567 for (RemotePlayer *player : m_players) {
568 if (force || player->checkModified() || (player->getPlayerSAO() &&
569 player->getPlayerSAO()->getMeta().isModified())) {
571 m_player_database->savePlayer(player);
572 } catch (DatabaseException &e) {
573 errorstream << "Failed to save player " << player->getName() << " exception: "
574 << e.what() << std::endl;
581 void ServerEnvironment::savePlayer(RemotePlayer *player)
584 m_player_database->savePlayer(player);
585 } catch (DatabaseException &e) {
586 errorstream << "Failed to save player " << player->getName() << " exception: "
587 << e.what() << std::endl;
592 PlayerSAO *ServerEnvironment::loadPlayer(RemotePlayer *player, bool *new_player,
593 session_t peer_id, bool is_singleplayer)
595 PlayerSAO *playersao = new PlayerSAO(this, player, peer_id, is_singleplayer);
596 // Create player if it doesn't exist
597 if (!m_player_database->loadPlayer(player, playersao)) {
599 // Set player position
600 infostream << "Server: Finding spawn place for player \""
601 << player->getName() << "\"" << std::endl;
602 playersao->setBasePosition(m_server->findSpawnPos());
604 // Make sure the player is saved
605 player->setModified(true);
607 // If the player exists, ensure that they respawn inside legal bounds
608 // This fixes an assert crash when the player can't be added
609 // to the environment
610 if (objectpos_over_limit(playersao->getBasePosition())) {
611 actionstream << "Respawn position for player \""
612 << player->getName() << "\" outside limits, resetting" << std::endl;
613 playersao->setBasePosition(m_server->findSpawnPos());
617 // Add player to environment
620 /* Clean up old HUD elements from previous sessions */
623 /* Add object to environment */
624 addActiveObject(playersao);
626 // Update active blocks asap so objects in those blocks appear on the client
627 m_force_update_active_blocks = true;
632 void ServerEnvironment::saveMeta()
637 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
639 // Open file and serialize
640 std::ostringstream ss(std::ios_base::binary);
642 Settings args("EnvArgsEnd");
643 args.setU64("game_time", m_game_time);
644 args.setU64("time_of_day", getTimeOfDay());
645 args.setU64("last_clear_objects_time", m_last_clear_objects_time);
646 args.setU64("lbm_introduction_times_version", 1);
647 args.set("lbm_introduction_times",
648 m_lbm_mgr.createIntroductionTimesString());
649 args.setU64("day_count", m_day_count);
652 if(!fs::safeWriteToFile(path, ss.str()))
654 infostream<<"ServerEnvironment::saveMeta(): Failed to write "
656 throw SerializationError("Couldn't save env meta");
660 void ServerEnvironment::loadMeta()
662 SANITY_CHECK(!m_meta_loaded);
663 m_meta_loaded = true;
665 // If file doesn't exist, load default environment metadata
666 if (!fs::PathExists(m_path_world + DIR_DELIM "env_meta.txt")) {
667 infostream << "ServerEnvironment: Loading default environment metadata"
673 infostream << "ServerEnvironment: Loading environment metadata" << std::endl;
675 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
677 // Open file and deserialize
678 std::ifstream is(path.c_str(), std::ios_base::binary);
680 infostream << "ServerEnvironment::loadMeta(): Failed to open "
681 << path << std::endl;
682 throw SerializationError("Couldn't load env meta");
685 Settings args("EnvArgsEnd");
687 if (!args.parseConfigLines(is)) {
688 throw SerializationError("ServerEnvironment::loadMeta(): "
689 "EnvArgsEnd not found!");
693 m_game_time = args.getU64("game_time");
694 } catch (SettingNotFoundException &e) {
695 // Getting this is crucial, otherwise timestamps are useless
696 throw SerializationError("Couldn't load env meta game_time");
699 setTimeOfDay(args.exists("time_of_day") ?
700 // set day to early morning by default
701 args.getU64("time_of_day") : 5250);
703 m_last_clear_objects_time = args.exists("last_clear_objects_time") ?
704 // If missing, do as if clearObjects was never called
705 args.getU64("last_clear_objects_time") : 0;
707 std::string lbm_introduction_times;
709 u64 ver = args.getU64("lbm_introduction_times_version");
711 lbm_introduction_times = args.get("lbm_introduction_times");
713 infostream << "ServerEnvironment::loadMeta(): Non-supported"
714 << " introduction time version " << ver << std::endl;
716 } catch (SettingNotFoundException &e) {
717 // No problem, this is expected. Just continue with an empty string
719 m_lbm_mgr.loadIntroductionTimes(lbm_introduction_times, m_server, m_game_time);
721 m_day_count = args.exists("day_count") ?
722 args.getU64("day_count") : 0;
726 * called if env_meta.txt doesn't exist (e.g. new world)
728 void ServerEnvironment::loadDefaultMeta()
730 m_lbm_mgr.loadIntroductionTimes("", m_server, m_game_time);
735 ActiveBlockModifier *abm;
737 std::vector<content_t> required_neighbors;
738 bool check_required_neighbors; // false if required_neighbors is known to be empty
746 ServerEnvironment *m_env;
747 std::vector<std::vector<ActiveABM> *> m_aabms;
749 ABMHandler(std::vector<ABMWithState> &abms,
750 float dtime_s, ServerEnvironment *env,
756 const NodeDefManager *ndef = env->getGameDef()->ndef();
757 for (ABMWithState &abmws : abms) {
758 ActiveBlockModifier *abm = abmws.abm;
759 float trigger_interval = abm->getTriggerInterval();
760 if(trigger_interval < 0.001)
761 trigger_interval = 0.001;
762 float actual_interval = dtime_s;
764 abmws.timer += dtime_s;
765 if(abmws.timer < trigger_interval)
767 abmws.timer -= trigger_interval;
768 actual_interval = trigger_interval;
770 float chance = abm->getTriggerChance();
775 if (abm->getSimpleCatchUp()) {
776 float intervals = actual_interval / trigger_interval;
779 aabm.chance = chance / intervals;
783 aabm.chance = chance;
786 aabm.min_y = abm->getMinY();
787 aabm.max_y = abm->getMaxY();
790 const std::vector<std::string> &required_neighbors_s =
791 abm->getRequiredNeighbors();
792 for (const std::string &required_neighbor_s : required_neighbors_s) {
793 ndef->getIds(required_neighbor_s, aabm.required_neighbors);
795 aabm.check_required_neighbors = !required_neighbors_s.empty();
798 const std::vector<std::string> &contents_s = abm->getTriggerContents();
799 for (const std::string &content_s : contents_s) {
800 std::vector<content_t> ids;
801 ndef->getIds(content_s, ids);
802 for (content_t c : ids) {
803 if (c >= m_aabms.size())
804 m_aabms.resize(c + 256, NULL);
806 m_aabms[c] = new std::vector<ActiveABM>;
807 m_aabms[c]->push_back(aabm);
815 for (auto &aabms : m_aabms)
819 // Find out how many objects the given block and its neighbours contain.
820 // Returns the number of objects in the block, and also in 'wider' the
821 // number of objects in the block and all its neighbours. The latter
822 // may an estimate if any neighbours are unloaded.
823 u32 countObjects(MapBlock *block, ServerMap * map, u32 &wider)
826 u32 wider_unknown_count = 0;
827 for(s16 x=-1; x<=1; x++)
828 for(s16 y=-1; y<=1; y++)
829 for(s16 z=-1; z<=1; z++)
831 MapBlock *block2 = map->getBlockNoCreateNoEx(
832 block->getPos() + v3s16(x,y,z));
834 wider_unknown_count++;
837 wider += block2->m_static_objects.m_active.size()
838 + block2->m_static_objects.m_stored.size();
841 u32 active_object_count = block->m_static_objects.m_active.size();
842 u32 wider_known_count = 3*3*3 - wider_unknown_count;
843 wider += wider_unknown_count * wider / wider_known_count;
844 return active_object_count;
847 void apply(MapBlock *block, int &blocks_scanned, int &abms_run, int &blocks_cached)
849 if(m_aabms.empty() || block->isDummy())
852 // Check the content type cache first
853 // to see whether there are any ABMs
854 // to be run at all for this block.
855 if (block->contents_cached) {
857 bool run_abms = false;
858 for (content_t c : block->contents) {
859 if (c < m_aabms.size() && m_aabms[c]) {
868 block->contents.clear();
872 ServerMap *map = &m_env->getServerMap();
874 u32 active_object_count_wider;
875 u32 active_object_count = this->countObjects(block, map, active_object_count_wider);
876 m_env->m_added_objects = 0;
879 for(p0.X=0; p0.X<MAP_BLOCKSIZE; p0.X++)
880 for(p0.Y=0; p0.Y<MAP_BLOCKSIZE; p0.Y++)
881 for(p0.Z=0; p0.Z<MAP_BLOCKSIZE; p0.Z++)
883 const MapNode &n = block->getNodeUnsafe(p0);
884 content_t c = n.getContent();
885 // Cache content types as we go
886 if (!block->contents_cached && !block->do_not_cache_contents) {
887 block->contents.insert(c);
888 if (block->contents.size() > 64) {
889 // Too many different nodes... don't try to cache
890 block->do_not_cache_contents = true;
891 block->contents.clear();
895 if (c >= m_aabms.size() || !m_aabms[c])
898 v3s16 p = p0 + block->getPosRelative();
899 for (ActiveABM &aabm : *m_aabms[c]) {
900 if ((p.Y < aabm.min_y) || (p.Y > aabm.max_y))
903 if (myrand() % aabm.chance != 0)
907 if (aabm.check_required_neighbors) {
909 for(p1.X = p0.X-1; p1.X <= p0.X+1; p1.X++)
910 for(p1.Y = p0.Y-1; p1.Y <= p0.Y+1; p1.Y++)
911 for(p1.Z = p0.Z-1; p1.Z <= p0.Z+1; p1.Z++)
916 if (block->isValidPosition(p1)) {
917 // if the neighbor is found on the same map block
918 // get it straight from there
919 const MapNode &n = block->getNodeUnsafe(p1);
922 // otherwise consult the map
923 MapNode n = map->getNode(p1 + block->getPosRelative());
926 if (CONTAINS(aabm.required_neighbors, c))
929 // No required neighbor found
935 // Call all the trigger variations
936 aabm.abm->trigger(m_env, p, n);
937 aabm.abm->trigger(m_env, p, n,
938 active_object_count, active_object_count_wider);
940 // Count surrounding objects again if the abms added any
941 if(m_env->m_added_objects > 0) {
942 active_object_count = countObjects(block, map, active_object_count_wider);
943 m_env->m_added_objects = 0;
947 block->contents_cached = !block->do_not_cache_contents;
951 void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime)
953 // Reset usage timer immediately, otherwise a block that becomes active
954 // again at around the same time as it would normally be unloaded will
955 // get unloaded incorrectly. (I think this still leaves a small possibility
956 // of a race condition between this and server::AsyncRunStep, which only
957 // some kind of synchronisation will fix, but it at least reduces the window
958 // of opportunity for it to break from seconds to nanoseconds)
959 block->resetUsageTimer();
961 // Get time difference
963 u32 stamp = block->getTimestamp();
964 if (m_game_time > stamp && stamp != BLOCK_TIMESTAMP_UNDEFINED)
965 dtime_s = m_game_time - stamp;
966 dtime_s += additional_dtime;
968 /*infostream<<"ServerEnvironment::activateBlock(): block timestamp: "
969 <<stamp<<", game time: "<<m_game_time<<std::endl;*/
971 // Remove stored static objects if clearObjects was called since block's timestamp
972 if (stamp == BLOCK_TIMESTAMP_UNDEFINED || stamp < m_last_clear_objects_time) {
973 block->m_static_objects.m_stored.clear();
974 // do not set changed flag to avoid unnecessary mapblock writes
977 // Set current time as timestamp
978 block->setTimestampNoChangedFlag(m_game_time);
980 /*infostream<<"ServerEnvironment::activateBlock(): block is "
981 <<dtime_s<<" seconds old."<<std::endl;*/
983 // Activate stored objects
984 activateObjects(block, dtime_s);
986 /* Handle LoadingBlockModifiers */
987 m_lbm_mgr.applyLBMs(this, block, stamp);
990 std::vector<NodeTimer> elapsed_timers =
991 block->m_node_timers.step((float)dtime_s);
992 if (!elapsed_timers.empty()) {
994 for (const NodeTimer &elapsed_timer : elapsed_timers) {
995 n = block->getNodeNoEx(elapsed_timer.position);
996 v3s16 p = elapsed_timer.position + block->getPosRelative();
997 if (m_script->node_on_timer(p, n, elapsed_timer.elapsed))
998 block->setNodeTimer(NodeTimer(elapsed_timer.timeout, 0,
999 elapsed_timer.position));
1004 void ServerEnvironment::addActiveBlockModifier(ActiveBlockModifier *abm)
1006 m_abms.emplace_back(abm);
1009 void ServerEnvironment::addLoadingBlockModifierDef(LoadingBlockModifierDef *lbm)
1011 m_lbm_mgr.addLBMDef(lbm);
1014 bool ServerEnvironment::setNode(v3s16 p, const MapNode &n)
1016 const NodeDefManager *ndef = m_server->ndef();
1017 MapNode n_old = m_map->getNode(p);
1019 const ContentFeatures &cf_old = ndef->get(n_old);
1022 if (cf_old.has_on_destruct)
1023 m_script->node_on_destruct(p, n_old);
1026 if (!m_map->addNodeWithEvent(p, n))
1029 // Update active VoxelManipulator if a mapgen thread
1030 m_map->updateVManip(p);
1032 // Call post-destructor
1033 if (cf_old.has_after_destruct)
1034 m_script->node_after_destruct(p, n_old);
1036 // Retrieve node content features
1037 // if new node is same as old, reuse old definition to prevent a lookup
1038 const ContentFeatures &cf_new = n_old == n ? cf_old : ndef->get(n);
1041 if (cf_new.has_on_construct)
1042 m_script->node_on_construct(p, n);
1047 bool ServerEnvironment::removeNode(v3s16 p)
1049 const NodeDefManager *ndef = m_server->ndef();
1050 MapNode n_old = m_map->getNode(p);
1053 if (ndef->get(n_old).has_on_destruct)
1054 m_script->node_on_destruct(p, n_old);
1057 // This is slightly optimized compared to addNodeWithEvent(air)
1058 if (!m_map->removeNodeWithEvent(p))
1061 // Update active VoxelManipulator if a mapgen thread
1062 m_map->updateVManip(p);
1064 // Call post-destructor
1065 if (ndef->get(n_old).has_after_destruct)
1066 m_script->node_after_destruct(p, n_old);
1068 // Air doesn't require constructor
1072 bool ServerEnvironment::swapNode(v3s16 p, const MapNode &n)
1074 if (!m_map->addNodeWithEvent(p, n, false))
1077 // Update active VoxelManipulator if a mapgen thread
1078 m_map->updateVManip(p);
1083 u8 ServerEnvironment::findSunlight(v3s16 pos) const
1085 // Directions for neighbouring nodes with specified order
1086 static const v3s16 dirs[] = {
1087 v3s16(-1, 0, 0), v3s16(1, 0, 0), v3s16(0, 0, -1), v3s16(0, 0, 1),
1088 v3s16(0, -1, 0), v3s16(0, 1, 0)
1091 const NodeDefManager *ndef = m_server->ndef();
1093 // found_light remembers the highest known sunlight value at pos
1096 struct stack_entry {
1100 std::stack<stack_entry> stack;
1101 stack.push({pos, 0});
1103 std::unordered_map<s64, s8> dists;
1104 dists[MapDatabase::getBlockAsInteger(pos)] = 0;
1106 while (!stack.empty()) {
1107 struct stack_entry e = stack.top();
1110 v3s16 currentPos = e.pos;
1111 s8 dist = e.dist + 1;
1113 for (const v3s16& off : dirs) {
1114 v3s16 neighborPos = currentPos + off;
1115 s64 neighborHash = MapDatabase::getBlockAsInteger(neighborPos);
1117 // Do not walk neighborPos multiple times unless the distance to the start
1118 // position is shorter
1119 auto it = dists.find(neighborHash);
1120 if (it != dists.end() && dist >= it->second)
1124 bool is_position_ok;
1125 MapNode node = m_map->getNode(neighborPos, &is_position_ok);
1126 if (!is_position_ok) {
1127 // This happens very rarely because the map at currentPos is loaded
1128 m_map->emergeBlock(neighborPos, false);
1129 node = m_map->getNode(neighborPos, &is_position_ok);
1130 if (!is_position_ok)
1131 continue; // not generated
1134 const ContentFeatures &def = ndef->get(node);
1135 if (!def.sunlight_propagates) {
1136 // Do not test propagation here again
1137 dists[neighborHash] = -1;
1141 // Sunlight could have come from here
1142 dists[neighborHash] = dist;
1143 u8 daylight = node.param1 & 0x0f;
1145 // In the special case where sunlight shines from above and thus
1146 // does not decrease with upwards distance, daylight is always
1147 // bigger than nightlight, which never reaches 15
1148 int possible_finlight = daylight - dist;
1149 if (possible_finlight <= found_light) {
1150 // Light from here cannot make a brighter light at currentPos than
1155 u8 nightlight = node.param1 >> 4;
1156 if (daylight > nightlight) {
1157 // Found a valid daylight
1158 found_light = possible_finlight;
1160 // Sunlight may be darker, so walk the neighbours
1161 stack.push({neighborPos, dist});
1168 void ServerEnvironment::clearObjects(ClearObjectsMode mode)
1170 infostream << "ServerEnvironment::clearObjects(): "
1171 << "Removing all active objects" << std::endl;
1172 auto cb_removal = [this] (ServerActiveObject *obj, u16 id) {
1173 if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER)
1176 // Delete static object if block is loaded
1177 deleteStaticFromBlock(obj, id, MOD_REASON_CLEAR_ALL_OBJECTS, true);
1179 // If known by some client, don't delete immediately
1180 if (obj->m_known_by_count > 0) {
1181 obj->markForRemoval();
1185 // Tell the object about removal
1186 obj->removingFromEnvironment();
1187 // Deregister in scripting api
1188 m_script->removeObjectReference(obj);
1190 // Delete active object
1191 if (obj->environmentDeletes())
1197 m_ao_manager.clear(cb_removal);
1199 // Get list of loaded blocks
1200 std::vector<v3s16> loaded_blocks;
1201 infostream << "ServerEnvironment::clearObjects(): "
1202 << "Listing all loaded blocks" << std::endl;
1203 m_map->listAllLoadedBlocks(loaded_blocks);
1204 infostream << "ServerEnvironment::clearObjects(): "
1205 << "Done listing all loaded blocks: "
1206 << loaded_blocks.size()<<std::endl;
1208 // Get list of loadable blocks
1209 std::vector<v3s16> loadable_blocks;
1210 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1211 infostream << "ServerEnvironment::clearObjects(): "
1212 << "Listing all loadable blocks" << std::endl;
1213 m_map->listAllLoadableBlocks(loadable_blocks);
1214 infostream << "ServerEnvironment::clearObjects(): "
1215 << "Done listing all loadable blocks: "
1216 << loadable_blocks.size() << std::endl;
1218 loadable_blocks = loaded_blocks;
1221 actionstream << "ServerEnvironment::clearObjects(): "
1222 << "Now clearing objects in " << loadable_blocks.size()
1223 << " blocks" << std::endl;
1225 // Grab a reference on each loaded block to avoid unloading it
1226 for (v3s16 p : loaded_blocks) {
1227 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1228 assert(block != NULL);
1232 // Remove objects in all loadable blocks
1233 u32 unload_interval = U32_MAX;
1234 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1235 unload_interval = g_settings->getS32("max_clearobjects_extra_loaded_blocks");
1236 unload_interval = MYMAX(unload_interval, 1);
1238 u32 report_interval = loadable_blocks.size() / 10;
1239 u32 num_blocks_checked = 0;
1240 u32 num_blocks_cleared = 0;
1241 u32 num_objs_cleared = 0;
1242 for (auto i = loadable_blocks.begin();
1243 i != loadable_blocks.end(); ++i) {
1245 MapBlock *block = m_map->emergeBlock(p, false);
1247 errorstream << "ServerEnvironment::clearObjects(): "
1248 << "Failed to emerge block " << PP(p) << std::endl;
1251 u32 num_stored = block->m_static_objects.m_stored.size();
1252 u32 num_active = block->m_static_objects.m_active.size();
1253 if (num_stored != 0 || num_active != 0) {
1254 block->m_static_objects.m_stored.clear();
1255 block->m_static_objects.m_active.clear();
1256 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1257 MOD_REASON_CLEAR_ALL_OBJECTS);
1258 num_objs_cleared += num_stored + num_active;
1259 num_blocks_cleared++;
1261 num_blocks_checked++;
1263 if (report_interval != 0 &&
1264 num_blocks_checked % report_interval == 0) {
1265 float percent = 100.0 * (float)num_blocks_checked /
1266 loadable_blocks.size();
1267 actionstream << "ServerEnvironment::clearObjects(): "
1268 << "Cleared " << num_objs_cleared << " objects"
1269 << " in " << num_blocks_cleared << " blocks ("
1270 << percent << "%)" << std::endl;
1272 if (num_blocks_checked % unload_interval == 0) {
1273 m_map->unloadUnreferencedBlocks();
1276 m_map->unloadUnreferencedBlocks();
1278 // Drop references that were added above
1279 for (v3s16 p : loaded_blocks) {
1280 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1285 m_last_clear_objects_time = m_game_time;
1287 actionstream << "ServerEnvironment::clearObjects(): "
1288 << "Finished: Cleared " << num_objs_cleared << " objects"
1289 << " in " << num_blocks_cleared << " blocks" << std::endl;
1292 void ServerEnvironment::step(float dtime)
1294 ScopeProfiler sp2(g_profiler, "ServerEnv::step()", SPT_AVG);
1295 const auto start_time = porting::getTimeUs();
1297 /* Step time of day */
1298 stepTimeOfDay(dtime);
1301 // NOTE: This is kind of funny on a singleplayer game, but doesn't
1302 // really matter that much.
1303 static thread_local const float server_step =
1304 g_settings->getFloat("dedicated_server_step");
1305 m_recommended_send_interval = server_step;
1311 m_game_time_fraction_counter += dtime;
1312 u32 inc_i = (u32)m_game_time_fraction_counter;
1313 m_game_time += inc_i;
1314 m_game_time_fraction_counter -= (float)inc_i;
1321 ScopeProfiler sp(g_profiler, "ServerEnv: move players", SPT_AVG);
1322 for (RemotePlayer *player : m_players) {
1323 // Ignore disconnected players
1324 if (player->getPeerId() == PEER_ID_INEXISTENT)
1328 player->move(dtime, this, 100 * BS);
1333 Manage active block list
1335 if (m_active_blocks_mgmt_interval.step(dtime, m_cache_active_block_mgmt_interval) ||
1336 m_force_update_active_blocks) {
1337 ScopeProfiler sp(g_profiler, "ServerEnv: update active blocks", SPT_AVG);
1340 Get player block positions
1342 std::vector<PlayerSAO*> players;
1343 players.reserve(m_players.size());
1344 for (RemotePlayer *player : m_players) {
1345 // Ignore disconnected players
1346 if (player->getPeerId() == PEER_ID_INEXISTENT)
1349 PlayerSAO *playersao = player->getPlayerSAO();
1352 players.push_back(playersao);
1356 Update list of active blocks, collecting changes
1358 // use active_object_send_range_blocks since that is max distance
1359 // for active objects sent the client anyway
1360 static thread_local const s16 active_object_range =
1361 g_settings->getS16("active_object_send_range_blocks");
1362 static thread_local const s16 active_block_range =
1363 g_settings->getS16("active_block_range");
1364 std::set<v3s16> blocks_removed;
1365 std::set<v3s16> blocks_added;
1366 m_active_blocks.update(players, active_block_range, active_object_range,
1367 blocks_removed, blocks_added);
1370 Handle removed blocks
1373 // Convert active objects that are no more in active blocks to static
1374 deactivateFarObjects(false);
1376 for (const v3s16 &p: blocks_removed) {
1377 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1381 // Set current time as timestamp (and let it set ChangedFlag)
1382 block->setTimestamp(m_game_time);
1389 for (const v3s16 &p: blocks_added) {
1390 MapBlock *block = m_map->getBlockOrEmerge(p);
1392 // TODO: The blocks removed here will only be picked up again
1393 // on the next cycle. To minimize the latency of objects being
1394 // activated we could remember the blocks pending activating
1395 // and activate them instantly as soon as they're loaded.
1396 m_active_blocks.remove(p);
1400 activateBlock(block);
1403 // Some blocks may be removed again by the code above so do this here
1404 m_active_block_gauge->set(m_active_blocks.size());
1406 m_force_update_active_blocks = false;
1409 Mess around in active blocks
1411 if (m_active_blocks_nodemetadata_interval.step(dtime, m_cache_nodetimer_interval)) {
1412 ScopeProfiler sp(g_profiler, "ServerEnv: Run node timers", SPT_AVG);
1414 float dtime = m_cache_nodetimer_interval;
1416 for (const v3s16 &p: m_active_blocks.m_list) {
1417 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1421 // Reset block usage timer
1422 block->resetUsageTimer();
1424 // Set current time as timestamp
1425 block->setTimestampNoChangedFlag(m_game_time);
1426 // If time has changed much from the one on disk,
1427 // set block to be saved when it is unloaded
1428 if(block->getTimestamp() > block->getDiskTimestamp() + 60)
1429 block->raiseModified(MOD_STATE_WRITE_AT_UNLOAD,
1430 MOD_REASON_BLOCK_EXPIRED);
1433 std::vector<NodeTimer> elapsed_timers = block->m_node_timers.step(dtime);
1434 if (!elapsed_timers.empty()) {
1437 for (const NodeTimer &elapsed_timer: elapsed_timers) {
1438 n = block->getNodeNoEx(elapsed_timer.position);
1439 p2 = elapsed_timer.position + block->getPosRelative();
1440 if (m_script->node_on_timer(p2, n, elapsed_timer.elapsed)) {
1441 block->setNodeTimer(NodeTimer(
1442 elapsed_timer.timeout, 0, elapsed_timer.position));
1449 if (m_active_block_modifier_interval.step(dtime, m_cache_abm_interval)) {
1450 ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg per interval", SPT_AVG);
1451 TimeTaker timer("modify in active blocks per interval");
1453 // Initialize handling of ActiveBlockModifiers
1454 ABMHandler abmhandler(m_abms, m_cache_abm_interval, this, true);
1456 int blocks_scanned = 0;
1458 int blocks_cached = 0;
1460 std::vector<v3s16> output(m_active_blocks.m_abm_list.size());
1462 // Shuffle the active blocks so that each block gets an equal chance
1463 // of having its ABMs run.
1464 std::copy(m_active_blocks.m_abm_list.begin(), m_active_blocks.m_abm_list.end(), output.begin());
1465 std::shuffle(output.begin(), output.end(), m_rgen);
1468 // determine the time budget for ABMs
1469 u32 max_time_ms = m_cache_abm_interval * 1000 * m_cache_abm_time_budget;
1470 for (const v3s16 &p : output) {
1471 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1477 // Set current time as timestamp
1478 block->setTimestampNoChangedFlag(m_game_time);
1480 /* Handle ActiveBlockModifiers */
1481 abmhandler.apply(block, blocks_scanned, abms_run, blocks_cached);
1483 u32 time_ms = timer.getTimerTime();
1485 if (time_ms > max_time_ms) {
1486 warningstream << "active block modifiers took "
1487 << time_ms << "ms (processed " << i << " of "
1488 << output.size() << " active blocks)" << std::endl;
1492 g_profiler->avg("ServerEnv: active blocks", m_active_blocks.m_abm_list.size());
1493 g_profiler->avg("ServerEnv: active blocks cached", blocks_cached);
1494 g_profiler->avg("ServerEnv: active blocks scanned for ABMs", blocks_scanned);
1495 g_profiler->avg("ServerEnv: ABMs run", abms_run);
1501 Step script environment (run global on_step())
1503 m_script->environment_Step(dtime);
1505 m_script->stepAsync();
1511 ScopeProfiler sp(g_profiler, "ServerEnv: Run SAO::step()", SPT_AVG);
1513 // This helps the objects to send data at the same time
1514 bool send_recommended = false;
1515 m_send_recommended_timer += dtime;
1516 if (m_send_recommended_timer > getSendRecommendedInterval()) {
1517 m_send_recommended_timer -= getSendRecommendedInterval();
1518 send_recommended = true;
1521 u32 object_count = 0;
1523 auto cb_state = [&] (ServerActiveObject *obj) {
1529 obj->step(dtime, send_recommended);
1530 // Read messages from object
1531 obj->dumpAOMessagesToQueue(m_active_object_messages);
1533 m_ao_manager.step(dtime, cb_state);
1535 m_active_object_gauge->set(object_count);
1539 Manage active objects
1541 if (m_object_management_interval.step(dtime, 0.5)) {
1542 removeRemovedObjects();
1546 Manage particle spawner expiration
1548 if (m_particle_management_interval.step(dtime, 1.0)) {
1549 for (std::unordered_map<u32, float>::iterator i = m_particle_spawners.begin();
1550 i != m_particle_spawners.end(); ) {
1551 //non expiring spawners
1552 if (i->second == PARTICLE_SPAWNER_NO_EXPIRY) {
1558 if (i->second <= 0.f)
1559 m_particle_spawners.erase(i++);
1565 // Send outdated player inventories
1566 for (RemotePlayer *player : m_players) {
1567 if (player->getPeerId() == PEER_ID_INEXISTENT)
1570 PlayerSAO *sao = player->getPlayerSAO();
1571 if (sao && player->inventory.checkModified())
1572 m_server->SendInventory(sao, true);
1575 // Send outdated detached inventories
1576 m_server->sendDetachedInventories(PEER_ID_INEXISTENT, true);
1578 const auto end_time = porting::getTimeUs();
1579 m_step_time_counter->increment(end_time - start_time);
1582 ServerEnvironment::BlockStatus ServerEnvironment::getBlockStatus(v3s16 blockpos)
1584 if (m_active_blocks.contains(blockpos))
1587 const MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos);
1588 if (block && !block->isDummy())
1591 if (m_map->isBlockInQueue(blockpos))
1597 u32 ServerEnvironment::addParticleSpawner(float exptime)
1599 // Timers with lifetime 0 do not expire
1600 float time = exptime > 0.f ? exptime : PARTICLE_SPAWNER_NO_EXPIRY;
1603 for (;;) { // look for unused particlespawner id
1605 std::unordered_map<u32, float>::iterator f = m_particle_spawners.find(id);
1606 if (f == m_particle_spawners.end()) {
1607 m_particle_spawners[id] = time;
1614 u32 ServerEnvironment::addParticleSpawner(float exptime, u16 attached_id)
1616 u32 id = addParticleSpawner(exptime);
1617 m_particle_spawner_attachments[id] = attached_id;
1618 if (ServerActiveObject *obj = getActiveObject(attached_id)) {
1619 obj->attachParticleSpawner(id);
1624 void ServerEnvironment::deleteParticleSpawner(u32 id, bool remove_from_object)
1626 m_particle_spawners.erase(id);
1627 const auto &it = m_particle_spawner_attachments.find(id);
1628 if (it != m_particle_spawner_attachments.end()) {
1629 u16 obj_id = it->second;
1630 ServerActiveObject *sao = getActiveObject(obj_id);
1631 if (sao != NULL && remove_from_object) {
1632 sao->detachParticleSpawner(id);
1634 m_particle_spawner_attachments.erase(id);
1638 u16 ServerEnvironment::addActiveObject(ServerActiveObject *object)
1640 assert(object); // Pre-condition
1642 u16 id = addActiveObjectRaw(object, true, 0);
1647 Finds out what new objects have been added to
1648 inside a radius around a position
1650 void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius,
1652 std::set<u16> ¤t_objects,
1653 std::queue<u16> &added_objects)
1655 f32 radius_f = radius * BS;
1656 f32 player_radius_f = player_radius * BS;
1658 if (player_radius_f < 0.0f)
1659 player_radius_f = 0.0f;
1661 m_ao_manager.getAddedActiveObjectsAroundPos(playersao->getBasePosition(), radius_f,
1662 player_radius_f, current_objects, added_objects);
1666 Finds out what objects have been removed from
1667 inside a radius around a position
1669 void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius,
1671 std::set<u16> ¤t_objects,
1672 std::queue<u16> &removed_objects)
1674 f32 radius_f = radius * BS;
1675 f32 player_radius_f = player_radius * BS;
1677 if (player_radius_f < 0)
1678 player_radius_f = 0;
1680 Go through current_objects; object is removed if:
1681 - object is not found in m_active_objects (this is actually an
1682 error condition; objects should be removed only after all clients
1683 have been informed about removal), or
1684 - object is to be removed or deactivated, or
1685 - object is too far away
1687 for (u16 id : current_objects) {
1688 ServerActiveObject *object = getActiveObject(id);
1690 if (object == NULL) {
1691 infostream << "ServerEnvironment::getRemovedActiveObjects():"
1692 << " object in current_objects is NULL" << std::endl;
1693 removed_objects.push(id);
1697 if (object->isGone()) {
1698 removed_objects.push(id);
1702 f32 distance_f = object->getBasePosition().getDistanceFrom(playersao->getBasePosition());
1703 if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1704 if (distance_f <= player_radius_f || player_radius_f == 0)
1706 } else if (distance_f <= radius_f)
1709 // Object is no longer visible
1710 removed_objects.push(id);
1714 void ServerEnvironment::setStaticForActiveObjectsInBlock(
1715 v3s16 blockpos, bool static_exists, v3s16 static_block)
1717 MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos);
1721 for (auto &so_it : block->m_static_objects.m_active) {
1722 // Get the ServerActiveObject counterpart to this StaticObject
1723 ServerActiveObject *sao = m_ao_manager.getActiveObject(so_it.first);
1725 // If this ever happens, there must be some kind of nasty bug.
1726 errorstream << "ServerEnvironment::setStaticForObjectsInBlock(): "
1727 "Object from MapBlock::m_static_objects::m_active not found "
1728 "in m_active_objects";
1732 sao->m_static_exists = static_exists;
1733 sao->m_static_block = static_block;
1737 bool ServerEnvironment::getActiveObjectMessage(ActiveObjectMessage *dest)
1739 if(m_active_object_messages.empty())
1742 *dest = std::move(m_active_object_messages.front());
1743 m_active_object_messages.pop();
1747 void ServerEnvironment::getSelectedActiveObjects(
1748 const core::line3d<f32> &shootline_on_map,
1749 std::vector<PointedThing> &objects)
1751 std::vector<ServerActiveObject *> objs;
1752 getObjectsInsideRadius(objs, shootline_on_map.start,
1753 shootline_on_map.getLength() + 10.0f, nullptr);
1754 const v3f line_vector = shootline_on_map.getVector();
1756 for (auto obj : objs) {
1759 aabb3f selection_box;
1760 if (!obj->getSelectionBox(&selection_box))
1763 v3f pos = obj->getBasePosition();
1765 aabb3f offsetted_box(selection_box.MinEdge + pos,
1766 selection_box.MaxEdge + pos);
1768 v3f current_intersection;
1769 v3s16 current_normal;
1770 if (boxLineCollision(offsetted_box, shootline_on_map.start, line_vector,
1771 ¤t_intersection, ¤t_normal)) {
1772 objects.emplace_back(
1773 (s16) obj->getId(), current_intersection, current_normal,
1774 (current_intersection - shootline_on_map.start).getLengthSQ());
1780 ************ Private methods *************
1783 u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object,
1784 bool set_changed, u32 dtime_s)
1786 if (!m_ao_manager.registerObject(object)) {
1790 // Register reference in scripting api (must be done before post-init)
1791 m_script->addObjectReference(object);
1792 // Post-initialize object
1793 object->addedToEnvironment(dtime_s);
1795 // Add static data to block
1796 if (object->isStaticAllowed()) {
1797 // Add static object to active static list of the block
1798 v3f objectpos = object->getBasePosition();
1799 StaticObject s_obj(object, objectpos);
1800 // Add to the block where the object is located in
1801 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
1802 MapBlock *block = m_map->emergeBlock(blockpos);
1804 block->m_static_objects.m_active[object->getId()] = s_obj;
1805 object->m_static_exists = true;
1806 object->m_static_block = blockpos;
1809 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1810 MOD_REASON_ADD_ACTIVE_OBJECT_RAW);
1812 v3s16 p = floatToInt(objectpos, BS);
1813 errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
1814 <<"could not emerge block for storing id="<<object->getId()
1815 <<" statically (pos="<<PP(p)<<")"<<std::endl;
1819 return object->getId();
1823 Remove objects that satisfy (isGone() && m_known_by_count==0)
1825 void ServerEnvironment::removeRemovedObjects()
1827 ScopeProfiler sp(g_profiler, "ServerEnvironment::removeRemovedObjects()", SPT_AVG);
1829 auto clear_cb = [this] (ServerActiveObject *obj, u16 id) {
1830 // This shouldn't happen but check it
1832 errorstream << "ServerEnvironment::removeRemovedObjects(): "
1833 << "NULL object found. id=" << id << std::endl;
1838 We will handle objects marked for removal or deactivation
1844 Delete static data from block if removed
1846 if (obj->isPendingRemoval())
1847 deleteStaticFromBlock(obj, id, MOD_REASON_REMOVE_OBJECTS_REMOVE, false);
1849 // If still known by clients, don't actually remove. On some future
1850 // invocation this will be 0, which is when removal will continue.
1851 if(obj->m_known_by_count > 0)
1855 Move static data from active to stored if deactivated
1857 if (!obj->isPendingRemoval() && obj->m_static_exists) {
1858 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
1860 const auto i = block->m_static_objects.m_active.find(id);
1861 if (i != block->m_static_objects.m_active.end()) {
1862 block->m_static_objects.m_stored.push_back(i->second);
1863 block->m_static_objects.m_active.erase(id);
1864 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1865 MOD_REASON_REMOVE_OBJECTS_DEACTIVATE);
1867 warningstream << "ServerEnvironment::removeRemovedObjects(): "
1868 << "id=" << id << " m_static_exists=true but "
1869 << "static data doesn't actually exist in "
1870 << PP(obj->m_static_block) << std::endl;
1873 infostream << "Failed to emerge block from which an object to "
1874 << "be deactivated was loaded from. id=" << id << std::endl;
1878 // Tell the object about removal
1879 obj->removingFromEnvironment();
1880 // Deregister in scripting api
1881 m_script->removeObjectReference(obj);
1884 if (obj->environmentDeletes())
1890 m_ao_manager.clear(clear_cb);
1893 static void print_hexdump(std::ostream &o, const std::string &data)
1895 const int linelength = 16;
1896 for(int l=0; ; l++){
1897 int i0 = linelength * l;
1898 bool at_end = false;
1899 int thislinelength = linelength;
1900 if(i0 + thislinelength > (int)data.size()){
1901 thislinelength = data.size() - i0;
1904 for(int di=0; di<linelength; di++){
1907 if(di<thislinelength)
1908 porting::mt_snprintf(buf, sizeof(buf), "%.2x ", data[i]);
1910 porting::mt_snprintf(buf, sizeof(buf), " ");
1914 for(int di=0; di<thislinelength; di++){
1927 ServerActiveObject* ServerEnvironment::createSAO(ActiveObjectType type, v3f pos,
1928 const std::string &data)
1931 case ACTIVEOBJECT_TYPE_LUAENTITY:
1932 return new LuaEntitySAO(this, pos, data);
1934 warningstream << "ServerActiveObject: No factory for type=" << type << std::endl;
1940 Convert stored objects from blocks near the players to active.
1942 void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s)
1947 // Ignore if no stored objects (to not set changed flag)
1948 if(block->m_static_objects.m_stored.empty())
1951 verbosestream<<"ServerEnvironment::activateObjects(): "
1952 <<"activating objects of block "<<PP(block->getPos())
1953 <<" ("<<block->m_static_objects.m_stored.size()
1954 <<" objects)"<<std::endl;
1955 bool large_amount = (block->m_static_objects.m_stored.size() > g_settings->getU16("max_objects_per_block"));
1957 errorstream<<"suspiciously large amount of objects detected: "
1958 <<block->m_static_objects.m_stored.size()<<" in "
1959 <<PP(block->getPos())
1960 <<"; removing all of them."<<std::endl;
1961 // Clear stored list
1962 block->m_static_objects.m_stored.clear();
1963 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1964 MOD_REASON_TOO_MANY_OBJECTS);
1968 // Activate stored objects
1969 std::vector<StaticObject> new_stored;
1970 for (const StaticObject &s_obj : block->m_static_objects.m_stored) {
1971 // Create an active object from the data
1972 ServerActiveObject *obj = createSAO((ActiveObjectType) s_obj.type, s_obj.pos,
1974 // If couldn't create object, store static data back.
1976 errorstream<<"ServerEnvironment::activateObjects(): "
1977 <<"failed to create active object from static object "
1978 <<"in block "<<PP(s_obj.pos/BS)
1979 <<" type="<<(int)s_obj.type<<" data:"<<std::endl;
1980 print_hexdump(verbosestream, s_obj.data);
1982 new_stored.push_back(s_obj);
1985 verbosestream<<"ServerEnvironment::activateObjects(): "
1986 <<"activated static object pos="<<PP(s_obj.pos/BS)
1987 <<" type="<<(int)s_obj.type<<std::endl;
1988 // This will also add the object to the active static list
1989 addActiveObjectRaw(obj, false, dtime_s);
1992 // Clear stored list
1993 block->m_static_objects.m_stored.clear();
1994 // Add leftover failed stuff to stored list
1995 for (const StaticObject &s_obj : new_stored) {
1996 block->m_static_objects.m_stored.push_back(s_obj);
2000 Note: Block hasn't really been modified here.
2001 The objects have just been activated and moved from the stored
2002 static list to the active static list.
2003 As such, the block is essentially the same.
2004 Thus, do not call block->raiseModified(MOD_STATE_WRITE_NEEDED).
2005 Otherwise there would be a huge amount of unnecessary I/O.
2010 Convert objects that are not standing inside active blocks to static.
2012 If m_known_by_count != 0, active object is not deleted, but static
2013 data is still updated.
2015 If force_delete is set, active object is deleted nevertheless. It
2016 shall only be set so in the destructor of the environment.
2018 If block wasn't generated (not in memory or on disk),
2020 void ServerEnvironment::deactivateFarObjects(bool _force_delete)
2022 auto cb_deactivate = [this, _force_delete] (ServerActiveObject *obj, u16 id) {
2023 // force_delete might be overriden per object
2024 bool force_delete = _force_delete;
2026 // Do not deactivate if disallowed
2027 if (!force_delete && !obj->shouldUnload())
2030 // removeRemovedObjects() is responsible for these
2031 if (!force_delete && obj->isGone())
2034 const v3f &objectpos = obj->getBasePosition();
2036 // The block in which the object resides in
2037 v3s16 blockpos_o = getNodeBlockPos(floatToInt(objectpos, BS));
2039 // If object's static data is stored in a deactivated block and object
2040 // is actually located in an active block, re-save to the block in
2041 // which the object is actually located in.
2042 if (!force_delete && obj->m_static_exists &&
2043 !m_active_blocks.contains(obj->m_static_block) &&
2044 m_active_blocks.contains(blockpos_o)) {
2046 // Delete from block where object was located
2047 deleteStaticFromBlock(obj, id, MOD_REASON_STATIC_DATA_REMOVED, false);
2049 StaticObject s_obj(obj, objectpos);
2050 // Save to block where object is located
2051 saveStaticToBlock(blockpos_o, id, obj, s_obj, MOD_REASON_STATIC_DATA_ADDED);
2056 // If block is still active, don't remove
2057 bool still_active = obj->isStaticAllowed() ?
2058 m_active_blocks.contains(blockpos_o) :
2059 getMap().getBlockNoCreateNoEx(blockpos_o) != nullptr;
2060 if (!force_delete && still_active)
2063 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
2064 << "deactivating object id=" << id << " on inactive block "
2065 << PP(blockpos_o) << std::endl;
2067 // If known by some client, don't immediately delete.
2068 bool pending_delete = (obj->m_known_by_count > 0 && !force_delete);
2071 Update the static data
2073 if (obj->isStaticAllowed()) {
2074 // Create new static object
2075 StaticObject s_obj(obj, objectpos);
2077 bool stays_in_same_block = false;
2078 bool data_changed = true;
2080 // Check if static data has changed considerably
2081 if (obj->m_static_exists) {
2082 if (obj->m_static_block == blockpos_o)
2083 stays_in_same_block = true;
2085 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
2088 const auto n = block->m_static_objects.m_active.find(id);
2089 if (n != block->m_static_objects.m_active.end()) {
2090 StaticObject static_old = n->second;
2092 float save_movem = obj->getMinimumSavedMovement();
2094 if (static_old.data == s_obj.data &&
2095 (static_old.pos - objectpos).getLength() < save_movem)
2096 data_changed = false;
2098 warningstream << "ServerEnvironment::deactivateFarObjects(): "
2099 << "id=" << id << " m_static_exists=true but "
2100 << "static data doesn't actually exist in "
2101 << PP(obj->m_static_block) << std::endl;
2107 While changes are always saved, blocks are only marked as modified
2108 if the object has moved or different staticdata. (see above)
2110 bool shall_be_written = (!stays_in_same_block || data_changed);
2111 u32 reason = shall_be_written ? MOD_REASON_STATIC_DATA_CHANGED : MOD_REASON_UNKNOWN;
2113 // Delete old static object
2114 deleteStaticFromBlock(obj, id, reason, false);
2116 // Add to the block where the object is located in
2117 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
2118 u16 store_id = pending_delete ? id : 0;
2119 if (!saveStaticToBlock(blockpos, store_id, obj, s_obj, reason))
2120 force_delete = true;
2123 // Regardless of what happens to the object at this point, deactivate it first.
2124 // This ensures that LuaEntity on_deactivate is always called.
2125 obj->markForDeactivation();
2128 If known by some client, set pending deactivation.
2129 Otherwise delete it immediately.
2131 if (pending_delete && !force_delete) {
2132 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
2133 << "object id=" << id << " is known by clients"
2134 << "; not deleting yet" << std::endl;
2139 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
2140 << "object id=" << id << " is not known by clients"
2141 << "; deleting" << std::endl;
2143 // Tell the object about removal
2144 obj->removingFromEnvironment();
2145 // Deregister in scripting api
2146 m_script->removeObjectReference(obj);
2148 // Delete active object
2149 if (obj->environmentDeletes())
2155 m_ao_manager.clear(cb_deactivate);
2158 void ServerEnvironment::deleteStaticFromBlock(
2159 ServerActiveObject *obj, u16 id, u32 mod_reason, bool no_emerge)
2161 if (!obj->m_static_exists)
2166 block = m_map->getBlockNoCreateNoEx(obj->m_static_block);
2168 block = m_map->emergeBlock(obj->m_static_block, false);
2171 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2172 << " when deleting static data of object from it. id=" << id << std::endl;
2176 block->m_static_objects.remove(id);
2177 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2178 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2180 obj->m_static_exists = false;
2183 bool ServerEnvironment::saveStaticToBlock(
2184 v3s16 blockpos, u16 store_id,
2185 ServerActiveObject *obj, const StaticObject &s_obj,
2188 MapBlock *block = nullptr;
2190 block = m_map->emergeBlock(blockpos);
2191 } catch (InvalidPositionException &e) {
2192 // Handled via NULL pointer
2193 // NOTE: emergeBlock's failure is usually determined by it
2194 // actually returning NULL
2198 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2199 << " when saving static data of object to it. id=" << store_id << std::endl;
2202 if (block->m_static_objects.m_stored.size() >= g_settings->getU16("max_objects_per_block")) {
2203 warningstream << "ServerEnv: Trying to store id = " << store_id
2204 << " statically but block " << PP(blockpos)
2205 << " already contains "
2206 << block->m_static_objects.m_stored.size()
2207 << " objects." << std::endl;
2211 block->m_static_objects.insert(store_id, s_obj);
2212 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2213 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2215 obj->m_static_exists = true;
2216 obj->m_static_block = blockpos;
2221 PlayerDatabase *ServerEnvironment::openPlayerDatabase(const std::string &name,
2222 const std::string &savedir, const Settings &conf)
2225 if (name == "sqlite3")
2226 return new PlayerDatabaseSQLite3(savedir);
2228 if (name == "dummy")
2229 return new Database_Dummy();
2232 if (name == "postgresql") {
2233 std::string connect_string;
2234 conf.getNoEx("pgsql_player_connection", connect_string);
2235 return new PlayerDatabasePostgreSQL(connect_string);
2240 if (name == "leveldb")
2241 return new PlayerDatabaseLevelDB(savedir);
2244 if (name == "files")
2245 return new PlayerDatabaseFiles(savedir + DIR_DELIM + "players");
2247 throw BaseException(std::string("Database backend ") + name + " not supported.");
2250 bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params,
2251 const Settings &cmd_args)
2253 std::string migrate_to = cmd_args.get("migrate-players");
2255 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2256 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2257 errorstream << "Cannot read world.mt!" << std::endl;
2261 if (!world_mt.exists("player_backend")) {
2262 errorstream << "Please specify your current backend in world.mt:"
2264 << " player_backend = {files|sqlite3|leveldb|postgresql}"
2269 std::string backend = world_mt.get("player_backend");
2270 if (backend == migrate_to) {
2271 errorstream << "Cannot migrate: new backend is same"
2272 << " as the old one" << std::endl;
2276 const std::string players_backup_path = game_params.world_path + DIR_DELIM
2279 if (backend == "files") {
2280 // Create backup directory
2281 fs::CreateDir(players_backup_path);
2285 PlayerDatabase *srcdb = ServerEnvironment::openPlayerDatabase(backend,
2286 game_params.world_path, world_mt);
2287 PlayerDatabase *dstdb = ServerEnvironment::openPlayerDatabase(migrate_to,
2288 game_params.world_path, world_mt);
2290 std::vector<std::string> player_list;
2291 srcdb->listPlayers(player_list);
2292 for (std::vector<std::string>::const_iterator it = player_list.begin();
2293 it != player_list.end(); ++it) {
2294 actionstream << "Migrating player " << it->c_str() << std::endl;
2295 RemotePlayer player(it->c_str(), NULL);
2296 PlayerSAO playerSAO(NULL, &player, 15000, false);
2298 srcdb->loadPlayer(&player, &playerSAO);
2300 playerSAO.finalize(&player, std::set<std::string>());
2301 player.setPlayerSAO(&playerSAO);
2303 dstdb->savePlayer(&player);
2305 // For files source, move player files to backup dir
2306 if (backend == "files") {
2308 game_params.world_path + DIR_DELIM + "players" + DIR_DELIM + (*it),
2309 players_backup_path + DIR_DELIM + (*it));
2313 actionstream << "Successfully migrated " << player_list.size() << " players"
2315 world_mt.set("player_backend", migrate_to);
2316 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2317 errorstream << "Failed to update world.mt!" << std::endl;
2319 actionstream << "world.mt updated" << std::endl;
2321 // When migration is finished from file backend, remove players directory if empty
2322 if (backend == "files") {
2323 fs::DeleteSingleFileOrEmptyDirectory(game_params.world_path + DIR_DELIM
2330 } catch (BaseException &e) {
2331 errorstream << "An error occurred during migration: " << e.what() << std::endl;
2337 AuthDatabase *ServerEnvironment::openAuthDatabase(
2338 const std::string &name, const std::string &savedir, const Settings &conf)
2340 if (name == "sqlite3")
2341 return new AuthDatabaseSQLite3(savedir);
2344 if (name == "postgresql") {
2345 std::string connect_string;
2346 conf.getNoEx("pgsql_auth_connection", connect_string);
2347 return new AuthDatabasePostgreSQL(connect_string);
2351 if (name == "files")
2352 return new AuthDatabaseFiles(savedir);
2355 if (name == "leveldb")
2356 return new AuthDatabaseLevelDB(savedir);
2359 throw BaseException(std::string("Database backend ") + name + " not supported.");
2362 bool ServerEnvironment::migrateAuthDatabase(
2363 const GameParams &game_params, const Settings &cmd_args)
2365 std::string migrate_to = cmd_args.get("migrate-auth");
2367 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2368 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2369 errorstream << "Cannot read world.mt!" << std::endl;
2373 std::string backend = "files";
2374 if (world_mt.exists("auth_backend"))
2375 backend = world_mt.get("auth_backend");
2377 warningstream << "No auth_backend found in world.mt, "
2378 "assuming \"files\"." << std::endl;
2380 if (backend == migrate_to) {
2381 errorstream << "Cannot migrate: new backend is same"
2382 << " as the old one" << std::endl;
2387 const std::unique_ptr<AuthDatabase> srcdb(ServerEnvironment::openAuthDatabase(
2388 backend, game_params.world_path, world_mt));
2389 const std::unique_ptr<AuthDatabase> dstdb(ServerEnvironment::openAuthDatabase(
2390 migrate_to, game_params.world_path, world_mt));
2392 std::vector<std::string> names_list;
2393 srcdb->listNames(names_list);
2394 for (const std::string &name : names_list) {
2395 actionstream << "Migrating auth entry for " << name << std::endl;
2397 AuthEntry authEntry;
2398 success = srcdb->getAuth(name, authEntry);
2399 success = success && dstdb->createAuth(authEntry);
2401 errorstream << "Failed to migrate " << name << std::endl;
2404 actionstream << "Successfully migrated " << names_list.size()
2405 << " auth entries" << std::endl;
2406 world_mt.set("auth_backend", migrate_to);
2407 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2408 errorstream << "Failed to update world.mt!" << std::endl;
2410 actionstream << "world.mt updated" << std::endl;
2412 if (backend == "files") {
2413 // special-case files migration:
2414 // move auth.txt to auth.txt.bak if possible
2415 std::string auth_txt_path =
2416 game_params.world_path + DIR_DELIM + "auth.txt";
2417 std::string auth_bak_path = auth_txt_path + ".bak";
2418 if (!fs::PathExists(auth_bak_path))
2419 if (fs::Rename(auth_txt_path, auth_bak_path))
2420 actionstream << "Renamed auth.txt to auth.txt.bak"
2423 errorstream << "Could not rename auth.txt to "
2424 "auth.txt.bak" << std::endl;
2426 warningstream << "auth.txt.bak already exists, auth.txt "
2427 "not renamed" << std::endl;
2430 } catch (BaseException &e) {
2431 errorstream << "An error occurred during migration: " << e.what()