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 auto it = getLBMsIntroducedAfter(stamp);
262 for (; it != m_lbm_lookup.end(); ++it) {
263 // Cache previous version to speedup lookup which has a very high performance
264 // penalty on each call
265 content_t previous_c = CONTENT_IGNORE;
266 const std::vector<LoadingBlockModifierDef *> *lbm_list = nullptr;
268 for (pos.X = 0; pos.X < MAP_BLOCKSIZE; pos.X++)
269 for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++)
270 for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++) {
271 n = block->getNodeNoCheck(pos, nullptr);
274 // If content_t are not matching perform an LBM lookup
275 if (previous_c != c) {
276 lbm_list = it->second.lookup(c);
282 for (auto lbmdef : *lbm_list) {
283 lbmdef->trigger(env, pos + pos_of_block, n);
293 void fillRadiusBlock(v3s16 p0, s16 r, std::set<v3s16> &list)
296 for(p.X=p0.X-r; p.X<=p0.X+r; p.X++)
297 for(p.Y=p0.Y-r; p.Y<=p0.Y+r; p.Y++)
298 for(p.Z=p0.Z-r; p.Z<=p0.Z+r; p.Z++)
301 if (p.getDistanceFrom(p0) <= r) {
308 void fillViewConeBlock(v3s16 p0,
310 const v3f camera_pos,
311 const v3f camera_dir,
312 const float camera_fov,
313 std::set<v3s16> &list)
316 const s16 r_nodes = r * BS * MAP_BLOCKSIZE;
317 for (p.X = p0.X - r; p.X <= p0.X+r; p.X++)
318 for (p.Y = p0.Y - r; p.Y <= p0.Y+r; p.Y++)
319 for (p.Z = p0.Z - r; p.Z <= p0.Z+r; p.Z++) {
320 if (isBlockInSight(p, camera_pos, camera_dir, camera_fov, r_nodes)) {
326 void ActiveBlockList::update(std::vector<PlayerSAO*> &active_players,
327 s16 active_block_range,
328 s16 active_object_range,
329 std::set<v3s16> &blocks_removed,
330 std::set<v3s16> &blocks_added)
335 std::set<v3s16> newlist = m_forceloaded_list;
336 m_abm_list = m_forceloaded_list;
337 for (const PlayerSAO *playersao : active_players) {
338 v3s16 pos = getNodeBlockPos(floatToInt(playersao->getBasePosition(), BS));
339 fillRadiusBlock(pos, active_block_range, m_abm_list);
340 fillRadiusBlock(pos, active_block_range, newlist);
342 s16 player_ao_range = std::min(active_object_range, playersao->getWantedRange());
343 // only do this if this would add blocks
344 if (player_ao_range > active_block_range) {
345 v3f camera_dir = v3f(0,0,1);
346 camera_dir.rotateYZBy(playersao->getLookPitch());
347 camera_dir.rotateXZBy(playersao->getRotation().Y);
348 fillViewConeBlock(pos,
350 playersao->getEyePosition(),
358 Find out which blocks on the old list are not on the new list
360 // Go through old list
361 for (v3s16 p : m_list) {
362 // If not on new list, it's been removed
363 if (newlist.find(p) == newlist.end())
364 blocks_removed.insert(p);
368 Find out which blocks on the new list are not on the old list
370 // Go through new list
371 for (v3s16 p : newlist) {
372 // If not on old list, it's been added
373 if(m_list.find(p) == m_list.end())
374 blocks_added.insert(p);
380 m_list = std::move(newlist);
387 // Random device to seed pseudo random generators.
388 static std::random_device seed;
390 ServerEnvironment::ServerEnvironment(ServerMap *map,
391 ServerScripting *scriptIface, Server *server,
392 const std::string &path_world, MetricsBackend *mb):
395 m_script(scriptIface),
397 m_path_world(path_world),
400 // Determine which database backend to use
401 std::string conf_path = path_world + DIR_DELIM + "world.mt";
404 std::string player_backend_name = "sqlite3";
405 std::string auth_backend_name = "sqlite3";
407 bool succeeded = conf.readConfigFile(conf_path.c_str());
409 // If we open world.mt read the backend configurations.
411 // Read those values before setting defaults
412 bool player_backend_exists = conf.exists("player_backend");
413 bool auth_backend_exists = conf.exists("auth_backend");
415 // player backend is not set, assume it's legacy file backend.
416 if (!player_backend_exists) {
417 // fall back to files
418 conf.set("player_backend", "files");
419 player_backend_name = "files";
421 if (!conf.updateConfigFile(conf_path.c_str())) {
422 errorstream << "ServerEnvironment::ServerEnvironment(): "
423 << "Failed to update world.mt!" << std::endl;
426 conf.getNoEx("player_backend", player_backend_name);
429 // auth backend is not set, assume it's legacy file backend.
430 if (!auth_backend_exists) {
431 conf.set("auth_backend", "files");
432 auth_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("auth_backend", auth_backend_name);
443 if (player_backend_name == "files") {
444 warningstream << "/!\\ You are using old player file backend. "
445 << "This backend is deprecated and will be removed in a future release /!\\"
446 << std::endl << "Switching to SQLite3 or PostgreSQL is advised, "
447 << "please read http://wiki.minetest.net/Database_backends." << std::endl;
450 if (auth_backend_name == "files") {
451 warningstream << "/!\\ You are using old auth file backend. "
452 << "This backend is deprecated and will be removed in a future release /!\\"
453 << std::endl << "Switching to SQLite3 is advised, "
454 << "please read http://wiki.minetest.net/Database_backends." << std::endl;
457 m_player_database = openPlayerDatabase(player_backend_name, path_world, conf);
458 m_auth_database = openAuthDatabase(auth_backend_name, path_world, conf);
460 m_step_time_counter = mb->addCounter(
461 "minetest_env_step_time", "Time spent in environment step (in microseconds)");
463 m_active_block_gauge = mb->addGauge(
464 "minetest_env_active_blocks", "Number of active blocks");
466 m_active_object_gauge = mb->addGauge(
467 "minetest_env_active_objects", "Number of active objects");
470 ServerEnvironment::~ServerEnvironment()
472 // Clear active block list.
473 // This makes the next one delete all active objects.
474 m_active_blocks.clear();
476 // Convert all objects to static and delete the active objects
477 deactivateFarObjects(true);
482 // Delete ActiveBlockModifiers
483 for (ABMWithState &m_abm : m_abms) {
487 // Deallocate players
488 for (RemotePlayer *m_player : m_players) {
492 delete m_player_database;
493 delete m_auth_database;
496 Map & ServerEnvironment::getMap()
501 ServerMap & ServerEnvironment::getServerMap()
506 RemotePlayer *ServerEnvironment::getPlayer(const session_t peer_id)
508 for (RemotePlayer *player : m_players) {
509 if (player->getPeerId() == peer_id)
515 RemotePlayer *ServerEnvironment::getPlayer(const char* name)
517 for (RemotePlayer *player : m_players) {
518 if (strcmp(player->getName(), name) == 0)
524 void ServerEnvironment::addPlayer(RemotePlayer *player)
527 Check that peer_ids are unique.
528 Also check that names are unique.
529 Exception: there can be multiple players with peer_id=0
531 // If peer id is non-zero, it has to be unique.
532 if (player->getPeerId() != PEER_ID_INEXISTENT)
533 FATAL_ERROR_IF(getPlayer(player->getPeerId()) != NULL, "Peer id not unique");
534 // Name has to be unique.
535 FATAL_ERROR_IF(getPlayer(player->getName()) != NULL, "Player name not unique");
537 m_players.push_back(player);
540 void ServerEnvironment::removePlayer(RemotePlayer *player)
542 for (std::vector<RemotePlayer *>::iterator it = m_players.begin();
543 it != m_players.end(); ++it) {
544 if ((*it) == player) {
552 bool ServerEnvironment::removePlayerFromDatabase(const std::string &name)
554 return m_player_database->removePlayer(name);
557 void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason,
558 const std::string &str_reason, bool reconnect)
560 for (RemotePlayer *player : m_players)
561 m_server->DenyAccess(player->getPeerId(), reason, str_reason, reconnect);
564 void ServerEnvironment::saveLoadedPlayers(bool force)
566 for (RemotePlayer *player : m_players) {
567 if (force || player->checkModified() || (player->getPlayerSAO() &&
568 player->getPlayerSAO()->getMeta().isModified())) {
570 m_player_database->savePlayer(player);
571 } catch (DatabaseException &e) {
572 errorstream << "Failed to save player " << player->getName() << " exception: "
573 << e.what() << std::endl;
580 void ServerEnvironment::savePlayer(RemotePlayer *player)
583 m_player_database->savePlayer(player);
584 } catch (DatabaseException &e) {
585 errorstream << "Failed to save player " << player->getName() << " exception: "
586 << e.what() << std::endl;
591 PlayerSAO *ServerEnvironment::loadPlayer(RemotePlayer *player, bool *new_player,
592 session_t peer_id, bool is_singleplayer)
594 PlayerSAO *playersao = new PlayerSAO(this, player, peer_id, is_singleplayer);
595 // Create player if it doesn't exist
596 if (!m_player_database->loadPlayer(player, playersao)) {
598 // Set player position
599 infostream << "Server: Finding spawn place for player \""
600 << player->getName() << "\"" << std::endl;
601 playersao->setBasePosition(m_server->findSpawnPos());
603 // Make sure the player is saved
604 player->setModified(true);
606 // If the player exists, ensure that they respawn inside legal bounds
607 // This fixes an assert crash when the player can't be added
608 // to the environment
609 if (objectpos_over_limit(playersao->getBasePosition())) {
610 actionstream << "Respawn position for player \""
611 << player->getName() << "\" outside limits, resetting" << std::endl;
612 playersao->setBasePosition(m_server->findSpawnPos());
616 // Add player to environment
619 /* Clean up old HUD elements from previous sessions */
622 /* Add object to environment */
623 addActiveObject(playersao);
625 // Update active blocks asap so objects in those blocks appear on the client
626 m_force_update_active_blocks = true;
631 void ServerEnvironment::saveMeta()
636 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
638 // Open file and serialize
639 std::ostringstream ss(std::ios_base::binary);
641 Settings args("EnvArgsEnd");
642 args.setU64("game_time", m_game_time);
643 args.setU64("time_of_day", getTimeOfDay());
644 args.setU64("last_clear_objects_time", m_last_clear_objects_time);
645 args.setU64("lbm_introduction_times_version", 1);
646 args.set("lbm_introduction_times",
647 m_lbm_mgr.createIntroductionTimesString());
648 args.setU64("day_count", m_day_count);
651 if(!fs::safeWriteToFile(path, ss.str()))
653 infostream<<"ServerEnvironment::saveMeta(): Failed to write "
655 throw SerializationError("Couldn't save env meta");
659 void ServerEnvironment::loadMeta()
661 SANITY_CHECK(!m_meta_loaded);
662 m_meta_loaded = true;
664 // If file doesn't exist, load default environment metadata
665 if (!fs::PathExists(m_path_world + DIR_DELIM "env_meta.txt")) {
666 infostream << "ServerEnvironment: Loading default environment metadata"
672 infostream << "ServerEnvironment: Loading environment metadata" << std::endl;
674 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
676 // Open file and deserialize
677 std::ifstream is(path.c_str(), std::ios_base::binary);
679 infostream << "ServerEnvironment::loadMeta(): Failed to open "
680 << path << std::endl;
681 throw SerializationError("Couldn't load env meta");
684 Settings args("EnvArgsEnd");
686 if (!args.parseConfigLines(is)) {
687 throw SerializationError("ServerEnvironment::loadMeta(): "
688 "EnvArgsEnd not found!");
692 m_game_time = args.getU64("game_time");
693 } catch (SettingNotFoundException &e) {
694 // Getting this is crucial, otherwise timestamps are useless
695 throw SerializationError("Couldn't load env meta game_time");
698 setTimeOfDay(args.exists("time_of_day") ?
699 // set day to early morning by default
700 args.getU64("time_of_day") : 5250);
702 m_last_clear_objects_time = args.exists("last_clear_objects_time") ?
703 // If missing, do as if clearObjects was never called
704 args.getU64("last_clear_objects_time") : 0;
706 std::string lbm_introduction_times;
708 u64 ver = args.getU64("lbm_introduction_times_version");
710 lbm_introduction_times = args.get("lbm_introduction_times");
712 infostream << "ServerEnvironment::loadMeta(): Non-supported"
713 << " introduction time version " << ver << std::endl;
715 } catch (SettingNotFoundException &e) {
716 // No problem, this is expected. Just continue with an empty string
718 m_lbm_mgr.loadIntroductionTimes(lbm_introduction_times, m_server, m_game_time);
720 m_day_count = args.exists("day_count") ?
721 args.getU64("day_count") : 0;
725 * called if env_meta.txt doesn't exist (e.g. new world)
727 void ServerEnvironment::loadDefaultMeta()
729 m_lbm_mgr.loadIntroductionTimes("", m_server, m_game_time);
734 ActiveBlockModifier *abm;
736 std::vector<content_t> required_neighbors;
737 bool check_required_neighbors; // false if required_neighbors is known to be empty
745 ServerEnvironment *m_env;
746 std::vector<std::vector<ActiveABM> *> m_aabms;
748 ABMHandler(std::vector<ABMWithState> &abms,
749 float dtime_s, ServerEnvironment *env,
755 const NodeDefManager *ndef = env->getGameDef()->ndef();
756 for (ABMWithState &abmws : abms) {
757 ActiveBlockModifier *abm = abmws.abm;
758 float trigger_interval = abm->getTriggerInterval();
759 if(trigger_interval < 0.001)
760 trigger_interval = 0.001;
761 float actual_interval = dtime_s;
763 abmws.timer += dtime_s;
764 if(abmws.timer < trigger_interval)
766 abmws.timer -= trigger_interval;
767 actual_interval = trigger_interval;
769 float chance = abm->getTriggerChance();
774 if (abm->getSimpleCatchUp()) {
775 float intervals = actual_interval / trigger_interval;
778 aabm.chance = chance / intervals;
782 aabm.chance = chance;
785 aabm.min_y = abm->getMinY();
786 aabm.max_y = abm->getMaxY();
789 const std::vector<std::string> &required_neighbors_s =
790 abm->getRequiredNeighbors();
791 for (const std::string &required_neighbor_s : required_neighbors_s) {
792 ndef->getIds(required_neighbor_s, aabm.required_neighbors);
794 aabm.check_required_neighbors = !required_neighbors_s.empty();
797 const std::vector<std::string> &contents_s = abm->getTriggerContents();
798 for (const std::string &content_s : contents_s) {
799 std::vector<content_t> ids;
800 ndef->getIds(content_s, ids);
801 for (content_t c : ids) {
802 if (c >= m_aabms.size())
803 m_aabms.resize(c + 256, NULL);
805 m_aabms[c] = new std::vector<ActiveABM>;
806 m_aabms[c]->push_back(aabm);
814 for (auto &aabms : m_aabms)
818 // Find out how many objects the given block and its neighbours contain.
819 // Returns the number of objects in the block, and also in 'wider' the
820 // number of objects in the block and all its neighbours. The latter
821 // may an estimate if any neighbours are unloaded.
822 u32 countObjects(MapBlock *block, ServerMap * map, u32 &wider)
825 u32 wider_unknown_count = 0;
826 for(s16 x=-1; x<=1; x++)
827 for(s16 y=-1; y<=1; y++)
828 for(s16 z=-1; z<=1; z++)
830 MapBlock *block2 = map->getBlockNoCreateNoEx(
831 block->getPos() + v3s16(x,y,z));
833 wider_unknown_count++;
836 wider += block2->m_static_objects.m_active.size()
837 + block2->m_static_objects.m_stored.size();
840 u32 active_object_count = block->m_static_objects.m_active.size();
841 u32 wider_known_count = 3*3*3 - wider_unknown_count;
842 wider += wider_unknown_count * wider / wider_known_count;
843 return active_object_count;
846 void apply(MapBlock *block, int &blocks_scanned, int &abms_run, int &blocks_cached)
848 if(m_aabms.empty() || block->isDummy())
851 // Check the content type cache first
852 // to see whether there are any ABMs
853 // to be run at all for this block.
854 if (block->contents_cached) {
856 bool run_abms = false;
857 for (content_t c : block->contents) {
858 if (c < m_aabms.size() && m_aabms[c]) {
867 block->contents.clear();
871 ServerMap *map = &m_env->getServerMap();
873 u32 active_object_count_wider;
874 u32 active_object_count = this->countObjects(block, map, active_object_count_wider);
875 m_env->m_added_objects = 0;
878 for(p0.X=0; p0.X<MAP_BLOCKSIZE; p0.X++)
879 for(p0.Y=0; p0.Y<MAP_BLOCKSIZE; p0.Y++)
880 for(p0.Z=0; p0.Z<MAP_BLOCKSIZE; p0.Z++)
882 const MapNode &n = block->getNodeUnsafe(p0);
883 content_t c = n.getContent();
884 // Cache content types as we go
885 if (!block->contents_cached && !block->do_not_cache_contents) {
886 block->contents.insert(c);
887 if (block->contents.size() > 64) {
888 // Too many different nodes... don't try to cache
889 block->do_not_cache_contents = true;
890 block->contents.clear();
894 if (c >= m_aabms.size() || !m_aabms[c])
897 v3s16 p = p0 + block->getPosRelative();
898 for (ActiveABM &aabm : *m_aabms[c]) {
899 if ((p.Y < aabm.min_y) || (p.Y > aabm.max_y))
902 if (myrand() % aabm.chance != 0)
906 if (aabm.check_required_neighbors) {
908 for(p1.X = p0.X-1; p1.X <= p0.X+1; p1.X++)
909 for(p1.Y = p0.Y-1; p1.Y <= p0.Y+1; p1.Y++)
910 for(p1.Z = p0.Z-1; p1.Z <= p0.Z+1; p1.Z++)
915 if (block->isValidPosition(p1)) {
916 // if the neighbor is found on the same map block
917 // get it straight from there
918 const MapNode &n = block->getNodeUnsafe(p1);
921 // otherwise consult the map
922 MapNode n = map->getNode(p1 + block->getPosRelative());
925 if (CONTAINS(aabm.required_neighbors, c))
928 // No required neighbor found
934 // Call all the trigger variations
935 aabm.abm->trigger(m_env, p, n);
936 aabm.abm->trigger(m_env, p, n,
937 active_object_count, active_object_count_wider);
939 // Count surrounding objects again if the abms added any
940 if(m_env->m_added_objects > 0) {
941 active_object_count = countObjects(block, map, active_object_count_wider);
942 m_env->m_added_objects = 0;
946 block->contents_cached = !block->do_not_cache_contents;
950 void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime)
952 // Reset usage timer immediately, otherwise a block that becomes active
953 // again at around the same time as it would normally be unloaded will
954 // get unloaded incorrectly. (I think this still leaves a small possibility
955 // of a race condition between this and server::AsyncRunStep, which only
956 // some kind of synchronisation will fix, but it at least reduces the window
957 // of opportunity for it to break from seconds to nanoseconds)
958 block->resetUsageTimer();
960 // Get time difference
962 u32 stamp = block->getTimestamp();
963 if (m_game_time > stamp && stamp != BLOCK_TIMESTAMP_UNDEFINED)
964 dtime_s = m_game_time - stamp;
965 dtime_s += additional_dtime;
967 /*infostream<<"ServerEnvironment::activateBlock(): block timestamp: "
968 <<stamp<<", game time: "<<m_game_time<<std::endl;*/
970 // Remove stored static objects if clearObjects was called since block's timestamp
971 if (stamp == BLOCK_TIMESTAMP_UNDEFINED || stamp < m_last_clear_objects_time) {
972 block->m_static_objects.m_stored.clear();
973 // do not set changed flag to avoid unnecessary mapblock writes
976 // Set current time as timestamp
977 block->setTimestampNoChangedFlag(m_game_time);
979 /*infostream<<"ServerEnvironment::activateBlock(): block is "
980 <<dtime_s<<" seconds old."<<std::endl;*/
982 // Activate stored objects
983 activateObjects(block, dtime_s);
985 /* Handle LoadingBlockModifiers */
986 m_lbm_mgr.applyLBMs(this, block, stamp);
989 std::vector<NodeTimer> elapsed_timers =
990 block->m_node_timers.step((float)dtime_s);
991 if (!elapsed_timers.empty()) {
993 for (const NodeTimer &elapsed_timer : elapsed_timers) {
994 n = block->getNodeNoEx(elapsed_timer.position);
995 v3s16 p = elapsed_timer.position + block->getPosRelative();
996 if (m_script->node_on_timer(p, n, elapsed_timer.elapsed))
997 block->setNodeTimer(NodeTimer(elapsed_timer.timeout, 0,
998 elapsed_timer.position));
1003 void ServerEnvironment::addActiveBlockModifier(ActiveBlockModifier *abm)
1005 m_abms.emplace_back(abm);
1008 void ServerEnvironment::addLoadingBlockModifierDef(LoadingBlockModifierDef *lbm)
1010 m_lbm_mgr.addLBMDef(lbm);
1013 bool ServerEnvironment::setNode(v3s16 p, const MapNode &n)
1015 const NodeDefManager *ndef = m_server->ndef();
1016 MapNode n_old = m_map->getNode(p);
1018 const ContentFeatures &cf_old = ndef->get(n_old);
1021 if (cf_old.has_on_destruct)
1022 m_script->node_on_destruct(p, n_old);
1025 if (!m_map->addNodeWithEvent(p, n))
1028 // Update active VoxelManipulator if a mapgen thread
1029 m_map->updateVManip(p);
1031 // Call post-destructor
1032 if (cf_old.has_after_destruct)
1033 m_script->node_after_destruct(p, n_old);
1035 // Retrieve node content features
1036 // if new node is same as old, reuse old definition to prevent a lookup
1037 const ContentFeatures &cf_new = n_old == n ? cf_old : ndef->get(n);
1040 if (cf_new.has_on_construct)
1041 m_script->node_on_construct(p, n);
1046 bool ServerEnvironment::removeNode(v3s16 p)
1048 const NodeDefManager *ndef = m_server->ndef();
1049 MapNode n_old = m_map->getNode(p);
1052 if (ndef->get(n_old).has_on_destruct)
1053 m_script->node_on_destruct(p, n_old);
1056 // This is slightly optimized compared to addNodeWithEvent(air)
1057 if (!m_map->removeNodeWithEvent(p))
1060 // Update active VoxelManipulator if a mapgen thread
1061 m_map->updateVManip(p);
1063 // Call post-destructor
1064 if (ndef->get(n_old).has_after_destruct)
1065 m_script->node_after_destruct(p, n_old);
1067 // Air doesn't require constructor
1071 bool ServerEnvironment::swapNode(v3s16 p, const MapNode &n)
1073 if (!m_map->addNodeWithEvent(p, n, false))
1076 // Update active VoxelManipulator if a mapgen thread
1077 m_map->updateVManip(p);
1082 u8 ServerEnvironment::findSunlight(v3s16 pos) const
1084 // Directions for neighbouring nodes with specified order
1085 static const v3s16 dirs[] = {
1086 v3s16(-1, 0, 0), v3s16(1, 0, 0), v3s16(0, 0, -1), v3s16(0, 0, 1),
1087 v3s16(0, -1, 0), v3s16(0, 1, 0)
1090 const NodeDefManager *ndef = m_server->ndef();
1092 // found_light remembers the highest known sunlight value at pos
1095 struct stack_entry {
1099 std::stack<stack_entry> stack;
1100 stack.push({pos, 0});
1102 std::unordered_map<s64, s8> dists;
1103 dists[MapDatabase::getBlockAsInteger(pos)] = 0;
1105 while (!stack.empty()) {
1106 struct stack_entry e = stack.top();
1109 v3s16 currentPos = e.pos;
1110 s8 dist = e.dist + 1;
1112 for (const v3s16& off : dirs) {
1113 v3s16 neighborPos = currentPos + off;
1114 s64 neighborHash = MapDatabase::getBlockAsInteger(neighborPos);
1116 // Do not walk neighborPos multiple times unless the distance to the start
1117 // position is shorter
1118 auto it = dists.find(neighborHash);
1119 if (it != dists.end() && dist >= it->second)
1123 bool is_position_ok;
1124 MapNode node = m_map->getNode(neighborPos, &is_position_ok);
1125 if (!is_position_ok) {
1126 // This happens very rarely because the map at currentPos is loaded
1127 m_map->emergeBlock(neighborPos, false);
1128 node = m_map->getNode(neighborPos, &is_position_ok);
1129 if (!is_position_ok)
1130 continue; // not generated
1133 const ContentFeatures &def = ndef->get(node);
1134 if (!def.sunlight_propagates) {
1135 // Do not test propagation here again
1136 dists[neighborHash] = -1;
1140 // Sunlight could have come from here
1141 dists[neighborHash] = dist;
1142 u8 daylight = node.param1 & 0x0f;
1144 // In the special case where sunlight shines from above and thus
1145 // does not decrease with upwards distance, daylight is always
1146 // bigger than nightlight, which never reaches 15
1147 int possible_finlight = daylight - dist;
1148 if (possible_finlight <= found_light) {
1149 // Light from here cannot make a brighter light at currentPos than
1154 u8 nightlight = node.param1 >> 4;
1155 if (daylight > nightlight) {
1156 // Found a valid daylight
1157 found_light = possible_finlight;
1159 // Sunlight may be darker, so walk the neighbours
1160 stack.push({neighborPos, dist});
1167 void ServerEnvironment::clearObjects(ClearObjectsMode mode)
1169 infostream << "ServerEnvironment::clearObjects(): "
1170 << "Removing all active objects" << std::endl;
1171 auto cb_removal = [this] (ServerActiveObject *obj, u16 id) {
1172 if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER)
1175 // Delete static object if block is loaded
1176 deleteStaticFromBlock(obj, id, MOD_REASON_CLEAR_ALL_OBJECTS, true);
1178 // If known by some client, don't delete immediately
1179 if (obj->m_known_by_count > 0) {
1180 obj->markForRemoval();
1184 // Tell the object about removal
1185 obj->removingFromEnvironment();
1186 // Deregister in scripting api
1187 m_script->removeObjectReference(obj);
1189 // Delete active object
1190 if (obj->environmentDeletes())
1196 m_ao_manager.clear(cb_removal);
1198 // Get list of loaded blocks
1199 std::vector<v3s16> loaded_blocks;
1200 infostream << "ServerEnvironment::clearObjects(): "
1201 << "Listing all loaded blocks" << std::endl;
1202 m_map->listAllLoadedBlocks(loaded_blocks);
1203 infostream << "ServerEnvironment::clearObjects(): "
1204 << "Done listing all loaded blocks: "
1205 << loaded_blocks.size()<<std::endl;
1207 // Get list of loadable blocks
1208 std::vector<v3s16> loadable_blocks;
1209 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1210 infostream << "ServerEnvironment::clearObjects(): "
1211 << "Listing all loadable blocks" << std::endl;
1212 m_map->listAllLoadableBlocks(loadable_blocks);
1213 infostream << "ServerEnvironment::clearObjects(): "
1214 << "Done listing all loadable blocks: "
1215 << loadable_blocks.size() << std::endl;
1217 loadable_blocks = loaded_blocks;
1220 actionstream << "ServerEnvironment::clearObjects(): "
1221 << "Now clearing objects in " << loadable_blocks.size()
1222 << " blocks" << std::endl;
1224 // Grab a reference on each loaded block to avoid unloading it
1225 for (v3s16 p : loaded_blocks) {
1226 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1227 assert(block != NULL);
1231 // Remove objects in all loadable blocks
1232 u32 unload_interval = U32_MAX;
1233 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1234 unload_interval = g_settings->getS32("max_clearobjects_extra_loaded_blocks");
1235 unload_interval = MYMAX(unload_interval, 1);
1237 u32 report_interval = loadable_blocks.size() / 10;
1238 u32 num_blocks_checked = 0;
1239 u32 num_blocks_cleared = 0;
1240 u32 num_objs_cleared = 0;
1241 for (auto i = loadable_blocks.begin();
1242 i != loadable_blocks.end(); ++i) {
1244 MapBlock *block = m_map->emergeBlock(p, false);
1246 errorstream << "ServerEnvironment::clearObjects(): "
1247 << "Failed to emerge block " << PP(p) << std::endl;
1250 u32 num_stored = block->m_static_objects.m_stored.size();
1251 u32 num_active = block->m_static_objects.m_active.size();
1252 if (num_stored != 0 || num_active != 0) {
1253 block->m_static_objects.m_stored.clear();
1254 block->m_static_objects.m_active.clear();
1255 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1256 MOD_REASON_CLEAR_ALL_OBJECTS);
1257 num_objs_cleared += num_stored + num_active;
1258 num_blocks_cleared++;
1260 num_blocks_checked++;
1262 if (report_interval != 0 &&
1263 num_blocks_checked % report_interval == 0) {
1264 float percent = 100.0 * (float)num_blocks_checked /
1265 loadable_blocks.size();
1266 actionstream << "ServerEnvironment::clearObjects(): "
1267 << "Cleared " << num_objs_cleared << " objects"
1268 << " in " << num_blocks_cleared << " blocks ("
1269 << percent << "%)" << std::endl;
1271 if (num_blocks_checked % unload_interval == 0) {
1272 m_map->unloadUnreferencedBlocks();
1275 m_map->unloadUnreferencedBlocks();
1277 // Drop references that were added above
1278 for (v3s16 p : loaded_blocks) {
1279 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1284 m_last_clear_objects_time = m_game_time;
1286 actionstream << "ServerEnvironment::clearObjects(): "
1287 << "Finished: Cleared " << num_objs_cleared << " objects"
1288 << " in " << num_blocks_cleared << " blocks" << std::endl;
1291 void ServerEnvironment::step(float dtime)
1293 ScopeProfiler sp2(g_profiler, "ServerEnv::step()", SPT_AVG);
1294 const auto start_time = porting::getTimeUs();
1296 /* Step time of day */
1297 stepTimeOfDay(dtime);
1300 // NOTE: This is kind of funny on a singleplayer game, but doesn't
1301 // really matter that much.
1302 static thread_local const float server_step =
1303 g_settings->getFloat("dedicated_server_step");
1304 m_recommended_send_interval = server_step;
1310 m_game_time_fraction_counter += dtime;
1311 u32 inc_i = (u32)m_game_time_fraction_counter;
1312 m_game_time += inc_i;
1313 m_game_time_fraction_counter -= (float)inc_i;
1320 ScopeProfiler sp(g_profiler, "ServerEnv: move players", SPT_AVG);
1321 for (RemotePlayer *player : m_players) {
1322 // Ignore disconnected players
1323 if (player->getPeerId() == PEER_ID_INEXISTENT)
1327 player->move(dtime, this, 100 * BS);
1332 Manage active block list
1334 if (m_active_blocks_mgmt_interval.step(dtime, m_cache_active_block_mgmt_interval) ||
1335 m_force_update_active_blocks) {
1336 ScopeProfiler sp(g_profiler, "ServerEnv: update active blocks", SPT_AVG);
1339 Get player block positions
1341 std::vector<PlayerSAO*> players;
1342 players.reserve(m_players.size());
1343 for (RemotePlayer *player : m_players) {
1344 // Ignore disconnected players
1345 if (player->getPeerId() == PEER_ID_INEXISTENT)
1348 PlayerSAO *playersao = player->getPlayerSAO();
1351 players.push_back(playersao);
1355 Update list of active blocks, collecting changes
1357 // use active_object_send_range_blocks since that is max distance
1358 // for active objects sent the client anyway
1359 static thread_local const s16 active_object_range =
1360 g_settings->getS16("active_object_send_range_blocks");
1361 static thread_local const s16 active_block_range =
1362 g_settings->getS16("active_block_range");
1363 std::set<v3s16> blocks_removed;
1364 std::set<v3s16> blocks_added;
1365 m_active_blocks.update(players, active_block_range, active_object_range,
1366 blocks_removed, blocks_added);
1369 Handle removed blocks
1372 // Convert active objects that are no more in active blocks to static
1373 deactivateFarObjects(false);
1375 for (const v3s16 &p: blocks_removed) {
1376 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1380 // Set current time as timestamp (and let it set ChangedFlag)
1381 block->setTimestamp(m_game_time);
1388 for (const v3s16 &p: blocks_added) {
1389 MapBlock *block = m_map->getBlockOrEmerge(p);
1391 // TODO: The blocks removed here will only be picked up again
1392 // on the next cycle. To minimize the latency of objects being
1393 // activated we could remember the blocks pending activating
1394 // and activate them instantly as soon as they're loaded.
1395 m_active_blocks.remove(p);
1399 activateBlock(block);
1402 // Some blocks may be removed again by the code above so do this here
1403 m_active_block_gauge->set(m_active_blocks.size());
1405 m_force_update_active_blocks = false;
1408 Mess around in active blocks
1410 if (m_active_blocks_nodemetadata_interval.step(dtime, m_cache_nodetimer_interval)) {
1411 ScopeProfiler sp(g_profiler, "ServerEnv: Run node timers", SPT_AVG);
1413 float dtime = m_cache_nodetimer_interval;
1415 for (const v3s16 &p: m_active_blocks.m_list) {
1416 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1420 // Reset block usage timer
1421 block->resetUsageTimer();
1423 // Set current time as timestamp
1424 block->setTimestampNoChangedFlag(m_game_time);
1425 // If time has changed much from the one on disk,
1426 // set block to be saved when it is unloaded
1427 if(block->getTimestamp() > block->getDiskTimestamp() + 60)
1428 block->raiseModified(MOD_STATE_WRITE_AT_UNLOAD,
1429 MOD_REASON_BLOCK_EXPIRED);
1432 std::vector<NodeTimer> elapsed_timers = block->m_node_timers.step(dtime);
1433 if (!elapsed_timers.empty()) {
1436 for (const NodeTimer &elapsed_timer: elapsed_timers) {
1437 n = block->getNodeNoEx(elapsed_timer.position);
1438 p2 = elapsed_timer.position + block->getPosRelative();
1439 if (m_script->node_on_timer(p2, n, elapsed_timer.elapsed)) {
1440 block->setNodeTimer(NodeTimer(
1441 elapsed_timer.timeout, 0, elapsed_timer.position));
1448 if (m_active_block_modifier_interval.step(dtime, m_cache_abm_interval)) {
1449 ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg per interval", SPT_AVG);
1450 TimeTaker timer("modify in active blocks per interval");
1452 // Initialize handling of ActiveBlockModifiers
1453 ABMHandler abmhandler(m_abms, m_cache_abm_interval, this, true);
1455 int blocks_scanned = 0;
1457 int blocks_cached = 0;
1459 std::vector<v3s16> output(m_active_blocks.m_abm_list.size());
1461 // Shuffle the active blocks so that each block gets an equal chance
1462 // of having its ABMs run.
1463 std::copy(m_active_blocks.m_abm_list.begin(), m_active_blocks.m_abm_list.end(), output.begin());
1464 std::shuffle(output.begin(), output.end(), m_rgen);
1467 // determine the time budget for ABMs
1468 u32 max_time_ms = m_cache_abm_interval * 1000 * m_cache_abm_time_budget;
1469 for (const v3s16 &p : output) {
1470 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1476 // Set current time as timestamp
1477 block->setTimestampNoChangedFlag(m_game_time);
1479 /* Handle ActiveBlockModifiers */
1480 abmhandler.apply(block, blocks_scanned, abms_run, blocks_cached);
1482 u32 time_ms = timer.getTimerTime();
1484 if (time_ms > max_time_ms) {
1485 warningstream << "active block modifiers took "
1486 << time_ms << "ms (processed " << i << " of "
1487 << output.size() << " active blocks)" << std::endl;
1491 g_profiler->avg("ServerEnv: active blocks", m_active_blocks.m_abm_list.size());
1492 g_profiler->avg("ServerEnv: active blocks cached", blocks_cached);
1493 g_profiler->avg("ServerEnv: active blocks scanned for ABMs", blocks_scanned);
1494 g_profiler->avg("ServerEnv: ABMs run", abms_run);
1500 Step script environment (run global on_step())
1502 m_script->environment_Step(dtime);
1504 m_script->stepAsync();
1510 ScopeProfiler sp(g_profiler, "ServerEnv: Run SAO::step()", SPT_AVG);
1512 // This helps the objects to send data at the same time
1513 bool send_recommended = false;
1514 m_send_recommended_timer += dtime;
1515 if (m_send_recommended_timer > getSendRecommendedInterval()) {
1516 m_send_recommended_timer -= getSendRecommendedInterval();
1517 send_recommended = true;
1520 u32 object_count = 0;
1522 auto cb_state = [&] (ServerActiveObject *obj) {
1528 obj->step(dtime, send_recommended);
1529 // Read messages from object
1530 obj->dumpAOMessagesToQueue(m_active_object_messages);
1532 m_ao_manager.step(dtime, cb_state);
1534 m_active_object_gauge->set(object_count);
1538 Manage active objects
1540 if (m_object_management_interval.step(dtime, 0.5)) {
1541 removeRemovedObjects();
1545 Manage particle spawner expiration
1547 if (m_particle_management_interval.step(dtime, 1.0)) {
1548 for (std::unordered_map<u32, float>::iterator i = m_particle_spawners.begin();
1549 i != m_particle_spawners.end(); ) {
1550 //non expiring spawners
1551 if (i->second == PARTICLE_SPAWNER_NO_EXPIRY) {
1557 if (i->second <= 0.f)
1558 m_particle_spawners.erase(i++);
1564 // Send outdated player inventories
1565 for (RemotePlayer *player : m_players) {
1566 if (player->getPeerId() == PEER_ID_INEXISTENT)
1569 PlayerSAO *sao = player->getPlayerSAO();
1570 if (sao && player->inventory.checkModified())
1571 m_server->SendInventory(sao, true);
1574 // Send outdated detached inventories
1575 m_server->sendDetachedInventories(PEER_ID_INEXISTENT, true);
1577 const auto end_time = porting::getTimeUs();
1578 m_step_time_counter->increment(end_time - start_time);
1581 ServerEnvironment::BlockStatus ServerEnvironment::getBlockStatus(v3s16 blockpos)
1583 if (m_active_blocks.contains(blockpos))
1586 const MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos);
1587 if (block && !block->isDummy())
1590 if (m_map->isBlockInQueue(blockpos))
1596 u32 ServerEnvironment::addParticleSpawner(float exptime)
1598 // Timers with lifetime 0 do not expire
1599 float time = exptime > 0.f ? exptime : PARTICLE_SPAWNER_NO_EXPIRY;
1602 for (;;) { // look for unused particlespawner id
1604 std::unordered_map<u32, float>::iterator f = m_particle_spawners.find(id);
1605 if (f == m_particle_spawners.end()) {
1606 m_particle_spawners[id] = time;
1613 u32 ServerEnvironment::addParticleSpawner(float exptime, u16 attached_id)
1615 u32 id = addParticleSpawner(exptime);
1616 m_particle_spawner_attachments[id] = attached_id;
1617 if (ServerActiveObject *obj = getActiveObject(attached_id)) {
1618 obj->attachParticleSpawner(id);
1623 void ServerEnvironment::deleteParticleSpawner(u32 id, bool remove_from_object)
1625 m_particle_spawners.erase(id);
1626 const auto &it = m_particle_spawner_attachments.find(id);
1627 if (it != m_particle_spawner_attachments.end()) {
1628 u16 obj_id = it->second;
1629 ServerActiveObject *sao = getActiveObject(obj_id);
1630 if (sao != NULL && remove_from_object) {
1631 sao->detachParticleSpawner(id);
1633 m_particle_spawner_attachments.erase(id);
1637 u16 ServerEnvironment::addActiveObject(ServerActiveObject *object)
1639 assert(object); // Pre-condition
1641 u16 id = addActiveObjectRaw(object, true, 0);
1646 Finds out what new objects have been added to
1647 inside a radius around a position
1649 void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius,
1651 std::set<u16> ¤t_objects,
1652 std::queue<u16> &added_objects)
1654 f32 radius_f = radius * BS;
1655 f32 player_radius_f = player_radius * BS;
1657 if (player_radius_f < 0.0f)
1658 player_radius_f = 0.0f;
1660 m_ao_manager.getAddedActiveObjectsAroundPos(playersao->getBasePosition(), radius_f,
1661 player_radius_f, current_objects, added_objects);
1665 Finds out what objects have been removed from
1666 inside a radius around a position
1668 void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius,
1670 std::set<u16> ¤t_objects,
1671 std::queue<u16> &removed_objects)
1673 f32 radius_f = radius * BS;
1674 f32 player_radius_f = player_radius * BS;
1676 if (player_radius_f < 0)
1677 player_radius_f = 0;
1679 Go through current_objects; object is removed if:
1680 - object is not found in m_active_objects (this is actually an
1681 error condition; objects should be removed only after all clients
1682 have been informed about removal), or
1683 - object is to be removed or deactivated, or
1684 - object is too far away
1686 for (u16 id : current_objects) {
1687 ServerActiveObject *object = getActiveObject(id);
1689 if (object == NULL) {
1690 infostream << "ServerEnvironment::getRemovedActiveObjects():"
1691 << " object in current_objects is NULL" << std::endl;
1692 removed_objects.push(id);
1696 if (object->isGone()) {
1697 removed_objects.push(id);
1701 f32 distance_f = object->getBasePosition().getDistanceFrom(playersao->getBasePosition());
1702 if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1703 if (distance_f <= player_radius_f || player_radius_f == 0)
1705 } else if (distance_f <= radius_f)
1708 // Object is no longer visible
1709 removed_objects.push(id);
1713 void ServerEnvironment::setStaticForActiveObjectsInBlock(
1714 v3s16 blockpos, bool static_exists, v3s16 static_block)
1716 MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos);
1720 for (auto &so_it : block->m_static_objects.m_active) {
1721 // Get the ServerActiveObject counterpart to this StaticObject
1722 ServerActiveObject *sao = m_ao_manager.getActiveObject(so_it.first);
1724 // If this ever happens, there must be some kind of nasty bug.
1725 errorstream << "ServerEnvironment::setStaticForObjectsInBlock(): "
1726 "Object from MapBlock::m_static_objects::m_active not found "
1727 "in m_active_objects";
1731 sao->m_static_exists = static_exists;
1732 sao->m_static_block = static_block;
1736 bool ServerEnvironment::getActiveObjectMessage(ActiveObjectMessage *dest)
1738 if(m_active_object_messages.empty())
1741 *dest = std::move(m_active_object_messages.front());
1742 m_active_object_messages.pop();
1746 void ServerEnvironment::getSelectedActiveObjects(
1747 const core::line3d<f32> &shootline_on_map,
1748 std::vector<PointedThing> &objects)
1750 std::vector<ServerActiveObject *> objs;
1751 getObjectsInsideRadius(objs, shootline_on_map.start,
1752 shootline_on_map.getLength() + 10.0f, nullptr);
1753 const v3f line_vector = shootline_on_map.getVector();
1755 for (auto obj : objs) {
1758 aabb3f selection_box;
1759 if (!obj->getSelectionBox(&selection_box))
1762 v3f pos = obj->getBasePosition();
1764 aabb3f offsetted_box(selection_box.MinEdge + pos,
1765 selection_box.MaxEdge + pos);
1767 v3f current_intersection;
1768 v3s16 current_normal;
1769 if (boxLineCollision(offsetted_box, shootline_on_map.start, line_vector,
1770 ¤t_intersection, ¤t_normal)) {
1771 objects.emplace_back(
1772 (s16) obj->getId(), current_intersection, current_normal,
1773 (current_intersection - shootline_on_map.start).getLengthSQ());
1779 ************ Private methods *************
1782 u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object,
1783 bool set_changed, u32 dtime_s)
1785 if (!m_ao_manager.registerObject(object)) {
1789 // Register reference in scripting api (must be done before post-init)
1790 m_script->addObjectReference(object);
1791 // Post-initialize object
1792 object->addedToEnvironment(dtime_s);
1794 // Add static data to block
1795 if (object->isStaticAllowed()) {
1796 // Add static object to active static list of the block
1797 v3f objectpos = object->getBasePosition();
1798 StaticObject s_obj(object, objectpos);
1799 // Add to the block where the object is located in
1800 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
1801 MapBlock *block = m_map->emergeBlock(blockpos);
1803 block->m_static_objects.m_active[object->getId()] = s_obj;
1804 object->m_static_exists = true;
1805 object->m_static_block = blockpos;
1808 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1809 MOD_REASON_ADD_ACTIVE_OBJECT_RAW);
1811 v3s16 p = floatToInt(objectpos, BS);
1812 errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
1813 <<"could not emerge block for storing id="<<object->getId()
1814 <<" statically (pos="<<PP(p)<<")"<<std::endl;
1818 return object->getId();
1822 Remove objects that satisfy (isGone() && m_known_by_count==0)
1824 void ServerEnvironment::removeRemovedObjects()
1826 ScopeProfiler sp(g_profiler, "ServerEnvironment::removeRemovedObjects()", SPT_AVG);
1828 auto clear_cb = [this] (ServerActiveObject *obj, u16 id) {
1829 // This shouldn't happen but check it
1831 errorstream << "ServerEnvironment::removeRemovedObjects(): "
1832 << "NULL object found. id=" << id << std::endl;
1837 We will handle objects marked for removal or deactivation
1843 Delete static data from block if removed
1845 if (obj->isPendingRemoval())
1846 deleteStaticFromBlock(obj, id, MOD_REASON_REMOVE_OBJECTS_REMOVE, false);
1848 // If still known by clients, don't actually remove. On some future
1849 // invocation this will be 0, which is when removal will continue.
1850 if(obj->m_known_by_count > 0)
1854 Move static data from active to stored if deactivated
1856 if (!obj->isPendingRemoval() && obj->m_static_exists) {
1857 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
1859 const auto i = block->m_static_objects.m_active.find(id);
1860 if (i != block->m_static_objects.m_active.end()) {
1861 block->m_static_objects.m_stored.push_back(i->second);
1862 block->m_static_objects.m_active.erase(id);
1863 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1864 MOD_REASON_REMOVE_OBJECTS_DEACTIVATE);
1866 warningstream << "ServerEnvironment::removeRemovedObjects(): "
1867 << "id=" << id << " m_static_exists=true but "
1868 << "static data doesn't actually exist in "
1869 << PP(obj->m_static_block) << std::endl;
1872 infostream << "Failed to emerge block from which an object to "
1873 << "be deactivated was loaded from. id=" << id << std::endl;
1877 // Tell the object about removal
1878 obj->removingFromEnvironment();
1879 // Deregister in scripting api
1880 m_script->removeObjectReference(obj);
1883 if (obj->environmentDeletes())
1889 m_ao_manager.clear(clear_cb);
1892 static void print_hexdump(std::ostream &o, const std::string &data)
1894 const int linelength = 16;
1895 for(int l=0; ; l++){
1896 int i0 = linelength * l;
1897 bool at_end = false;
1898 int thislinelength = linelength;
1899 if(i0 + thislinelength > (int)data.size()){
1900 thislinelength = data.size() - i0;
1903 for(int di=0; di<linelength; di++){
1906 if(di<thislinelength)
1907 porting::mt_snprintf(buf, sizeof(buf), "%.2x ", data[i]);
1909 porting::mt_snprintf(buf, sizeof(buf), " ");
1913 for(int di=0; di<thislinelength; di++){
1926 ServerActiveObject* ServerEnvironment::createSAO(ActiveObjectType type, v3f pos,
1927 const std::string &data)
1930 case ACTIVEOBJECT_TYPE_LUAENTITY:
1931 return new LuaEntitySAO(this, pos, data);
1933 warningstream << "ServerActiveObject: No factory for type=" << type << std::endl;
1939 Convert stored objects from blocks near the players to active.
1941 void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s)
1946 // Ignore if no stored objects (to not set changed flag)
1947 if(block->m_static_objects.m_stored.empty())
1950 verbosestream<<"ServerEnvironment::activateObjects(): "
1951 <<"activating objects of block "<<PP(block->getPos())
1952 <<" ("<<block->m_static_objects.m_stored.size()
1953 <<" objects)"<<std::endl;
1954 bool large_amount = (block->m_static_objects.m_stored.size() > g_settings->getU16("max_objects_per_block"));
1956 errorstream<<"suspiciously large amount of objects detected: "
1957 <<block->m_static_objects.m_stored.size()<<" in "
1958 <<PP(block->getPos())
1959 <<"; removing all of them."<<std::endl;
1960 // Clear stored list
1961 block->m_static_objects.m_stored.clear();
1962 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1963 MOD_REASON_TOO_MANY_OBJECTS);
1967 // Activate stored objects
1968 std::vector<StaticObject> new_stored;
1969 for (const StaticObject &s_obj : block->m_static_objects.m_stored) {
1970 // Create an active object from the data
1971 ServerActiveObject *obj = createSAO((ActiveObjectType) s_obj.type, s_obj.pos,
1973 // If couldn't create object, store static data back.
1975 errorstream<<"ServerEnvironment::activateObjects(): "
1976 <<"failed to create active object from static object "
1977 <<"in block "<<PP(s_obj.pos/BS)
1978 <<" type="<<(int)s_obj.type<<" data:"<<std::endl;
1979 print_hexdump(verbosestream, s_obj.data);
1981 new_stored.push_back(s_obj);
1984 verbosestream<<"ServerEnvironment::activateObjects(): "
1985 <<"activated static object pos="<<PP(s_obj.pos/BS)
1986 <<" type="<<(int)s_obj.type<<std::endl;
1987 // This will also add the object to the active static list
1988 addActiveObjectRaw(obj, false, dtime_s);
1991 // Clear stored list
1992 block->m_static_objects.m_stored.clear();
1993 // Add leftover failed stuff to stored list
1994 for (const StaticObject &s_obj : new_stored) {
1995 block->m_static_objects.m_stored.push_back(s_obj);
1999 Note: Block hasn't really been modified here.
2000 The objects have just been activated and moved from the stored
2001 static list to the active static list.
2002 As such, the block is essentially the same.
2003 Thus, do not call block->raiseModified(MOD_STATE_WRITE_NEEDED).
2004 Otherwise there would be a huge amount of unnecessary I/O.
2009 Convert objects that are not standing inside active blocks to static.
2011 If m_known_by_count != 0, active object is not deleted, but static
2012 data is still updated.
2014 If force_delete is set, active object is deleted nevertheless. It
2015 shall only be set so in the destructor of the environment.
2017 If block wasn't generated (not in memory or on disk),
2019 void ServerEnvironment::deactivateFarObjects(bool _force_delete)
2021 auto cb_deactivate = [this, _force_delete] (ServerActiveObject *obj, u16 id) {
2022 // force_delete might be overriden per object
2023 bool force_delete = _force_delete;
2025 // Do not deactivate if disallowed
2026 if (!force_delete && !obj->shouldUnload())
2029 // removeRemovedObjects() is responsible for these
2030 if (!force_delete && obj->isGone())
2033 const v3f &objectpos = obj->getBasePosition();
2035 // The block in which the object resides in
2036 v3s16 blockpos_o = getNodeBlockPos(floatToInt(objectpos, BS));
2038 // If object's static data is stored in a deactivated block and object
2039 // is actually located in an active block, re-save to the block in
2040 // which the object is actually located in.
2041 if (!force_delete && obj->m_static_exists &&
2042 !m_active_blocks.contains(obj->m_static_block) &&
2043 m_active_blocks.contains(blockpos_o)) {
2045 // Delete from block where object was located
2046 deleteStaticFromBlock(obj, id, MOD_REASON_STATIC_DATA_REMOVED, false);
2048 StaticObject s_obj(obj, objectpos);
2049 // Save to block where object is located
2050 saveStaticToBlock(blockpos_o, id, obj, s_obj, MOD_REASON_STATIC_DATA_ADDED);
2055 // If block is still active, don't remove
2056 bool still_active = obj->isStaticAllowed() ?
2057 m_active_blocks.contains(blockpos_o) :
2058 getMap().getBlockNoCreateNoEx(blockpos_o) != nullptr;
2059 if (!force_delete && still_active)
2062 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
2063 << "deactivating object id=" << id << " on inactive block "
2064 << PP(blockpos_o) << std::endl;
2066 // If known by some client, don't immediately delete.
2067 bool pending_delete = (obj->m_known_by_count > 0 && !force_delete);
2070 Update the static data
2072 if (obj->isStaticAllowed()) {
2073 // Create new static object
2074 StaticObject s_obj(obj, objectpos);
2076 bool stays_in_same_block = false;
2077 bool data_changed = true;
2079 // Check if static data has changed considerably
2080 if (obj->m_static_exists) {
2081 if (obj->m_static_block == blockpos_o)
2082 stays_in_same_block = true;
2084 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
2087 const auto n = block->m_static_objects.m_active.find(id);
2088 if (n != block->m_static_objects.m_active.end()) {
2089 StaticObject static_old = n->second;
2091 float save_movem = obj->getMinimumSavedMovement();
2093 if (static_old.data == s_obj.data &&
2094 (static_old.pos - objectpos).getLength() < save_movem)
2095 data_changed = false;
2097 warningstream << "ServerEnvironment::deactivateFarObjects(): "
2098 << "id=" << id << " m_static_exists=true but "
2099 << "static data doesn't actually exist in "
2100 << PP(obj->m_static_block) << std::endl;
2106 While changes are always saved, blocks are only marked as modified
2107 if the object has moved or different staticdata. (see above)
2109 bool shall_be_written = (!stays_in_same_block || data_changed);
2110 u32 reason = shall_be_written ? MOD_REASON_STATIC_DATA_CHANGED : MOD_REASON_UNKNOWN;
2112 // Delete old static object
2113 deleteStaticFromBlock(obj, id, reason, false);
2115 // Add to the block where the object is located in
2116 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
2117 u16 store_id = pending_delete ? id : 0;
2118 if (!saveStaticToBlock(blockpos, store_id, obj, s_obj, reason))
2119 force_delete = true;
2122 // Regardless of what happens to the object at this point, deactivate it first.
2123 // This ensures that LuaEntity on_deactivate is always called.
2124 obj->markForDeactivation();
2127 If known by some client, set pending deactivation.
2128 Otherwise delete it immediately.
2130 if (pending_delete && !force_delete) {
2131 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
2132 << "object id=" << id << " is known by clients"
2133 << "; not deleting yet" << std::endl;
2138 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
2139 << "object id=" << id << " is not known by clients"
2140 << "; deleting" << std::endl;
2142 // Tell the object about removal
2143 obj->removingFromEnvironment();
2144 // Deregister in scripting api
2145 m_script->removeObjectReference(obj);
2147 // Delete active object
2148 if (obj->environmentDeletes())
2154 m_ao_manager.clear(cb_deactivate);
2157 void ServerEnvironment::deleteStaticFromBlock(
2158 ServerActiveObject *obj, u16 id, u32 mod_reason, bool no_emerge)
2160 if (!obj->m_static_exists)
2165 block = m_map->getBlockNoCreateNoEx(obj->m_static_block);
2167 block = m_map->emergeBlock(obj->m_static_block, false);
2170 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2171 << " when deleting static data of object from it. id=" << id << std::endl;
2175 block->m_static_objects.remove(id);
2176 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2177 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2179 obj->m_static_exists = false;
2182 bool ServerEnvironment::saveStaticToBlock(
2183 v3s16 blockpos, u16 store_id,
2184 ServerActiveObject *obj, const StaticObject &s_obj,
2187 MapBlock *block = nullptr;
2189 block = m_map->emergeBlock(blockpos);
2190 } catch (InvalidPositionException &e) {
2191 // Handled via NULL pointer
2192 // NOTE: emergeBlock's failure is usually determined by it
2193 // actually returning NULL
2197 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2198 << " when saving static data of object to it. id=" << store_id << std::endl;
2201 if (block->m_static_objects.m_stored.size() >= g_settings->getU16("max_objects_per_block")) {
2202 warningstream << "ServerEnv: Trying to store id = " << store_id
2203 << " statically but block " << PP(blockpos)
2204 << " already contains "
2205 << block->m_static_objects.m_stored.size()
2206 << " objects." << std::endl;
2210 block->m_static_objects.insert(store_id, s_obj);
2211 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2212 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2214 obj->m_static_exists = true;
2215 obj->m_static_block = blockpos;
2220 PlayerDatabase *ServerEnvironment::openPlayerDatabase(const std::string &name,
2221 const std::string &savedir, const Settings &conf)
2224 if (name == "sqlite3")
2225 return new PlayerDatabaseSQLite3(savedir);
2227 if (name == "dummy")
2228 return new Database_Dummy();
2231 if (name == "postgresql") {
2232 std::string connect_string;
2233 conf.getNoEx("pgsql_player_connection", connect_string);
2234 return new PlayerDatabasePostgreSQL(connect_string);
2239 if (name == "leveldb")
2240 return new PlayerDatabaseLevelDB(savedir);
2243 if (name == "files")
2244 return new PlayerDatabaseFiles(savedir + DIR_DELIM + "players");
2246 throw BaseException(std::string("Database backend ") + name + " not supported.");
2249 bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params,
2250 const Settings &cmd_args)
2252 std::string migrate_to = cmd_args.get("migrate-players");
2254 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2255 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2256 errorstream << "Cannot read world.mt!" << std::endl;
2260 if (!world_mt.exists("player_backend")) {
2261 errorstream << "Please specify your current backend in world.mt:"
2263 << " player_backend = {files|sqlite3|leveldb|postgresql}"
2268 std::string backend = world_mt.get("player_backend");
2269 if (backend == migrate_to) {
2270 errorstream << "Cannot migrate: new backend is same"
2271 << " as the old one" << std::endl;
2275 const std::string players_backup_path = game_params.world_path + DIR_DELIM
2278 if (backend == "files") {
2279 // Create backup directory
2280 fs::CreateDir(players_backup_path);
2284 PlayerDatabase *srcdb = ServerEnvironment::openPlayerDatabase(backend,
2285 game_params.world_path, world_mt);
2286 PlayerDatabase *dstdb = ServerEnvironment::openPlayerDatabase(migrate_to,
2287 game_params.world_path, world_mt);
2289 std::vector<std::string> player_list;
2290 srcdb->listPlayers(player_list);
2291 for (std::vector<std::string>::const_iterator it = player_list.begin();
2292 it != player_list.end(); ++it) {
2293 actionstream << "Migrating player " << it->c_str() << std::endl;
2294 RemotePlayer player(it->c_str(), NULL);
2295 PlayerSAO playerSAO(NULL, &player, 15000, false);
2297 srcdb->loadPlayer(&player, &playerSAO);
2299 playerSAO.finalize(&player, std::set<std::string>());
2300 player.setPlayerSAO(&playerSAO);
2302 dstdb->savePlayer(&player);
2304 // For files source, move player files to backup dir
2305 if (backend == "files") {
2307 game_params.world_path + DIR_DELIM + "players" + DIR_DELIM + (*it),
2308 players_backup_path + DIR_DELIM + (*it));
2312 actionstream << "Successfully migrated " << player_list.size() << " players"
2314 world_mt.set("player_backend", migrate_to);
2315 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2316 errorstream << "Failed to update world.mt!" << std::endl;
2318 actionstream << "world.mt updated" << std::endl;
2320 // When migration is finished from file backend, remove players directory if empty
2321 if (backend == "files") {
2322 fs::DeleteSingleFileOrEmptyDirectory(game_params.world_path + DIR_DELIM
2329 } catch (BaseException &e) {
2330 errorstream << "An error occurred during migration: " << e.what() << std::endl;
2336 AuthDatabase *ServerEnvironment::openAuthDatabase(
2337 const std::string &name, const std::string &savedir, const Settings &conf)
2339 if (name == "sqlite3")
2340 return new AuthDatabaseSQLite3(savedir);
2343 if (name == "postgresql") {
2344 std::string connect_string;
2345 conf.getNoEx("pgsql_auth_connection", connect_string);
2346 return new AuthDatabasePostgreSQL(connect_string);
2350 if (name == "files")
2351 return new AuthDatabaseFiles(savedir);
2354 if (name == "leveldb")
2355 return new AuthDatabaseLevelDB(savedir);
2358 throw BaseException(std::string("Database backend ") + name + " not supported.");
2361 bool ServerEnvironment::migrateAuthDatabase(
2362 const GameParams &game_params, const Settings &cmd_args)
2364 std::string migrate_to = cmd_args.get("migrate-auth");
2366 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2367 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2368 errorstream << "Cannot read world.mt!" << std::endl;
2372 std::string backend = "files";
2373 if (world_mt.exists("auth_backend"))
2374 backend = world_mt.get("auth_backend");
2376 warningstream << "No auth_backend found in world.mt, "
2377 "assuming \"files\"." << std::endl;
2379 if (backend == migrate_to) {
2380 errorstream << "Cannot migrate: new backend is same"
2381 << " as the old one" << std::endl;
2386 const std::unique_ptr<AuthDatabase> srcdb(ServerEnvironment::openAuthDatabase(
2387 backend, game_params.world_path, world_mt));
2388 const std::unique_ptr<AuthDatabase> dstdb(ServerEnvironment::openAuthDatabase(
2389 migrate_to, game_params.world_path, world_mt));
2391 std::vector<std::string> names_list;
2392 srcdb->listNames(names_list);
2393 for (const std::string &name : names_list) {
2394 actionstream << "Migrating auth entry for " << name << std::endl;
2396 AuthEntry authEntry;
2397 success = srcdb->getAuth(name, authEntry);
2398 success = success && dstdb->createAuth(authEntry);
2400 errorstream << "Failed to migrate " << name << std::endl;
2403 actionstream << "Successfully migrated " << names_list.size()
2404 << " auth entries" << std::endl;
2405 world_mt.set("auth_backend", migrate_to);
2406 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2407 errorstream << "Failed to update world.mt!" << std::endl;
2409 actionstream << "world.mt updated" << std::endl;
2411 if (backend == "files") {
2412 // special-case files migration:
2413 // move auth.txt to auth.txt.bak if possible
2414 std::string auth_txt_path =
2415 game_params.world_path + DIR_DELIM + "auth.txt";
2416 std::string auth_bak_path = auth_txt_path + ".bak";
2417 if (!fs::PathExists(auth_bak_path))
2418 if (fs::Rename(auth_txt_path, auth_bak_path))
2419 actionstream << "Renamed auth.txt to auth.txt.bak"
2422 errorstream << "Could not rename auth.txt to "
2423 "auth.txt.bak" << std::endl;
2425 warningstream << "auth.txt.bak already exists, auth.txt "
2426 "not renamed" << std::endl;
2429 } catch (BaseException &e) {
2430 errorstream << "An error occurred during migration: " << e.what()