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,
253 const u32 stamp, const float dtime_s)
255 // Precondition, we need m_lbm_lookup to be initialized
256 FATAL_ERROR_IF(!m_query_mode,
257 "attempted to query on non fully set up LBMManager");
258 v3s16 pos_of_block = block->getPosRelative();
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);
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, dtime_s);
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);
385 OnMapblocksChangedReceiver
388 void OnMapblocksChangedReceiver::onMapEditEvent(const MapEditEvent &event)
391 for (const v3s16 &p : event.modified_blocks) {
392 modified_blocks.insert(p);
400 // Random device to seed pseudo random generators.
401 static std::random_device seed;
403 ServerEnvironment::ServerEnvironment(ServerMap *map,
404 ServerScripting *script_iface, Server *server,
405 const std::string &path_world, MetricsBackend *mb):
408 m_script(script_iface),
410 m_path_world(path_world),
413 m_step_time_counter = mb->addCounter(
414 "minetest_env_step_time", "Time spent in environment step (in microseconds)");
416 m_active_block_gauge = mb->addGauge(
417 "minetest_env_active_blocks", "Number of active blocks");
419 m_active_object_gauge = mb->addGauge(
420 "minetest_env_active_objects", "Number of active objects");
423 void ServerEnvironment::init()
425 // Determine which database backend to use
426 std::string conf_path = m_path_world + DIR_DELIM + "world.mt";
429 std::string player_backend_name = "sqlite3";
430 std::string auth_backend_name = "sqlite3";
432 bool succeeded = conf.readConfigFile(conf_path.c_str());
434 // If we open world.mt read the backend configurations.
436 // Check that the world's blocksize matches the compiled MAP_BLOCKSIZE
438 conf.getU16NoEx("blocksize", blocksize);
439 if (blocksize != MAP_BLOCKSIZE) {
440 throw BaseException(std::string("The map's blocksize is not supported."));
443 // Read those values before setting defaults
444 bool player_backend_exists = conf.exists("player_backend");
445 bool auth_backend_exists = conf.exists("auth_backend");
447 // player backend is not set, assume it's legacy file backend.
448 if (!player_backend_exists) {
449 // fall back to files
450 conf.set("player_backend", "files");
451 player_backend_name = "files";
453 if (!conf.updateConfigFile(conf_path.c_str())) {
454 errorstream << "ServerEnvironment::ServerEnvironment(): "
455 << "Failed to update world.mt!" << std::endl;
458 conf.getNoEx("player_backend", player_backend_name);
461 // auth backend is not set, assume it's legacy file backend.
462 if (!auth_backend_exists) {
463 conf.set("auth_backend", "files");
464 auth_backend_name = "files";
466 if (!conf.updateConfigFile(conf_path.c_str())) {
467 errorstream << "ServerEnvironment::ServerEnvironment(): "
468 << "Failed to update world.mt!" << std::endl;
471 conf.getNoEx("auth_backend", auth_backend_name);
475 if (player_backend_name == "files") {
476 warningstream << "/!\\ You are using old player file backend. "
477 << "This backend is deprecated and will be removed in a future release /!\\"
478 << std::endl << "Switching to SQLite3 or PostgreSQL is advised, "
479 << "please read http://wiki.minetest.net/Database_backends." << std::endl;
482 if (auth_backend_name == "files") {
483 warningstream << "/!\\ You are using old auth file backend. "
484 << "This backend is deprecated and will be removed in a future release /!\\"
485 << std::endl << "Switching to SQLite3 is advised, "
486 << "please read http://wiki.minetest.net/Database_backends." << std::endl;
489 m_player_database = openPlayerDatabase(player_backend_name, m_path_world, conf);
490 m_auth_database = openAuthDatabase(auth_backend_name, m_path_world, conf);
492 if (m_map && m_script->has_on_mapblocks_changed()) {
493 m_map->addEventReceiver(&m_on_mapblocks_changed_receiver);
494 m_on_mapblocks_changed_receiver.receiving = true;
498 ServerEnvironment::~ServerEnvironment()
500 // Clear active block list.
501 // This makes the next one delete all active objects.
502 m_active_blocks.clear();
504 // Convert all objects to static and delete the active objects
505 deactivateFarObjects(true);
511 // Delete ActiveBlockModifiers
512 for (ABMWithState &m_abm : m_abms) {
516 // Deallocate players
517 for (RemotePlayer *m_player : m_players) {
521 delete m_player_database;
522 delete m_auth_database;
525 Map & ServerEnvironment::getMap()
530 ServerMap & ServerEnvironment::getServerMap()
535 RemotePlayer *ServerEnvironment::getPlayer(const session_t peer_id)
537 for (RemotePlayer *player : m_players) {
538 if (player->getPeerId() == peer_id)
544 RemotePlayer *ServerEnvironment::getPlayer(const char* name)
546 for (RemotePlayer *player : m_players) {
547 if (strcmp(player->getName(), name) == 0)
553 void ServerEnvironment::addPlayer(RemotePlayer *player)
556 Check that peer_ids are unique.
557 Also check that names are unique.
558 Exception: there can be multiple players with peer_id=0
560 // If peer id is non-zero, it has to be unique.
561 if (player->getPeerId() != PEER_ID_INEXISTENT)
562 FATAL_ERROR_IF(getPlayer(player->getPeerId()) != NULL, "Peer id not unique");
563 // Name has to be unique.
564 FATAL_ERROR_IF(getPlayer(player->getName()) != NULL, "Player name not unique");
566 m_players.push_back(player);
569 void ServerEnvironment::removePlayer(RemotePlayer *player)
571 for (std::vector<RemotePlayer *>::iterator it = m_players.begin();
572 it != m_players.end(); ++it) {
573 if ((*it) == player) {
581 bool ServerEnvironment::removePlayerFromDatabase(const std::string &name)
583 return m_player_database->removePlayer(name);
586 void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason,
587 const std::string &str_reason, bool reconnect)
589 for (RemotePlayer *player : m_players)
590 m_server->DenyAccess(player->getPeerId(), reason, str_reason, reconnect);
593 void ServerEnvironment::saveLoadedPlayers(bool force)
595 for (RemotePlayer *player : m_players) {
596 if (force || player->checkModified() || (player->getPlayerSAO() &&
597 player->getPlayerSAO()->getMeta().isModified())) {
599 m_player_database->savePlayer(player);
600 } catch (DatabaseException &e) {
601 errorstream << "Failed to save player " << player->getName() << " exception: "
602 << e.what() << std::endl;
609 void ServerEnvironment::savePlayer(RemotePlayer *player)
612 m_player_database->savePlayer(player);
613 } catch (DatabaseException &e) {
614 errorstream << "Failed to save player " << player->getName() << " exception: "
615 << e.what() << std::endl;
620 PlayerSAO *ServerEnvironment::loadPlayer(RemotePlayer *player, bool *new_player,
621 session_t peer_id, bool is_singleplayer)
623 PlayerSAO *playersao = new PlayerSAO(this, player, peer_id, is_singleplayer);
624 // Create player if it doesn't exist
625 if (!m_player_database->loadPlayer(player, playersao)) {
627 // Set player position
628 infostream << "Server: Finding spawn place for player \""
629 << player->getName() << "\"" << std::endl;
630 playersao->setBasePosition(m_server->findSpawnPos());
632 // Make sure the player is saved
633 player->setModified(true);
635 // If the player exists, ensure that they respawn inside legal bounds
636 // This fixes an assert crash when the player can't be added
637 // to the environment
638 if (objectpos_over_limit(playersao->getBasePosition())) {
639 actionstream << "Respawn position for player \""
640 << player->getName() << "\" outside limits, resetting" << std::endl;
641 playersao->setBasePosition(m_server->findSpawnPos());
645 // Add player to environment
648 /* Clean up old HUD elements from previous sessions */
651 /* Add object to environment */
652 addActiveObject(playersao);
654 // Update active blocks quickly for a bit so objects in those blocks appear on the client
655 m_fast_active_block_divider = 10;
660 void ServerEnvironment::saveMeta()
665 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
667 // Open file and serialize
668 std::ostringstream ss(std::ios_base::binary);
670 Settings args("EnvArgsEnd");
671 args.setU64("game_time", m_game_time);
672 args.setU64("time_of_day", getTimeOfDay());
673 args.setU64("last_clear_objects_time", m_last_clear_objects_time);
674 args.setU64("lbm_introduction_times_version", 1);
675 args.set("lbm_introduction_times",
676 m_lbm_mgr.createIntroductionTimesString());
677 args.setU64("day_count", m_day_count);
680 if(!fs::safeWriteToFile(path, ss.str()))
682 infostream<<"ServerEnvironment::saveMeta(): Failed to write "
684 throw SerializationError("Couldn't save env meta");
688 void ServerEnvironment::loadMeta()
690 SANITY_CHECK(!m_meta_loaded);
691 m_meta_loaded = true;
693 // If file doesn't exist, load default environment metadata
694 if (!fs::PathExists(m_path_world + DIR_DELIM "env_meta.txt")) {
695 infostream << "ServerEnvironment: Loading default environment metadata"
701 infostream << "ServerEnvironment: Loading environment metadata" << std::endl;
703 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
705 // Open file and deserialize
706 std::ifstream is(path.c_str(), std::ios_base::binary);
708 infostream << "ServerEnvironment::loadMeta(): Failed to open "
709 << path << std::endl;
710 throw SerializationError("Couldn't load env meta");
713 Settings args("EnvArgsEnd");
715 if (!args.parseConfigLines(is)) {
716 throw SerializationError("ServerEnvironment::loadMeta(): "
717 "EnvArgsEnd not found!");
721 m_game_time = args.getU64("game_time");
722 } catch (SettingNotFoundException &e) {
723 // Getting this is crucial, otherwise timestamps are useless
724 throw SerializationError("Couldn't load env meta game_time");
727 setTimeOfDay(args.exists("time_of_day") ?
728 // set day to early morning by default
729 args.getU64("time_of_day") : 5250);
731 m_last_clear_objects_time = args.exists("last_clear_objects_time") ?
732 // If missing, do as if clearObjects was never called
733 args.getU64("last_clear_objects_time") : 0;
735 std::string lbm_introduction_times;
737 u64 ver = args.getU64("lbm_introduction_times_version");
739 lbm_introduction_times = args.get("lbm_introduction_times");
741 infostream << "ServerEnvironment::loadMeta(): Non-supported"
742 << " introduction time version " << ver << std::endl;
744 } catch (SettingNotFoundException &e) {
745 // No problem, this is expected. Just continue with an empty string
747 m_lbm_mgr.loadIntroductionTimes(lbm_introduction_times, m_server, m_game_time);
749 m_day_count = args.exists("day_count") ?
750 args.getU64("day_count") : 0;
754 * called if env_meta.txt doesn't exist (e.g. new world)
756 void ServerEnvironment::loadDefaultMeta()
758 m_lbm_mgr.loadIntroductionTimes("", m_server, m_game_time);
763 ActiveBlockModifier *abm;
765 std::vector<content_t> required_neighbors;
766 bool check_required_neighbors; // false if required_neighbors is known to be empty
774 ServerEnvironment *m_env;
775 std::vector<std::vector<ActiveABM> *> m_aabms;
777 ABMHandler(std::vector<ABMWithState> &abms,
778 float dtime_s, ServerEnvironment *env,
784 const NodeDefManager *ndef = env->getGameDef()->ndef();
785 for (ABMWithState &abmws : abms) {
786 ActiveBlockModifier *abm = abmws.abm;
787 float trigger_interval = abm->getTriggerInterval();
788 if(trigger_interval < 0.001)
789 trigger_interval = 0.001;
790 float actual_interval = dtime_s;
792 abmws.timer += dtime_s;
793 if(abmws.timer < trigger_interval)
795 abmws.timer -= trigger_interval;
796 actual_interval = trigger_interval;
798 float chance = abm->getTriggerChance();
803 if (abm->getSimpleCatchUp()) {
804 float intervals = actual_interval / trigger_interval;
807 aabm.chance = chance / intervals;
808 if (aabm.chance == 0)
811 aabm.chance = chance;
814 aabm.min_y = abm->getMinY();
815 aabm.max_y = abm->getMaxY();
818 const std::vector<std::string> &required_neighbors_s =
819 abm->getRequiredNeighbors();
820 for (const std::string &required_neighbor_s : required_neighbors_s) {
821 ndef->getIds(required_neighbor_s, aabm.required_neighbors);
823 aabm.check_required_neighbors = !required_neighbors_s.empty();
826 const std::vector<std::string> &contents_s = abm->getTriggerContents();
827 for (const std::string &content_s : contents_s) {
828 std::vector<content_t> ids;
829 ndef->getIds(content_s, ids);
830 for (content_t c : ids) {
831 if (c >= m_aabms.size())
832 m_aabms.resize(c + 256, NULL);
834 m_aabms[c] = new std::vector<ActiveABM>;
835 m_aabms[c]->push_back(aabm);
843 for (auto &aabms : m_aabms)
847 // Find out how many objects the given block and its neighbors contain.
848 // Returns the number of objects in the block, and also in 'wider' the
849 // number of objects in the block and all its neighbors. The latter
850 // may an estimate if any neighbors are unloaded.
851 u32 countObjects(MapBlock *block, ServerMap * map, u32 &wider)
854 u32 wider_unknown_count = 0;
855 for(s16 x=-1; x<=1; x++)
856 for(s16 y=-1; y<=1; y++)
857 for(s16 z=-1; z<=1; z++)
859 MapBlock *block2 = map->getBlockNoCreateNoEx(
860 block->getPos() + v3s16(x,y,z));
862 wider_unknown_count++;
865 wider += block2->m_static_objects.size();
868 u32 active_object_count = block->m_static_objects.getActiveSize();
869 u32 wider_known_count = 3 * 3 * 3 - wider_unknown_count;
870 wider += wider_unknown_count * wider / wider_known_count;
871 return active_object_count;
873 void apply(MapBlock *block, int &blocks_scanned, int &abms_run, int &blocks_cached)
878 // Check the content type cache first
879 // to see whether there are any ABMs
880 // to be run at all for this block.
881 if (block->contents_cached) {
883 bool run_abms = false;
884 for (content_t c : block->contents) {
885 if (c < m_aabms.size() && m_aabms[c]) {
894 block->contents.clear();
898 ServerMap *map = &m_env->getServerMap();
900 u32 active_object_count_wider;
901 u32 active_object_count = this->countObjects(block, map, active_object_count_wider);
902 m_env->m_added_objects = 0;
905 for(p0.X=0; p0.X<MAP_BLOCKSIZE; p0.X++)
906 for(p0.Y=0; p0.Y<MAP_BLOCKSIZE; p0.Y++)
907 for(p0.Z=0; p0.Z<MAP_BLOCKSIZE; p0.Z++)
909 MapNode n = block->getNodeNoCheck(p0);
910 content_t c = n.getContent();
911 // Cache content types as we go
912 if (!block->contents_cached && !block->do_not_cache_contents) {
913 block->contents.insert(c);
914 if (block->contents.size() > 64) {
915 // Too many different nodes... don't try to cache
916 block->do_not_cache_contents = true;
917 block->contents.clear();
921 if (c >= m_aabms.size() || !m_aabms[c])
924 v3s16 p = p0 + block->getPosRelative();
925 for (ActiveABM &aabm : *m_aabms[c]) {
926 if ((p.Y < aabm.min_y) || (p.Y > aabm.max_y))
929 if (myrand() % aabm.chance != 0)
933 if (aabm.check_required_neighbors) {
935 for(p1.X = p0.X-1; p1.X <= p0.X+1; p1.X++)
936 for(p1.Y = p0.Y-1; p1.Y <= p0.Y+1; p1.Y++)
937 for(p1.Z = p0.Z-1; p1.Z <= p0.Z+1; p1.Z++)
942 if (block->isValidPosition(p1)) {
943 // if the neighbor is found on the same map block
944 // get it straight from there
945 const MapNode &n = block->getNodeNoCheck(p1);
948 // otherwise consult the map
949 MapNode n = map->getNode(p1 + block->getPosRelative());
952 if (CONTAINS(aabm.required_neighbors, c))
955 // No required neighbor found
961 // Call all the trigger variations
962 aabm.abm->trigger(m_env, p, n);
963 aabm.abm->trigger(m_env, p, n,
964 active_object_count, active_object_count_wider);
966 // Count surrounding objects again if the abms added any
967 if(m_env->m_added_objects > 0) {
968 active_object_count = countObjects(block, map, active_object_count_wider);
969 m_env->m_added_objects = 0;
972 // Update and check node after possible modification
973 n = block->getNodeNoCheck(p0);
974 if (n.getContent() != c)
978 block->contents_cached = !block->do_not_cache_contents;
982 void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime)
984 // Reset usage timer immediately, otherwise a block that becomes active
985 // again at around the same time as it would normally be unloaded will
986 // get unloaded incorrectly. (I think this still leaves a small possibility
987 // of a race condition between this and server::AsyncRunStep, which only
988 // some kind of synchronisation will fix, but it at least reduces the window
989 // of opportunity for it to break from seconds to nanoseconds)
990 block->resetUsageTimer();
992 // Get time difference
994 u32 stamp = block->getTimestamp();
995 if (m_game_time > stamp && stamp != BLOCK_TIMESTAMP_UNDEFINED)
996 dtime_s = m_game_time - stamp;
997 dtime_s += additional_dtime;
999 /*infostream<<"ServerEnvironment::activateBlock(): block timestamp: "
1000 <<stamp<<", game time: "<<m_game_time<<std::endl;*/
1002 // Remove stored static objects if clearObjects was called since block's timestamp
1003 if (stamp == BLOCK_TIMESTAMP_UNDEFINED || stamp < m_last_clear_objects_time) {
1004 block->m_static_objects.clearStored();
1005 // do not set changed flag to avoid unnecessary mapblock writes
1008 // Set current time as timestamp
1009 block->setTimestampNoChangedFlag(m_game_time);
1011 /*infostream<<"ServerEnvironment::activateBlock(): block is "
1012 <<dtime_s<<" seconds old."<<std::endl;*/
1014 // Activate stored objects
1015 activateObjects(block, dtime_s);
1017 /* Handle LoadingBlockModifiers */
1018 m_lbm_mgr.applyLBMs(this, block, stamp, (float)dtime_s);
1021 block->step((float)dtime_s, [&](v3s16 p, MapNode n, f32 d) -> bool {
1022 return m_script->node_on_timer(p, n, d);
1026 void ServerEnvironment::addActiveBlockModifier(ActiveBlockModifier *abm)
1028 m_abms.emplace_back(abm);
1031 void ServerEnvironment::addLoadingBlockModifierDef(LoadingBlockModifierDef *lbm)
1033 m_lbm_mgr.addLBMDef(lbm);
1036 bool ServerEnvironment::setNode(v3s16 p, const MapNode &n)
1038 const NodeDefManager *ndef = m_server->ndef();
1039 MapNode n_old = m_map->getNode(p);
1041 const ContentFeatures &cf_old = ndef->get(n_old);
1044 if (cf_old.has_on_destruct)
1045 m_script->node_on_destruct(p, n_old);
1048 if (!m_map->addNodeWithEvent(p, n))
1051 // Update active VoxelManipulator if a mapgen thread
1052 m_map->updateVManip(p);
1054 // Call post-destructor
1055 if (cf_old.has_after_destruct)
1056 m_script->node_after_destruct(p, n_old);
1058 // Retrieve node content features
1059 // if new node is same as old, reuse old definition to prevent a lookup
1060 const ContentFeatures &cf_new = n_old == n ? cf_old : ndef->get(n);
1063 if (cf_new.has_on_construct)
1064 m_script->node_on_construct(p, n);
1069 bool ServerEnvironment::removeNode(v3s16 p)
1071 const NodeDefManager *ndef = m_server->ndef();
1072 MapNode n_old = m_map->getNode(p);
1075 if (ndef->get(n_old).has_on_destruct)
1076 m_script->node_on_destruct(p, n_old);
1079 // This is slightly optimized compared to addNodeWithEvent(air)
1080 if (!m_map->removeNodeWithEvent(p))
1083 // Update active VoxelManipulator if a mapgen thread
1084 m_map->updateVManip(p);
1086 // Call post-destructor
1087 if (ndef->get(n_old).has_after_destruct)
1088 m_script->node_after_destruct(p, n_old);
1090 // Air doesn't require constructor
1094 bool ServerEnvironment::swapNode(v3s16 p, const MapNode &n)
1096 if (!m_map->addNodeWithEvent(p, n, false))
1099 // Update active VoxelManipulator if a mapgen thread
1100 m_map->updateVManip(p);
1105 u8 ServerEnvironment::findSunlight(v3s16 pos) const
1107 // Directions for neighboring nodes with specified order
1108 static const v3s16 dirs[] = {
1109 v3s16(-1, 0, 0), v3s16(1, 0, 0), v3s16(0, 0, -1), v3s16(0, 0, 1),
1110 v3s16(0, -1, 0), v3s16(0, 1, 0)
1113 const NodeDefManager *ndef = m_server->ndef();
1115 // found_light remembers the highest known sunlight value at pos
1118 struct stack_entry {
1122 std::stack<stack_entry> stack;
1123 stack.push({pos, 0});
1125 std::unordered_map<s64, s8> dists;
1126 dists[MapDatabase::getBlockAsInteger(pos)] = 0;
1128 while (!stack.empty()) {
1129 struct stack_entry e = stack.top();
1132 v3s16 currentPos = e.pos;
1133 s8 dist = e.dist + 1;
1135 for (const v3s16& off : dirs) {
1136 v3s16 neighborPos = currentPos + off;
1137 s64 neighborHash = MapDatabase::getBlockAsInteger(neighborPos);
1139 // Do not walk neighborPos multiple times unless the distance to the start
1140 // position is shorter
1141 auto it = dists.find(neighborHash);
1142 if (it != dists.end() && dist >= it->second)
1146 bool is_position_ok;
1147 MapNode node = m_map->getNode(neighborPos, &is_position_ok);
1148 if (!is_position_ok) {
1149 // This happens very rarely because the map at currentPos is loaded
1150 m_map->emergeBlock(neighborPos, false);
1151 node = m_map->getNode(neighborPos, &is_position_ok);
1152 if (!is_position_ok)
1153 continue; // not generated
1156 const ContentFeatures &def = ndef->get(node);
1157 if (!def.sunlight_propagates) {
1158 // Do not test propagation here again
1159 dists[neighborHash] = -1;
1163 // Sunlight could have come from here
1164 dists[neighborHash] = dist;
1165 u8 daylight = node.param1 & 0x0f;
1167 // In the special case where sunlight shines from above and thus
1168 // does not decrease with upwards distance, daylight is always
1169 // bigger than nightlight, which never reaches 15
1170 int possible_finlight = daylight - dist;
1171 if (possible_finlight <= found_light) {
1172 // Light from here cannot make a brighter light at currentPos than
1177 u8 nightlight = node.param1 >> 4;
1178 if (daylight > nightlight) {
1179 // Found a valid daylight
1180 found_light = possible_finlight;
1182 // Sunlight may be darker, so walk the neighbors
1183 stack.push({neighborPos, dist});
1190 void ServerEnvironment::clearObjects(ClearObjectsMode mode)
1192 infostream << "ServerEnvironment::clearObjects(): "
1193 << "Removing all active objects" << std::endl;
1194 auto cb_removal = [this] (ServerActiveObject *obj, u16 id) {
1195 if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER)
1198 // Delete static object if block is loaded
1199 deleteStaticFromBlock(obj, id, MOD_REASON_CLEAR_ALL_OBJECTS, true);
1201 // If known by some client, don't delete immediately
1202 if (obj->m_known_by_count > 0) {
1203 obj->markForRemoval();
1207 // Tell the object about removal
1208 obj->removingFromEnvironment();
1209 // Deregister in scripting api
1210 m_script->removeObjectReference(obj);
1212 // Delete active object
1213 if (obj->environmentDeletes())
1219 m_ao_manager.clear(cb_removal);
1221 // Get list of loaded blocks
1222 std::vector<v3s16> loaded_blocks;
1223 infostream << "ServerEnvironment::clearObjects(): "
1224 << "Listing all loaded blocks" << std::endl;
1225 m_map->listAllLoadedBlocks(loaded_blocks);
1226 infostream << "ServerEnvironment::clearObjects(): "
1227 << "Done listing all loaded blocks: "
1228 << loaded_blocks.size()<<std::endl;
1230 // Get list of loadable blocks
1231 std::vector<v3s16> loadable_blocks;
1232 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1233 infostream << "ServerEnvironment::clearObjects(): "
1234 << "Listing all loadable blocks" << std::endl;
1235 m_map->listAllLoadableBlocks(loadable_blocks);
1236 infostream << "ServerEnvironment::clearObjects(): "
1237 << "Done listing all loadable blocks: "
1238 << loadable_blocks.size() << std::endl;
1240 loadable_blocks = loaded_blocks;
1243 actionstream << "ServerEnvironment::clearObjects(): "
1244 << "Now clearing objects in " << loadable_blocks.size()
1245 << " blocks" << std::endl;
1247 // Grab a reference on each loaded block to avoid unloading it
1248 for (v3s16 p : loaded_blocks) {
1249 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1250 assert(block != NULL);
1254 // Remove objects in all loadable blocks
1255 u32 unload_interval = U32_MAX;
1256 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1257 unload_interval = g_settings->getS32("max_clearobjects_extra_loaded_blocks");
1258 unload_interval = MYMAX(unload_interval, 1);
1260 u32 report_interval = loadable_blocks.size() / 10;
1261 u32 num_blocks_checked = 0;
1262 u32 num_blocks_cleared = 0;
1263 u32 num_objs_cleared = 0;
1264 for (auto i = loadable_blocks.begin();
1265 i != loadable_blocks.end(); ++i) {
1267 MapBlock *block = m_map->emergeBlock(p, false);
1269 errorstream << "ServerEnvironment::clearObjects(): "
1270 << "Failed to emerge block " << PP(p) << std::endl;
1274 u32 num_cleared = block->clearObjects();
1275 if (num_cleared > 0) {
1276 num_objs_cleared += num_cleared;
1277 num_blocks_cleared++;
1279 num_blocks_checked++;
1281 if (report_interval != 0 &&
1282 num_blocks_checked % report_interval == 0) {
1283 float percent = 100.0 * (float)num_blocks_checked /
1284 loadable_blocks.size();
1285 actionstream << "ServerEnvironment::clearObjects(): "
1286 << "Cleared " << num_objs_cleared << " objects"
1287 << " in " << num_blocks_cleared << " blocks ("
1288 << percent << "%)" << std::endl;
1290 if (num_blocks_checked % unload_interval == 0) {
1291 m_map->unloadUnreferencedBlocks();
1294 m_map->unloadUnreferencedBlocks();
1296 // Drop references that were added above
1297 for (v3s16 p : loaded_blocks) {
1298 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1303 m_last_clear_objects_time = m_game_time;
1305 actionstream << "ServerEnvironment::clearObjects(): "
1306 << "Finished: Cleared " << num_objs_cleared << " objects"
1307 << " in " << num_blocks_cleared << " blocks" << std::endl;
1310 void ServerEnvironment::step(float dtime)
1312 ScopeProfiler sp2(g_profiler, "ServerEnv::step()", SPT_AVG);
1313 const auto start_time = porting::getTimeUs();
1315 /* Step time of day */
1316 stepTimeOfDay(dtime);
1319 // NOTE: This is kind of funny on a singleplayer game, but doesn't
1320 // really matter that much.
1321 static thread_local const float server_step =
1322 g_settings->getFloat("dedicated_server_step");
1323 m_recommended_send_interval = server_step;
1329 m_game_time_fraction_counter += dtime;
1330 u32 inc_i = (u32)m_game_time_fraction_counter;
1331 m_game_time += inc_i;
1332 m_game_time_fraction_counter -= (float)inc_i;
1339 ScopeProfiler sp(g_profiler, "ServerEnv: move players", SPT_AVG);
1340 for (RemotePlayer *player : m_players) {
1341 // Ignore disconnected players
1342 if (player->getPeerId() == PEER_ID_INEXISTENT)
1346 player->move(dtime, this, 100 * BS);
1351 Manage active block list
1353 if (m_active_blocks_mgmt_interval.step(dtime, m_cache_active_block_mgmt_interval / m_fast_active_block_divider)) {
1354 ScopeProfiler sp(g_profiler, "ServerEnv: update active blocks", SPT_AVG);
1357 Get player block positions
1359 std::vector<PlayerSAO*> players;
1360 players.reserve(m_players.size());
1361 for (RemotePlayer *player : m_players) {
1362 // Ignore disconnected players
1363 if (player->getPeerId() == PEER_ID_INEXISTENT)
1366 PlayerSAO *playersao = player->getPlayerSAO();
1369 players.push_back(playersao);
1373 Update list of active blocks, collecting changes
1375 // use active_object_send_range_blocks since that is max distance
1376 // for active objects sent the client anyway
1377 static thread_local const s16 active_object_range =
1378 g_settings->getS16("active_object_send_range_blocks");
1379 static thread_local const s16 active_block_range =
1380 g_settings->getS16("active_block_range");
1381 std::set<v3s16> blocks_removed;
1382 std::set<v3s16> blocks_added;
1383 m_active_blocks.update(players, active_block_range, active_object_range,
1384 blocks_removed, blocks_added);
1387 Handle removed blocks
1390 // Convert active objects that are no more in active blocks to static
1391 deactivateFarObjects(false);
1393 for (const v3s16 &p: blocks_removed) {
1394 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1398 // Set current time as timestamp (and let it set ChangedFlag)
1399 block->setTimestamp(m_game_time);
1406 for (const v3s16 &p: blocks_added) {
1407 MapBlock *block = m_map->getBlockOrEmerge(p);
1409 // TODO: The blocks removed here will only be picked up again
1410 // on the next cycle. To minimize the latency of objects being
1411 // activated we could remember the blocks pending activating
1412 // and activate them instantly as soon as they're loaded.
1413 m_active_blocks.remove(p);
1417 activateBlock(block);
1420 // Some blocks may be removed again by the code above so do this here
1421 m_active_block_gauge->set(m_active_blocks.size());
1423 if (m_fast_active_block_divider > 1)
1424 --m_fast_active_block_divider;
1428 Mess around in active blocks
1430 if (m_active_blocks_nodemetadata_interval.step(dtime, m_cache_nodetimer_interval)) {
1431 ScopeProfiler sp(g_profiler, "ServerEnv: Run node timers", SPT_AVG);
1433 float dtime = m_cache_nodetimer_interval;
1435 for (const v3s16 &p: m_active_blocks.m_list) {
1436 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1440 // Reset block usage timer
1441 block->resetUsageTimer();
1443 // Set current time as timestamp
1444 block->setTimestampNoChangedFlag(m_game_time);
1445 // If time has changed much from the one on disk,
1446 // set block to be saved when it is unloaded
1447 if(block->getTimestamp() > block->getDiskTimestamp() + 60)
1448 block->raiseModified(MOD_STATE_WRITE_AT_UNLOAD,
1449 MOD_REASON_BLOCK_EXPIRED);
1452 block->step(dtime, [&](v3s16 p, MapNode n, f32 d) -> bool {
1453 return m_script->node_on_timer(p, n, d);
1458 if (m_active_block_modifier_interval.step(dtime, m_cache_abm_interval)) {
1459 ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg per interval", SPT_AVG);
1460 TimeTaker timer("modify in active blocks per interval");
1462 // Shuffle to prevent persistent artifacts of ordering
1463 std::shuffle(m_abms.begin(), m_abms.end(), m_rgen);
1465 // Initialize handling of ActiveBlockModifiers
1466 ABMHandler abmhandler(m_abms, m_cache_abm_interval, this, true);
1468 int blocks_scanned = 0;
1470 int blocks_cached = 0;
1472 std::vector<v3s16> output(m_active_blocks.m_abm_list.size());
1474 // Shuffle the active blocks so that each block gets an equal chance
1475 // of having its ABMs run.
1476 std::copy(m_active_blocks.m_abm_list.begin(), m_active_blocks.m_abm_list.end(), output.begin());
1477 std::shuffle(output.begin(), output.end(), m_rgen);
1480 // determine the time budget for ABMs
1481 u32 max_time_ms = m_cache_abm_interval * 1000 * m_cache_abm_time_budget;
1482 for (const v3s16 &p : output) {
1483 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1489 // Set current time as timestamp
1490 block->setTimestampNoChangedFlag(m_game_time);
1492 /* Handle ActiveBlockModifiers */
1493 abmhandler.apply(block, blocks_scanned, abms_run, blocks_cached);
1495 u32 time_ms = timer.getTimerTime();
1497 if (time_ms > max_time_ms) {
1498 warningstream << "active block modifiers took "
1499 << time_ms << "ms (processed " << i << " of "
1500 << output.size() << " active blocks)" << std::endl;
1504 g_profiler->avg("ServerEnv: active blocks", m_active_blocks.m_abm_list.size());
1505 g_profiler->avg("ServerEnv: active blocks cached", blocks_cached);
1506 g_profiler->avg("ServerEnv: active blocks scanned for ABMs", blocks_scanned);
1507 g_profiler->avg("ServerEnv: ABMs run", abms_run);
1513 Step script environment (run global on_step())
1515 m_script->environment_Step(dtime);
1517 m_script->stepAsync();
1523 ScopeProfiler sp(g_profiler, "ServerEnv: Run SAO::step()", SPT_AVG);
1525 // This helps the objects to send data at the same time
1526 bool send_recommended = false;
1527 m_send_recommended_timer += dtime;
1528 if (m_send_recommended_timer > getSendRecommendedInterval()) {
1529 m_send_recommended_timer -= getSendRecommendedInterval();
1530 send_recommended = true;
1533 u32 object_count = 0;
1535 auto cb_state = [&](ServerActiveObject *obj) {
1541 obj->step(dtime, send_recommended);
1542 // Read messages from object
1543 obj->dumpAOMessagesToQueue(m_active_object_messages);
1545 m_ao_manager.step(dtime, cb_state);
1547 m_active_object_gauge->set(object_count);
1551 Manage active objects
1553 if (m_object_management_interval.step(dtime, 0.5)) {
1554 removeRemovedObjects();
1558 Manage particle spawner expiration
1560 if (m_particle_management_interval.step(dtime, 1.0)) {
1561 for (std::unordered_map<u32, float>::iterator i = m_particle_spawners.begin();
1562 i != m_particle_spawners.end(); ) {
1563 //non expiring spawners
1564 if (i->second == PARTICLE_SPAWNER_NO_EXPIRY) {
1570 if (i->second <= 0.f)
1571 m_particle_spawners.erase(i++);
1577 // Send outdated player inventories
1578 for (RemotePlayer *player : m_players) {
1579 if (player->getPeerId() == PEER_ID_INEXISTENT)
1582 PlayerSAO *sao = player->getPlayerSAO();
1583 if (sao && player->inventory.checkModified())
1584 m_server->SendInventory(sao, true);
1587 // Send outdated detached inventories
1588 m_server->sendDetachedInventories(PEER_ID_INEXISTENT, true);
1590 // Notify mods of modified mapblocks
1591 if (m_on_mapblocks_changed_receiver.receiving &&
1592 !m_on_mapblocks_changed_receiver.modified_blocks.empty()) {
1593 std::unordered_set<v3s16> modified_blocks;
1594 std::swap(modified_blocks, m_on_mapblocks_changed_receiver.modified_blocks);
1595 m_script->on_mapblocks_changed(modified_blocks);
1598 const auto end_time = porting::getTimeUs();
1599 m_step_time_counter->increment(end_time - start_time);
1602 ServerEnvironment::BlockStatus ServerEnvironment::getBlockStatus(v3s16 blockpos)
1604 if (m_active_blocks.contains(blockpos))
1607 const MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos);
1611 if (m_map->isBlockInQueue(blockpos))
1617 u32 ServerEnvironment::addParticleSpawner(float exptime)
1619 // Timers with lifetime 0 do not expire
1620 float time = exptime > 0.f ? exptime : PARTICLE_SPAWNER_NO_EXPIRY;
1623 for (;;) { // look for unused particlespawner id
1625 std::unordered_map<u32, float>::iterator f = m_particle_spawners.find(id);
1626 if (f == m_particle_spawners.end()) {
1627 m_particle_spawners[id] = time;
1634 u32 ServerEnvironment::addParticleSpawner(float exptime, u16 attached_id)
1636 u32 id = addParticleSpawner(exptime);
1637 m_particle_spawner_attachments[id] = attached_id;
1638 if (ServerActiveObject *obj = getActiveObject(attached_id)) {
1639 obj->attachParticleSpawner(id);
1644 void ServerEnvironment::deleteParticleSpawner(u32 id, bool remove_from_object)
1646 m_particle_spawners.erase(id);
1647 const auto &it = m_particle_spawner_attachments.find(id);
1648 if (it != m_particle_spawner_attachments.end()) {
1649 u16 obj_id = it->second;
1650 ServerActiveObject *sao = getActiveObject(obj_id);
1651 if (sao != NULL && remove_from_object) {
1652 sao->detachParticleSpawner(id);
1654 m_particle_spawner_attachments.erase(id);
1658 u16 ServerEnvironment::addActiveObject(ServerActiveObject *object)
1660 assert(object); // Pre-condition
1662 u16 id = addActiveObjectRaw(object, true, 0);
1667 Finds out what new objects have been added to
1668 inside a radius around a position
1670 void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius,
1672 std::set<u16> ¤t_objects,
1673 std::queue<u16> &added_objects)
1675 f32 radius_f = radius * BS;
1676 f32 player_radius_f = player_radius * BS;
1678 if (player_radius_f < 0.0f)
1679 player_radius_f = 0.0f;
1681 m_ao_manager.getAddedActiveObjectsAroundPos(playersao->getBasePosition(), radius_f,
1682 player_radius_f, current_objects, added_objects);
1686 Finds out what objects have been removed from
1687 inside a radius around a position
1689 void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius,
1691 std::set<u16> ¤t_objects,
1692 std::queue<u16> &removed_objects)
1694 f32 radius_f = radius * BS;
1695 f32 player_radius_f = player_radius * BS;
1697 if (player_radius_f < 0)
1698 player_radius_f = 0;
1700 Go through current_objects; object is removed if:
1701 - object is not found in m_active_objects (this is actually an
1702 error condition; objects should be removed only after all clients
1703 have been informed about removal), or
1704 - object is to be removed or deactivated, or
1705 - object is too far away
1707 for (u16 id : current_objects) {
1708 ServerActiveObject *object = getActiveObject(id);
1710 if (object == NULL) {
1711 infostream << "ServerEnvironment::getRemovedActiveObjects():"
1712 << " object in current_objects is NULL" << std::endl;
1713 removed_objects.push(id);
1717 if (object->isGone()) {
1718 removed_objects.push(id);
1722 f32 distance_f = object->getBasePosition().getDistanceFrom(playersao->getBasePosition());
1723 if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1724 if (distance_f <= player_radius_f || player_radius_f == 0)
1726 } else if (distance_f <= radius_f)
1729 // Object is no longer visible
1730 removed_objects.push(id);
1734 void ServerEnvironment::setStaticForActiveObjectsInBlock(
1735 v3s16 blockpos, bool static_exists, v3s16 static_block)
1737 MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos);
1741 for (auto &so_it : block->m_static_objects.getAllActives()) {
1742 // Get the ServerActiveObject counterpart to this StaticObject
1743 ServerActiveObject *sao = m_ao_manager.getActiveObject(so_it.first);
1745 // If this ever happens, there must be some kind of nasty bug.
1746 errorstream << "ServerEnvironment::setStaticForObjectsInBlock(): "
1747 "Object from MapBlock::m_static_objects::m_active not found "
1748 "in m_active_objects";
1752 sao->m_static_exists = static_exists;
1753 sao->m_static_block = static_block;
1757 bool ServerEnvironment::getActiveObjectMessage(ActiveObjectMessage *dest)
1759 if (m_active_object_messages.empty())
1762 *dest = std::move(m_active_object_messages.front());
1763 m_active_object_messages.pop();
1767 void ServerEnvironment::getSelectedActiveObjects(
1768 const core::line3d<f32> &shootline_on_map,
1769 std::vector<PointedThing> &objects)
1771 std::vector<ServerActiveObject *> objs;
1772 getObjectsInsideRadius(objs, shootline_on_map.start,
1773 shootline_on_map.getLength() + 10.0f, nullptr);
1774 const v3f line_vector = shootline_on_map.getVector();
1776 for (auto obj : objs) {
1779 aabb3f selection_box;
1780 if (!obj->getSelectionBox(&selection_box))
1783 v3f pos = obj->getBasePosition();
1784 v3f rel_pos = shootline_on_map.start - pos;
1786 v3f current_intersection;
1788 v3f current_raw_normal;
1790 ObjectProperties *props = obj->accessObjectProperties();
1792 UnitSAO* usao = dynamic_cast<UnitSAO*>(obj);
1793 if (props->rotate_selectionbox && usao != nullptr) {
1794 collision = boxLineCollision(selection_box, usao->getTotalRotation(),
1795 rel_pos, line_vector, ¤t_intersection, ¤t_normal, ¤t_raw_normal);
1797 collision = boxLineCollision(selection_box, rel_pos, line_vector,
1798 ¤t_intersection, ¤t_normal);
1799 current_raw_normal = current_normal;
1802 current_intersection += pos;
1803 objects.emplace_back(
1804 (s16) obj->getId(), current_intersection, current_normal, current_raw_normal,
1805 (current_intersection - shootline_on_map.start).getLengthSQ());
1811 ************ Private methods *************
1814 u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object,
1815 bool set_changed, u32 dtime_s)
1817 if (!m_ao_manager.registerObject(object)) {
1821 // Register reference in scripting api (must be done before post-init)
1822 m_script->addObjectReference(object);
1823 // Post-initialize object
1824 object->addedToEnvironment(dtime_s);
1826 // Add static data to block
1827 if (object->isStaticAllowed()) {
1828 // Add static object to active static list of the block
1829 v3f objectpos = object->getBasePosition();
1830 StaticObject s_obj(object, objectpos);
1831 // Add to the block where the object is located in
1832 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
1833 MapBlock *block = m_map->emergeBlock(blockpos);
1835 block->m_static_objects.setActive(object->getId(), s_obj);
1836 object->m_static_exists = true;
1837 object->m_static_block = blockpos;
1840 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1841 MOD_REASON_ADD_ACTIVE_OBJECT_RAW);
1843 v3s16 p = floatToInt(objectpos, BS);
1844 errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
1845 <<"could not emerge block for storing id="<<object->getId()
1846 <<" statically (pos="<<PP(p)<<")"<<std::endl;
1850 return object->getId();
1854 Remove objects that satisfy (isGone() && m_known_by_count==0)
1856 void ServerEnvironment::removeRemovedObjects()
1858 ScopeProfiler sp(g_profiler, "ServerEnvironment::removeRemovedObjects()", SPT_AVG);
1860 auto clear_cb = [this](ServerActiveObject *obj, u16 id) {
1861 // This shouldn't happen but check it
1863 errorstream << "ServerEnvironment::removeRemovedObjects(): "
1864 << "NULL object found. id=" << id << std::endl;
1869 We will handle objects marked for removal or deactivation
1875 Delete static data from block if removed
1877 if (obj->isPendingRemoval())
1878 deleteStaticFromBlock(obj, id, MOD_REASON_REMOVE_OBJECTS_REMOVE, false);
1880 // If still known by clients, don't actually remove. On some future
1881 // invocation this will be 0, which is when removal will continue.
1882 if (obj->m_known_by_count > 0)
1886 Move static data from active to stored if deactivated
1888 if (!obj->isPendingRemoval() && obj->m_static_exists) {
1889 if (MapBlock *block = m_map->emergeBlock(obj->m_static_block, false)) {
1890 if (!block->storeActiveObject(id)) {
1891 warningstream << "ServerEnvironment::removeRemovedObjects(): "
1892 << "id=" << id << " m_static_exists=true but "
1893 << "static data doesn't actually exist in "
1894 << PP(obj->m_static_block) << std::endl;
1897 infostream << "Failed to emerge block from which an object to "
1898 << "be deactivated was loaded from. id=" << id << std::endl;
1902 // Tell the object about removal
1903 obj->removingFromEnvironment();
1904 // Deregister in scripting api
1905 m_script->removeObjectReference(obj);
1908 if (obj->environmentDeletes())
1914 m_ao_manager.clear(clear_cb);
1917 static void print_hexdump(std::ostream &o, const std::string &data)
1919 const int linelength = 16;
1920 for (int l = 0;; l++) {
1921 int i0 = linelength * l;
1922 bool at_end = false;
1923 int thislinelength = linelength;
1924 if (i0 + thislinelength > (int)data.size()) {
1925 thislinelength = data.size() - i0;
1928 for (int di = 0; di < linelength; di++) {
1931 if (di < thislinelength)
1932 porting::mt_snprintf(buf, sizeof(buf), "%.2x ", data[i]);
1934 porting::mt_snprintf(buf, sizeof(buf), " ");
1938 for (int di = 0; di < thislinelength; di++) {
1951 ServerActiveObject* ServerEnvironment::createSAO(ActiveObjectType type, v3f pos,
1952 const std::string &data)
1955 case ACTIVEOBJECT_TYPE_LUAENTITY:
1956 return new LuaEntitySAO(this, pos, data);
1958 warningstream << "ServerActiveObject: No factory for type=" << type << std::endl;
1964 Convert stored objects from blocks near the players to active.
1966 void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s)
1971 if (!block->onObjectsActivation())
1974 // Activate stored objects
1975 std::vector<StaticObject> new_stored;
1976 for (const StaticObject &s_obj : block->m_static_objects.getAllStored()) {
1977 // Create an active object from the data
1978 ServerActiveObject *obj =
1979 createSAO((ActiveObjectType)s_obj.type, s_obj.pos, s_obj.data);
1980 // If couldn't create object, store static data back.
1982 errorstream << "ServerEnvironment::activateObjects(): "
1983 << "failed to create active object from static object "
1984 << "in block " << PP(s_obj.pos / BS)
1985 << " type=" << (int)s_obj.type << " data:" << std::endl;
1986 print_hexdump(verbosestream, s_obj.data);
1988 new_stored.push_back(s_obj);
1991 verbosestream << "ServerEnvironment::activateObjects(): "
1992 << "activated static object pos=" << PP(s_obj.pos / BS)
1993 << " type=" << (int)s_obj.type << std::endl;
1994 // This will also add the object to the active static list
1995 addActiveObjectRaw(obj, false, dtime_s);
1998 // Clear stored list
1999 block->m_static_objects.clearStored();
2000 // Add leftover failed stuff to stored list
2001 for (const StaticObject &s_obj : new_stored) {
2002 block->m_static_objects.pushStored(s_obj);
2006 Note: Block hasn't really been modified here.
2007 The objects have just been activated and moved from the stored
2008 static list to the active static list.
2009 As such, the block is essentially the same.
2010 Thus, do not call block->raiseModified(MOD_STATE_WRITE_NEEDED).
2011 Otherwise there would be a huge amount of unnecessary I/O.
2016 Convert objects that are not standing inside active blocks to static.
2018 If m_known_by_count != 0, active object is not deleted, but static
2019 data is still updated.
2021 If force_delete is set, active object is deleted nevertheless. It
2022 shall only be set so in the destructor of the environment.
2024 If block wasn't generated (not in memory or on disk),
2026 void ServerEnvironment::deactivateFarObjects(bool _force_delete)
2028 auto cb_deactivate = [this, _force_delete](ServerActiveObject *obj, u16 id) {
2029 // force_delete might be overridden per object
2030 bool force_delete = _force_delete;
2032 // Do not deactivate if disallowed
2033 if (!force_delete && !obj->shouldUnload())
2036 // removeRemovedObjects() is responsible for these
2037 if (!force_delete && obj->isGone())
2040 const v3f &objectpos = obj->getBasePosition();
2042 // The block in which the object resides in
2043 v3s16 blockpos_o = getNodeBlockPos(floatToInt(objectpos, BS));
2045 // If object's static data is stored in a deactivated block and object
2046 // is actually located in an active block, re-save to the block in
2047 // which the object is actually located in.
2048 if (!force_delete && obj->m_static_exists &&
2049 !m_active_blocks.contains(obj->m_static_block) &&
2050 m_active_blocks.contains(blockpos_o)) {
2052 // Delete from block where object was located
2053 deleteStaticFromBlock(obj, id, MOD_REASON_STATIC_DATA_REMOVED, false);
2055 StaticObject s_obj(obj, objectpos);
2056 // Save to block where object is located
2057 saveStaticToBlock(blockpos_o, id, obj, s_obj, MOD_REASON_STATIC_DATA_ADDED);
2062 // If block is still active, don't remove
2063 bool still_active = obj->isStaticAllowed() ?
2064 m_active_blocks.contains(blockpos_o) :
2065 getMap().getBlockNoCreateNoEx(blockpos_o) != nullptr;
2066 if (!force_delete && still_active)
2069 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
2070 << "deactivating object id=" << id << " on inactive block "
2071 << PP(blockpos_o) << std::endl;
2073 // If known by some client, don't immediately delete.
2074 bool pending_delete = (obj->m_known_by_count > 0 && !force_delete);
2077 Update the static data
2079 if (obj->isStaticAllowed()) {
2080 // Create new static object
2081 StaticObject s_obj(obj, objectpos);
2083 bool stays_in_same_block = false;
2084 bool data_changed = true;
2086 // Check if static data has changed considerably
2087 if (obj->m_static_exists) {
2088 if (obj->m_static_block == blockpos_o)
2089 stays_in_same_block = true;
2091 if (MapBlock *block = m_map->emergeBlock(obj->m_static_block, false)) {
2092 const auto n = block->m_static_objects.getAllActives().find(id);
2093 if (n != block->m_static_objects.getAllActives().end()) {
2094 StaticObject static_old = n->second;
2096 float save_movem = obj->getMinimumSavedMovement();
2098 if (static_old.data == s_obj.data &&
2099 (static_old.pos - objectpos).getLength() < save_movem)
2100 data_changed = false;
2102 warningstream << "ServerEnvironment::deactivateFarObjects(): "
2103 << "id=" << id << " m_static_exists=true but "
2104 << "static data doesn't actually exist in "
2105 << PP(obj->m_static_block) << std::endl;
2111 While changes are always saved, blocks are only marked as modified
2112 if the object has moved or different staticdata. (see above)
2114 bool shall_be_written = (!stays_in_same_block || data_changed);
2115 u32 reason = shall_be_written ? MOD_REASON_STATIC_DATA_CHANGED : MOD_REASON_UNKNOWN;
2117 // Delete old static object
2118 deleteStaticFromBlock(obj, id, reason, false);
2120 // Add to the block where the object is located in
2121 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
2122 u16 store_id = pending_delete ? id : 0;
2123 if (!saveStaticToBlock(blockpos, store_id, obj, s_obj, reason))
2124 force_delete = true;
2127 // Regardless of what happens to the object at this point, deactivate it first.
2128 // This ensures that LuaEntity on_deactivate is always called.
2129 obj->markForDeactivation();
2132 If known by some client, set pending deactivation.
2133 Otherwise delete it immediately.
2135 if (pending_delete && !force_delete) {
2136 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
2137 << "object id=" << id << " is known by clients"
2138 << "; not deleting yet" << std::endl;
2143 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
2144 << "object id=" << id << " is not known by clients"
2145 << "; deleting" << std::endl;
2147 // Tell the object about removal
2148 obj->removingFromEnvironment();
2149 // Deregister in scripting api
2150 m_script->removeObjectReference(obj);
2152 // Delete active object
2153 if (obj->environmentDeletes())
2159 m_ao_manager.clear(cb_deactivate);
2162 void ServerEnvironment::deleteStaticFromBlock(
2163 ServerActiveObject *obj, u16 id, u32 mod_reason, bool no_emerge)
2165 if (!obj->m_static_exists)
2170 block = m_map->getBlockNoCreateNoEx(obj->m_static_block);
2172 block = m_map->emergeBlock(obj->m_static_block, false);
2175 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2176 << " when deleting static data of object from it. id=" << id << std::endl;
2180 block->m_static_objects.remove(id);
2181 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2182 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2184 obj->m_static_exists = false;
2187 bool ServerEnvironment::saveStaticToBlock(
2188 v3s16 blockpos, u16 store_id,
2189 ServerActiveObject *obj, const StaticObject &s_obj,
2192 MapBlock *block = nullptr;
2194 block = m_map->emergeBlock(blockpos);
2195 } catch (InvalidPositionException &e) {
2196 // Handled via NULL pointer
2197 // NOTE: emergeBlock's failure is usually determined by it
2198 // actually returning NULL
2202 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2203 << " when saving static data of object to it. id=" << store_id << std::endl;
2207 if (!block->saveStaticObject(store_id, s_obj, mod_reason))
2210 obj->m_static_exists = true;
2211 obj->m_static_block = blockpos;
2216 PlayerDatabase *ServerEnvironment::openPlayerDatabase(const std::string &name,
2217 const std::string &savedir, const Settings &conf)
2220 if (name == "sqlite3")
2221 return new PlayerDatabaseSQLite3(savedir);
2223 if (name == "dummy")
2224 return new Database_Dummy();
2227 if (name == "postgresql") {
2228 std::string connect_string;
2229 conf.getNoEx("pgsql_player_connection", connect_string);
2230 return new PlayerDatabasePostgreSQL(connect_string);
2235 if (name == "leveldb")
2236 return new PlayerDatabaseLevelDB(savedir);
2239 if (name == "files")
2240 return new PlayerDatabaseFiles(savedir + DIR_DELIM + "players");
2242 throw BaseException(std::string("Database backend ") + name + " not supported.");
2245 bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params,
2246 const Settings &cmd_args)
2248 std::string migrate_to = cmd_args.get("migrate-players");
2250 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2251 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2252 errorstream << "Cannot read world.mt!" << std::endl;
2256 if (!world_mt.exists("player_backend")) {
2257 errorstream << "Please specify your current backend in world.mt:"
2259 << " player_backend = {files|sqlite3|leveldb|postgresql}"
2264 std::string backend = world_mt.get("player_backend");
2265 if (backend == migrate_to) {
2266 errorstream << "Cannot migrate: new backend is same"
2267 << " as the old one" << std::endl;
2271 const std::string players_backup_path = game_params.world_path + DIR_DELIM
2274 if (backend == "files") {
2275 // Create backup directory
2276 fs::CreateDir(players_backup_path);
2280 PlayerDatabase *srcdb = ServerEnvironment::openPlayerDatabase(backend,
2281 game_params.world_path, world_mt);
2282 PlayerDatabase *dstdb = ServerEnvironment::openPlayerDatabase(migrate_to,
2283 game_params.world_path, world_mt);
2285 std::vector<std::string> player_list;
2286 srcdb->listPlayers(player_list);
2287 for (std::vector<std::string>::const_iterator it = player_list.begin();
2288 it != player_list.end(); ++it) {
2289 actionstream << "Migrating player " << it->c_str() << std::endl;
2290 RemotePlayer player(it->c_str(), NULL);
2291 PlayerSAO playerSAO(NULL, &player, 15000, false);
2293 srcdb->loadPlayer(&player, &playerSAO);
2295 playerSAO.finalize(&player, std::set<std::string>());
2296 player.setPlayerSAO(&playerSAO);
2298 dstdb->savePlayer(&player);
2300 // For files source, move player files to backup dir
2301 if (backend == "files") {
2303 game_params.world_path + DIR_DELIM + "players" + DIR_DELIM + (*it),
2304 players_backup_path + DIR_DELIM + (*it));
2308 actionstream << "Successfully migrated " << player_list.size() << " players"
2310 world_mt.set("player_backend", migrate_to);
2311 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2312 errorstream << "Failed to update world.mt!" << std::endl;
2314 actionstream << "world.mt updated" << std::endl;
2316 // When migration is finished from file backend, remove players directory if empty
2317 if (backend == "files") {
2318 fs::DeleteSingleFileOrEmptyDirectory(game_params.world_path + DIR_DELIM
2325 } catch (BaseException &e) {
2326 errorstream << "An error occurred during migration: " << e.what() << std::endl;
2332 AuthDatabase *ServerEnvironment::openAuthDatabase(
2333 const std::string &name, const std::string &savedir, const Settings &conf)
2335 if (name == "sqlite3")
2336 return new AuthDatabaseSQLite3(savedir);
2339 if (name == "postgresql") {
2340 std::string connect_string;
2341 conf.getNoEx("pgsql_auth_connection", connect_string);
2342 return new AuthDatabasePostgreSQL(connect_string);
2346 if (name == "files")
2347 return new AuthDatabaseFiles(savedir);
2350 if (name == "leveldb")
2351 return new AuthDatabaseLevelDB(savedir);
2354 throw BaseException(std::string("Database backend ") + name + " not supported.");
2357 bool ServerEnvironment::migrateAuthDatabase(
2358 const GameParams &game_params, const Settings &cmd_args)
2360 std::string migrate_to = cmd_args.get("migrate-auth");
2362 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2363 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2364 errorstream << "Cannot read world.mt!" << std::endl;
2368 std::string backend = "files";
2369 if (world_mt.exists("auth_backend"))
2370 backend = world_mt.get("auth_backend");
2372 warningstream << "No auth_backend found in world.mt, "
2373 "assuming \"files\"." << std::endl;
2375 if (backend == migrate_to) {
2376 errorstream << "Cannot migrate: new backend is same"
2377 << " as the old one" << std::endl;
2382 const std::unique_ptr<AuthDatabase> srcdb(ServerEnvironment::openAuthDatabase(
2383 backend, game_params.world_path, world_mt));
2384 const std::unique_ptr<AuthDatabase> dstdb(ServerEnvironment::openAuthDatabase(
2385 migrate_to, game_params.world_path, world_mt));
2387 std::vector<std::string> names_list;
2388 srcdb->listNames(names_list);
2389 for (const std::string &name : names_list) {
2390 actionstream << "Migrating auth entry for " << name << std::endl;
2392 AuthEntry authEntry;
2393 success = srcdb->getAuth(name, authEntry);
2394 success = success && dstdb->createAuth(authEntry);
2396 errorstream << "Failed to migrate " << name << std::endl;
2399 actionstream << "Successfully migrated " << names_list.size()
2400 << " auth entries" << std::endl;
2401 world_mt.set("auth_backend", migrate_to);
2402 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2403 errorstream << "Failed to update world.mt!" << std::endl;
2405 actionstream << "world.mt updated" << std::endl;
2407 if (backend == "files") {
2408 // special-case files migration:
2409 // move auth.txt to auth.txt.bak if possible
2410 std::string auth_txt_path =
2411 game_params.world_path + DIR_DELIM + "auth.txt";
2412 std::string auth_bak_path = auth_txt_path + ".bak";
2413 if (!fs::PathExists(auth_bak_path))
2414 if (fs::Rename(auth_txt_path, auth_bak_path))
2415 actionstream << "Renamed auth.txt to auth.txt.bak"
2418 errorstream << "Could not rename auth.txt to "
2419 "auth.txt.bak" << std::endl;
2421 warningstream << "auth.txt.bak already exists, auth.txt "
2422 "not renamed" << std::endl;
2425 } catch (BaseException &e) {
2426 errorstream << "An error occurred during migration: " << e.what()