3 Copyright (C) 2010-2017 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "serverenvironment.h"
26 #include "nodemetadata.h"
32 #include "remoteplayer.h"
33 #include "scripting_server.h"
35 #include "util/serialize.h"
36 #include "util/basic_macros.h"
37 #include "util/pointedthing.h"
38 #include "threading/mutex_auto_lock.h"
40 #include "gameparams.h"
41 #include "database/database-dummy.h"
42 #include "database/database-files.h"
43 #include "database/database-sqlite3.h"
45 #include "database/database-postgresql.h"
47 #include "server/luaentity_sao.h"
48 #include "server/player_sao.h"
50 #define LBM_NAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_:"
52 // A number that is much smaller than the timeout for particle spawners should/could ever be
53 #define PARTICLE_SPAWNER_NO_EXPIRY -1024.f
59 ABMWithState::ABMWithState(ActiveBlockModifier *abm_):
62 // Initialize timer to random value to spread processing
63 float itv = abm->getTriggerInterval();
64 itv = MYMAX(0.001, itv); // No less than 1ms
65 int minval = MYMAX(-0.51*itv, -60); // Clamp to
66 int maxval = MYMIN(0.51*itv, 60); // +-60 seconds
67 timer = myrand_range(minval, maxval);
74 void LBMContentMapping::deleteContents()
76 for (auto &it : lbm_list) {
81 void LBMContentMapping::addLBM(LoadingBlockModifierDef *lbm_def, IGameDef *gamedef)
83 // Add the lbm_def to the LBMContentMapping.
84 // Unknown names get added to the global NameIdMapping.
85 const NodeDefManager *nodedef = gamedef->ndef();
87 lbm_list.push_back(lbm_def);
89 for (const std::string &nodeTrigger: lbm_def->trigger_contents) {
90 std::vector<content_t> c_ids;
91 bool found = nodedef->getIds(nodeTrigger, c_ids);
93 content_t c_id = gamedef->allocateUnknownNodeId(nodeTrigger);
94 if (c_id == CONTENT_IGNORE) {
95 // Seems it can't be allocated.
96 warningstream << "Could not internalize node name \"" << nodeTrigger
97 << "\" while loading LBM \"" << lbm_def->name << "\"." << std::endl;
100 c_ids.push_back(c_id);
103 for (content_t c_id : c_ids) {
104 map[c_id].push_back(lbm_def);
109 const std::vector<LoadingBlockModifierDef *> *
110 LBMContentMapping::lookup(content_t c) const
112 lbm_map::const_iterator it = map.find(c);
115 // This first dereferences the iterator, returning
116 // a std::vector<LoadingBlockModifierDef *>
117 // reference, then we convert it to a pointer.
118 return &(it->second);
121 LBMManager::~LBMManager()
123 for (auto &m_lbm_def : m_lbm_defs) {
124 delete m_lbm_def.second;
127 for (auto &it : m_lbm_lookup) {
128 (it.second).deleteContents();
132 void LBMManager::addLBMDef(LoadingBlockModifierDef *lbm_def)
134 // Precondition, in query mode the map isn't used anymore
135 FATAL_ERROR_IF(m_query_mode,
136 "attempted to modify LBMManager in query mode");
138 if (!string_allowed(lbm_def->name, LBM_NAME_ALLOWED_CHARS)) {
139 throw ModError("Error adding LBM \"" + lbm_def->name +
140 "\": Does not follow naming conventions: "
141 "Only characters [a-z0-9_:] are allowed.");
144 m_lbm_defs[lbm_def->name] = lbm_def;
147 void LBMManager::loadIntroductionTimes(const std::string ×,
148 IGameDef *gamedef, u32 now)
153 // Storing it in a map first instead of
154 // handling the stuff directly in the loop
155 // removes all duplicate entries.
156 // TODO make this std::unordered_map
157 std::map<std::string, u32> introduction_times;
160 The introduction times string consists of name~time entries,
161 with each entry terminated by a semicolon. The time is decimal.
166 while ((idx_new = times.find(';', idx)) != std::string::npos) {
167 std::string entry = times.substr(idx, idx_new - idx);
168 std::vector<std::string> components = str_split(entry, '~');
169 if (components.size() != 2)
170 throw SerializationError("Introduction times entry \""
171 + entry + "\" requires exactly one '~'!");
172 const std::string &name = components[0];
173 u32 time = from_string<u32>(components[1]);
174 introduction_times[name] = time;
178 // Put stuff from introduction_times into m_lbm_lookup
179 for (std::map<std::string, u32>::const_iterator it = introduction_times.begin();
180 it != introduction_times.end(); ++it) {
181 const std::string &name = it->first;
182 u32 time = it->second;
184 std::map<std::string, LoadingBlockModifierDef *>::iterator def_it =
185 m_lbm_defs.find(name);
186 if (def_it == m_lbm_defs.end()) {
187 // This seems to be an LBM entry for
188 // an LBM we haven't loaded. Discard it.
191 LoadingBlockModifierDef *lbm_def = def_it->second;
192 if (lbm_def->run_at_every_load) {
193 // This seems to be an LBM entry for
194 // an LBM that runs at every load.
195 // Don't add it just yet.
199 m_lbm_lookup[time].addLBM(lbm_def, gamedef);
201 // Erase the entry so that we know later
202 // what elements didn't get put into m_lbm_lookup
203 m_lbm_defs.erase(name);
206 // Now also add the elements from m_lbm_defs to m_lbm_lookup
207 // that weren't added in the previous step.
208 // They are introduced first time to this world,
209 // or are run at every load (introducement time hardcoded to U32_MAX).
211 LBMContentMapping &lbms_we_introduce_now = m_lbm_lookup[now];
212 LBMContentMapping &lbms_running_always = m_lbm_lookup[U32_MAX];
214 for (auto &m_lbm_def : m_lbm_defs) {
215 if (m_lbm_def.second->run_at_every_load) {
216 lbms_running_always.addLBM(m_lbm_def.second, gamedef);
218 lbms_we_introduce_now.addLBM(m_lbm_def.second, gamedef);
222 // Clear the list, so that we don't delete remaining elements
223 // twice in the destructor
227 std::string LBMManager::createIntroductionTimesString()
229 // Precondition, we must be in query mode
230 FATAL_ERROR_IF(!m_query_mode,
231 "attempted to query on non fully set up LBMManager");
233 std::ostringstream oss;
234 for (const auto &it : m_lbm_lookup) {
236 const std::vector<LoadingBlockModifierDef *> &lbm_list = it.second.lbm_list;
237 for (const auto &lbm_def : lbm_list) {
238 // Don't add if the LBM runs at every load,
239 // then introducement time is hardcoded
240 // and doesn't need to be stored
241 if (lbm_def->run_at_every_load)
243 oss << lbm_def->name << "~" << time << ";";
249 void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, u32 stamp)
251 // Precondition, we need m_lbm_lookup to be initialized
252 FATAL_ERROR_IF(!m_query_mode,
253 "attempted to query on non fully set up LBMManager");
254 v3s16 pos_of_block = block->getPosRelative();
258 lbm_lookup_map::const_iterator it = getLBMsIntroducedAfter(stamp);
259 for (; it != m_lbm_lookup.end(); ++it) {
260 // Cache previous version to speedup lookup which has a very high performance
261 // penalty on each call
262 content_t previous_c{};
263 std::vector<LoadingBlockModifierDef *> *lbm_list = nullptr;
265 for (pos.X = 0; pos.X < MAP_BLOCKSIZE; pos.X++)
266 for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++)
267 for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++) {
268 n = block->getNodeNoEx(pos);
271 // If content_t are not matching perform an LBM lookup
272 if (previous_c != c) {
273 lbm_list = (std::vector<LoadingBlockModifierDef *> *)
274 it->second.lookup(c);
280 for (auto lbmdef : *lbm_list) {
281 lbmdef->trigger(env, pos + pos_of_block, n);
291 void fillRadiusBlock(v3s16 p0, s16 r, std::set<v3s16> &list)
294 for(p.X=p0.X-r; p.X<=p0.X+r; p.X++)
295 for(p.Y=p0.Y-r; p.Y<=p0.Y+r; p.Y++)
296 for(p.Z=p0.Z-r; p.Z<=p0.Z+r; p.Z++)
299 if (p.getDistanceFrom(p0) <= r) {
306 void fillViewConeBlock(v3s16 p0,
308 const v3f camera_pos,
309 const v3f camera_dir,
310 const float camera_fov,
311 std::set<v3s16> &list)
314 const s16 r_nodes = r * BS * MAP_BLOCKSIZE;
315 for (p.X = p0.X - r; p.X <= p0.X+r; p.X++)
316 for (p.Y = p0.Y - r; p.Y <= p0.Y+r; p.Y++)
317 for (p.Z = p0.Z - r; p.Z <= p0.Z+r; p.Z++) {
318 if (isBlockInSight(p, camera_pos, camera_dir, camera_fov, r_nodes)) {
324 void ActiveBlockList::update(std::vector<PlayerSAO*> &active_players,
325 s16 active_block_range,
326 s16 active_object_range,
327 std::set<v3s16> &blocks_removed,
328 std::set<v3s16> &blocks_added)
333 std::set<v3s16> newlist = m_forceloaded_list;
334 m_abm_list = m_forceloaded_list;
335 for (const PlayerSAO *playersao : active_players) {
336 v3s16 pos = getNodeBlockPos(floatToInt(playersao->getBasePosition(), BS));
337 fillRadiusBlock(pos, active_block_range, m_abm_list);
338 fillRadiusBlock(pos, active_block_range, newlist);
340 s16 player_ao_range = std::min(active_object_range, playersao->getWantedRange());
341 // only do this if this would add blocks
342 if (player_ao_range > active_block_range) {
343 v3f camera_dir = v3f(0,0,1);
344 camera_dir.rotateYZBy(playersao->getLookPitch());
345 camera_dir.rotateXZBy(playersao->getRotation().Y);
346 fillViewConeBlock(pos,
348 playersao->getEyePosition(),
356 Find out which blocks on the old list are not on the new list
358 // Go through old list
359 for (v3s16 p : m_list) {
360 // If not on new list, it's been removed
361 if (newlist.find(p) == newlist.end())
362 blocks_removed.insert(p);
366 Find out which blocks on the new list are not on the old list
368 // Go through new list
369 for (v3s16 p : newlist) {
370 // If not on old list, it's been added
371 if(m_list.find(p) == m_list.end())
372 blocks_added.insert(p);
379 for (v3s16 p : newlist) {
388 // Random device to seed pseudo random generators.
389 static std::random_device seed;
391 ServerEnvironment::ServerEnvironment(ServerMap *map,
392 ServerScripting *scriptIface, Server *server,
393 const std::string &path_world):
396 m_script(scriptIface),
398 m_path_world(path_world),
401 // Determine which database backend to use
402 std::string conf_path = path_world + DIR_DELIM + "world.mt";
405 std::string player_backend_name = "sqlite3";
406 std::string auth_backend_name = "sqlite3";
408 bool succeeded = conf.readConfigFile(conf_path.c_str());
410 // If we open world.mt read the backend configurations.
412 // Read those values before setting defaults
413 bool player_backend_exists = conf.exists("player_backend");
414 bool auth_backend_exists = conf.exists("auth_backend");
416 // player backend is not set, assume it's legacy file backend.
417 if (!player_backend_exists) {
418 // fall back to files
419 conf.set("player_backend", "files");
420 player_backend_name = "files";
422 if (!conf.updateConfigFile(conf_path.c_str())) {
423 errorstream << "ServerEnvironment::ServerEnvironment(): "
424 << "Failed to update world.mt!" << std::endl;
427 conf.getNoEx("player_backend", player_backend_name);
430 // auth backend is not set, assume it's legacy file backend.
431 if (!auth_backend_exists) {
432 conf.set("auth_backend", "files");
433 auth_backend_name = "files";
435 if (!conf.updateConfigFile(conf_path.c_str())) {
436 errorstream << "ServerEnvironment::ServerEnvironment(): "
437 << "Failed to update world.mt!" << std::endl;
440 conf.getNoEx("auth_backend", auth_backend_name);
444 if (player_backend_name == "files") {
445 warningstream << "/!\\ You are using old player file backend. "
446 << "This backend is deprecated and will be removed in a future release /!\\"
447 << std::endl << "Switching to SQLite3 or PostgreSQL is advised, "
448 << "please read http://wiki.minetest.net/Database_backends." << std::endl;
451 if (auth_backend_name == "files") {
452 warningstream << "/!\\ You are using old auth file backend. "
453 << "This backend is deprecated and will be removed in a future release /!\\"
454 << std::endl << "Switching to SQLite3 is advised, "
455 << "please read http://wiki.minetest.net/Database_backends." << std::endl;
458 m_player_database = openPlayerDatabase(player_backend_name, path_world, conf);
459 m_auth_database = openAuthDatabase(auth_backend_name, path_world, conf);
462 ServerEnvironment::~ServerEnvironment()
464 // Clear active block list.
465 // This makes the next one delete all active objects.
466 m_active_blocks.clear();
468 // Convert all objects to static and delete the active objects
469 deactivateFarObjects(true);
474 // Delete ActiveBlockModifiers
475 for (ABMWithState &m_abm : m_abms) {
479 // Deallocate players
480 for (RemotePlayer *m_player : m_players) {
484 delete m_player_database;
485 delete m_auth_database;
488 Map & ServerEnvironment::getMap()
493 ServerMap & ServerEnvironment::getServerMap()
498 RemotePlayer *ServerEnvironment::getPlayer(const session_t peer_id)
500 for (RemotePlayer *player : m_players) {
501 if (player->getPeerId() == peer_id)
507 RemotePlayer *ServerEnvironment::getPlayer(const char* name)
509 for (RemotePlayer *player : m_players) {
510 if (strcmp(player->getName(), name) == 0)
516 void ServerEnvironment::addPlayer(RemotePlayer *player)
519 Check that peer_ids are unique.
520 Also check that names are unique.
521 Exception: there can be multiple players with peer_id=0
523 // If peer id is non-zero, it has to be unique.
524 if (player->getPeerId() != PEER_ID_INEXISTENT)
525 FATAL_ERROR_IF(getPlayer(player->getPeerId()) != NULL, "Peer id not unique");
526 // Name has to be unique.
527 FATAL_ERROR_IF(getPlayer(player->getName()) != NULL, "Player name not unique");
529 m_players.push_back(player);
532 void ServerEnvironment::removePlayer(RemotePlayer *player)
534 for (std::vector<RemotePlayer *>::iterator it = m_players.begin();
535 it != m_players.end(); ++it) {
536 if ((*it) == player) {
544 bool ServerEnvironment::removePlayerFromDatabase(const std::string &name)
546 return m_player_database->removePlayer(name);
549 void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason,
550 const std::string &str_reason, bool reconnect)
552 for (RemotePlayer *player : m_players) {
553 m_server->DenyAccessVerCompliant(player->getPeerId(),
554 player->protocol_version, reason, str_reason, reconnect);
558 void ServerEnvironment::saveLoadedPlayers(bool force)
560 for (RemotePlayer *player : m_players) {
561 if (force || player->checkModified() || (player->getPlayerSAO() &&
562 player->getPlayerSAO()->getMeta().isModified())) {
564 m_player_database->savePlayer(player);
565 } catch (DatabaseException &e) {
566 errorstream << "Failed to save player " << player->getName() << " exception: "
567 << e.what() << std::endl;
574 void ServerEnvironment::savePlayer(RemotePlayer *player)
577 m_player_database->savePlayer(player);
578 } catch (DatabaseException &e) {
579 errorstream << "Failed to save player " << player->getName() << " exception: "
580 << e.what() << std::endl;
585 PlayerSAO *ServerEnvironment::loadPlayer(RemotePlayer *player, bool *new_player,
586 session_t peer_id, bool is_singleplayer)
588 PlayerSAO *playersao = new PlayerSAO(this, player, peer_id, is_singleplayer);
589 // Create player if it doesn't exist
590 if (!m_player_database->loadPlayer(player, playersao)) {
592 // Set player position
593 infostream << "Server: Finding spawn place for player \""
594 << player->getName() << "\"" << std::endl;
595 playersao->setBasePosition(m_server->findSpawnPos());
597 // Make sure the player is saved
598 player->setModified(true);
600 // If the player exists, ensure that they respawn inside legal bounds
601 // This fixes an assert crash when the player can't be added
602 // to the environment
603 if (objectpos_over_limit(playersao->getBasePosition())) {
604 actionstream << "Respawn position for player \""
605 << player->getName() << "\" outside limits, resetting" << std::endl;
606 playersao->setBasePosition(m_server->findSpawnPos());
610 // Add player to environment
613 /* Clean up old HUD elements from previous sessions */
616 /* Add object to environment */
617 addActiveObject(playersao);
622 void ServerEnvironment::saveMeta()
624 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
626 // Open file and serialize
627 std::ostringstream ss(std::ios_base::binary);
630 args.setU64("game_time", m_game_time);
631 args.setU64("time_of_day", getTimeOfDay());
632 args.setU64("last_clear_objects_time", m_last_clear_objects_time);
633 args.setU64("lbm_introduction_times_version", 1);
634 args.set("lbm_introduction_times",
635 m_lbm_mgr.createIntroductionTimesString());
636 args.setU64("day_count", m_day_count);
640 if(!fs::safeWriteToFile(path, ss.str()))
642 infostream<<"ServerEnvironment::saveMeta(): Failed to write "
644 throw SerializationError("Couldn't save env meta");
648 void ServerEnvironment::loadMeta()
650 // If file doesn't exist, load default environment metadata
651 if (!fs::PathExists(m_path_world + DIR_DELIM "env_meta.txt")) {
652 infostream << "ServerEnvironment: Loading default environment metadata"
658 infostream << "ServerEnvironment: Loading environment metadata" << std::endl;
660 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
662 // Open file and deserialize
663 std::ifstream is(path.c_str(), std::ios_base::binary);
665 infostream << "ServerEnvironment::loadMeta(): Failed to open "
666 << path << std::endl;
667 throw SerializationError("Couldn't load env meta");
672 if (!args.parseConfigLines(is, "EnvArgsEnd")) {
673 throw SerializationError("ServerEnvironment::loadMeta(): "
674 "EnvArgsEnd not found!");
678 m_game_time = args.getU64("game_time");
679 } catch (SettingNotFoundException &e) {
680 // Getting this is crucial, otherwise timestamps are useless
681 throw SerializationError("Couldn't load env meta game_time");
684 setTimeOfDay(args.exists("time_of_day") ?
685 // set day to early morning by default
686 args.getU64("time_of_day") : 5250);
688 m_last_clear_objects_time = args.exists("last_clear_objects_time") ?
689 // If missing, do as if clearObjects was never called
690 args.getU64("last_clear_objects_time") : 0;
692 std::string lbm_introduction_times;
694 u64 ver = args.getU64("lbm_introduction_times_version");
696 lbm_introduction_times = args.get("lbm_introduction_times");
698 infostream << "ServerEnvironment::loadMeta(): Non-supported"
699 << " introduction time version " << ver << std::endl;
701 } catch (SettingNotFoundException &e) {
702 // No problem, this is expected. Just continue with an empty string
704 m_lbm_mgr.loadIntroductionTimes(lbm_introduction_times, m_server, m_game_time);
706 m_day_count = args.exists("day_count") ?
707 args.getU64("day_count") : 0;
711 * called if env_meta.txt doesn't exist (e.g. new world)
713 void ServerEnvironment::loadDefaultMeta()
715 m_lbm_mgr.loadIntroductionTimes("", m_server, m_game_time);
720 ActiveBlockModifier *abm;
722 std::vector<content_t> required_neighbors;
723 bool check_required_neighbors; // false if required_neighbors is known to be empty
729 ServerEnvironment *m_env;
730 std::vector<std::vector<ActiveABM> *> m_aabms;
732 ABMHandler(std::vector<ABMWithState> &abms,
733 float dtime_s, ServerEnvironment *env,
739 const NodeDefManager *ndef = env->getGameDef()->ndef();
740 for (ABMWithState &abmws : abms) {
741 ActiveBlockModifier *abm = abmws.abm;
742 float trigger_interval = abm->getTriggerInterval();
743 if(trigger_interval < 0.001)
744 trigger_interval = 0.001;
745 float actual_interval = dtime_s;
747 abmws.timer += dtime_s;
748 if(abmws.timer < trigger_interval)
750 abmws.timer -= trigger_interval;
751 actual_interval = trigger_interval;
753 float chance = abm->getTriggerChance();
758 if (abm->getSimpleCatchUp()) {
759 float intervals = actual_interval / trigger_interval;
762 aabm.chance = chance / intervals;
766 aabm.chance = chance;
770 const std::vector<std::string> &required_neighbors_s =
771 abm->getRequiredNeighbors();
772 for (const std::string &required_neighbor_s : required_neighbors_s) {
773 ndef->getIds(required_neighbor_s, aabm.required_neighbors);
775 aabm.check_required_neighbors = !required_neighbors_s.empty();
778 const std::vector<std::string> &contents_s = abm->getTriggerContents();
779 for (const std::string &content_s : contents_s) {
780 std::vector<content_t> ids;
781 ndef->getIds(content_s, ids);
782 for (content_t c : ids) {
783 if (c >= m_aabms.size())
784 m_aabms.resize(c + 256, NULL);
786 m_aabms[c] = new std::vector<ActiveABM>;
787 m_aabms[c]->push_back(aabm);
795 for (auto &aabms : m_aabms)
799 // Find out how many objects the given block and its neighbours contain.
800 // Returns the number of objects in the block, and also in 'wider' the
801 // number of objects in the block and all its neighbours. The latter
802 // may an estimate if any neighbours are unloaded.
803 u32 countObjects(MapBlock *block, ServerMap * map, u32 &wider)
806 u32 wider_unknown_count = 0;
807 for(s16 x=-1; x<=1; x++)
808 for(s16 y=-1; y<=1; y++)
809 for(s16 z=-1; z<=1; z++)
811 MapBlock *block2 = map->getBlockNoCreateNoEx(
812 block->getPos() + v3s16(x,y,z));
814 wider_unknown_count++;
817 wider += block2->m_static_objects.m_active.size()
818 + block2->m_static_objects.m_stored.size();
821 u32 active_object_count = block->m_static_objects.m_active.size();
822 u32 wider_known_count = 3*3*3 - wider_unknown_count;
823 wider += wider_unknown_count * wider / wider_known_count;
824 return active_object_count;
827 void apply(MapBlock *block, int &blocks_scanned, int &abms_run, int &blocks_cached)
829 if(m_aabms.empty() || block->isDummy())
832 // Check the content type cache first
833 // to see whether there are any ABMs
834 // to be run at all for this block.
835 if (block->contents_cached) {
837 bool run_abms = false;
838 for (content_t c : block->contents) {
839 if (c < m_aabms.size() && m_aabms[c]) {
848 block->contents.clear();
852 ServerMap *map = &m_env->getServerMap();
854 u32 active_object_count_wider;
855 u32 active_object_count = this->countObjects(block, map, active_object_count_wider);
856 m_env->m_added_objects = 0;
859 for(p0.X=0; p0.X<MAP_BLOCKSIZE; p0.X++)
860 for(p0.Y=0; p0.Y<MAP_BLOCKSIZE; p0.Y++)
861 for(p0.Z=0; p0.Z<MAP_BLOCKSIZE; p0.Z++)
863 const MapNode &n = block->getNodeUnsafe(p0);
864 content_t c = n.getContent();
865 // Cache content types as we go
866 if (!block->contents_cached && !block->do_not_cache_contents) {
867 block->contents.insert(c);
868 if (block->contents.size() > 64) {
869 // Too many different nodes... don't try to cache
870 block->do_not_cache_contents = true;
871 block->contents.clear();
875 if (c >= m_aabms.size() || !m_aabms[c])
878 v3s16 p = p0 + block->getPosRelative();
879 for (ActiveABM &aabm : *m_aabms[c]) {
880 if (myrand() % aabm.chance != 0)
884 if (aabm.check_required_neighbors) {
886 for(p1.X = p0.X-1; p1.X <= p0.X+1; p1.X++)
887 for(p1.Y = p0.Y-1; p1.Y <= p0.Y+1; p1.Y++)
888 for(p1.Z = p0.Z-1; p1.Z <= p0.Z+1; p1.Z++)
893 if (block->isValidPosition(p1)) {
894 // if the neighbor is found on the same map block
895 // get it straight from there
896 const MapNode &n = block->getNodeUnsafe(p1);
899 // otherwise consult the map
900 MapNode n = map->getNode(p1 + block->getPosRelative());
903 if (CONTAINS(aabm.required_neighbors, c))
906 // No required neighbor found
912 // Call all the trigger variations
913 aabm.abm->trigger(m_env, p, n);
914 aabm.abm->trigger(m_env, p, n,
915 active_object_count, active_object_count_wider);
917 // Count surrounding objects again if the abms added any
918 if(m_env->m_added_objects > 0) {
919 active_object_count = countObjects(block, map, active_object_count_wider);
920 m_env->m_added_objects = 0;
924 block->contents_cached = !block->do_not_cache_contents;
928 void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime)
930 // Reset usage timer immediately, otherwise a block that becomes active
931 // again at around the same time as it would normally be unloaded will
932 // get unloaded incorrectly. (I think this still leaves a small possibility
933 // of a race condition between this and server::AsyncRunStep, which only
934 // some kind of synchronisation will fix, but it at least reduces the window
935 // of opportunity for it to break from seconds to nanoseconds)
936 block->resetUsageTimer();
938 // Get time difference
940 u32 stamp = block->getTimestamp();
941 if (m_game_time > stamp && stamp != BLOCK_TIMESTAMP_UNDEFINED)
942 dtime_s = m_game_time - stamp;
943 dtime_s += additional_dtime;
945 /*infostream<<"ServerEnvironment::activateBlock(): block timestamp: "
946 <<stamp<<", game time: "<<m_game_time<<std::endl;*/
948 // Remove stored static objects if clearObjects was called since block's timestamp
949 if (stamp == BLOCK_TIMESTAMP_UNDEFINED || stamp < m_last_clear_objects_time) {
950 block->m_static_objects.m_stored.clear();
951 // do not set changed flag to avoid unnecessary mapblock writes
954 // Set current time as timestamp
955 block->setTimestampNoChangedFlag(m_game_time);
957 /*infostream<<"ServerEnvironment::activateBlock(): block is "
958 <<dtime_s<<" seconds old."<<std::endl;*/
960 // Activate stored objects
961 activateObjects(block, dtime_s);
963 /* Handle LoadingBlockModifiers */
964 m_lbm_mgr.applyLBMs(this, block, stamp);
967 std::vector<NodeTimer> elapsed_timers =
968 block->m_node_timers.step((float)dtime_s);
969 if (!elapsed_timers.empty()) {
971 for (const NodeTimer &elapsed_timer : elapsed_timers) {
972 n = block->getNodeNoEx(elapsed_timer.position);
973 v3s16 p = elapsed_timer.position + block->getPosRelative();
974 if (m_script->node_on_timer(p, n, elapsed_timer.elapsed))
975 block->setNodeTimer(NodeTimer(elapsed_timer.timeout, 0,
976 elapsed_timer.position));
981 void ServerEnvironment::addActiveBlockModifier(ActiveBlockModifier *abm)
983 m_abms.emplace_back(abm);
986 void ServerEnvironment::addLoadingBlockModifierDef(LoadingBlockModifierDef *lbm)
988 m_lbm_mgr.addLBMDef(lbm);
991 bool ServerEnvironment::setNode(v3s16 p, const MapNode &n)
993 const NodeDefManager *ndef = m_server->ndef();
994 MapNode n_old = m_map->getNode(p);
996 const ContentFeatures &cf_old = ndef->get(n_old);
999 if (cf_old.has_on_destruct)
1000 m_script->node_on_destruct(p, n_old);
1003 if (!m_map->addNodeWithEvent(p, n))
1006 // Update active VoxelManipulator if a mapgen thread
1007 m_map->updateVManip(p);
1009 // Call post-destructor
1010 if (cf_old.has_after_destruct)
1011 m_script->node_after_destruct(p, n_old);
1013 // Retrieve node content features
1014 // if new node is same as old, reuse old definition to prevent a lookup
1015 const ContentFeatures &cf_new = n_old == n ? cf_old : ndef->get(n);
1018 if (cf_new.has_on_construct)
1019 m_script->node_on_construct(p, n);
1024 bool ServerEnvironment::removeNode(v3s16 p)
1026 const NodeDefManager *ndef = m_server->ndef();
1027 MapNode n_old = m_map->getNode(p);
1030 if (ndef->get(n_old).has_on_destruct)
1031 m_script->node_on_destruct(p, n_old);
1034 // This is slightly optimized compared to addNodeWithEvent(air)
1035 if (!m_map->removeNodeWithEvent(p))
1038 // Update active VoxelManipulator if a mapgen thread
1039 m_map->updateVManip(p);
1041 // Call post-destructor
1042 if (ndef->get(n_old).has_after_destruct)
1043 m_script->node_after_destruct(p, n_old);
1045 // Air doesn't require constructor
1049 bool ServerEnvironment::swapNode(v3s16 p, const MapNode &n)
1051 if (!m_map->addNodeWithEvent(p, n, false))
1054 // Update active VoxelManipulator if a mapgen thread
1055 m_map->updateVManip(p);
1060 void ServerEnvironment::clearObjects(ClearObjectsMode mode)
1062 infostream << "ServerEnvironment::clearObjects(): "
1063 << "Removing all active objects" << std::endl;
1064 auto cb_removal = [this] (ServerActiveObject *obj, u16 id) {
1065 if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER)
1068 // Delete static object if block is loaded
1069 deleteStaticFromBlock(obj, id, MOD_REASON_CLEAR_ALL_OBJECTS, true);
1071 // If known by some client, don't delete immediately
1072 if (obj->m_known_by_count > 0) {
1073 obj->m_pending_removal = true;
1077 // Tell the object about removal
1078 obj->removingFromEnvironment();
1079 // Deregister in scripting api
1080 m_script->removeObjectReference(obj);
1082 // Delete active object
1083 if (obj->environmentDeletes())
1089 m_ao_manager.clear(cb_removal);
1091 // Get list of loaded blocks
1092 std::vector<v3s16> loaded_blocks;
1093 infostream << "ServerEnvironment::clearObjects(): "
1094 << "Listing all loaded blocks" << std::endl;
1095 m_map->listAllLoadedBlocks(loaded_blocks);
1096 infostream << "ServerEnvironment::clearObjects(): "
1097 << "Done listing all loaded blocks: "
1098 << loaded_blocks.size()<<std::endl;
1100 // Get list of loadable blocks
1101 std::vector<v3s16> loadable_blocks;
1102 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1103 infostream << "ServerEnvironment::clearObjects(): "
1104 << "Listing all loadable blocks" << std::endl;
1105 m_map->listAllLoadableBlocks(loadable_blocks);
1106 infostream << "ServerEnvironment::clearObjects(): "
1107 << "Done listing all loadable blocks: "
1108 << loadable_blocks.size() << std::endl;
1110 loadable_blocks = loaded_blocks;
1113 actionstream << "ServerEnvironment::clearObjects(): "
1114 << "Now clearing objects in " << loadable_blocks.size()
1115 << " blocks" << std::endl;
1117 // Grab a reference on each loaded block to avoid unloading it
1118 for (v3s16 p : loaded_blocks) {
1119 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1120 assert(block != NULL);
1124 // Remove objects in all loadable blocks
1125 u32 unload_interval = U32_MAX;
1126 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1127 unload_interval = g_settings->getS32("max_clearobjects_extra_loaded_blocks");
1128 unload_interval = MYMAX(unload_interval, 1);
1130 u32 report_interval = loadable_blocks.size() / 10;
1131 u32 num_blocks_checked = 0;
1132 u32 num_blocks_cleared = 0;
1133 u32 num_objs_cleared = 0;
1134 for (auto i = loadable_blocks.begin();
1135 i != loadable_blocks.end(); ++i) {
1137 MapBlock *block = m_map->emergeBlock(p, false);
1139 errorstream << "ServerEnvironment::clearObjects(): "
1140 << "Failed to emerge block " << PP(p) << std::endl;
1143 u32 num_stored = block->m_static_objects.m_stored.size();
1144 u32 num_active = block->m_static_objects.m_active.size();
1145 if (num_stored != 0 || num_active != 0) {
1146 block->m_static_objects.m_stored.clear();
1147 block->m_static_objects.m_active.clear();
1148 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1149 MOD_REASON_CLEAR_ALL_OBJECTS);
1150 num_objs_cleared += num_stored + num_active;
1151 num_blocks_cleared++;
1153 num_blocks_checked++;
1155 if (report_interval != 0 &&
1156 num_blocks_checked % report_interval == 0) {
1157 float percent = 100.0 * (float)num_blocks_checked /
1158 loadable_blocks.size();
1159 actionstream << "ServerEnvironment::clearObjects(): "
1160 << "Cleared " << num_objs_cleared << " objects"
1161 << " in " << num_blocks_cleared << " blocks ("
1162 << percent << "%)" << std::endl;
1164 if (num_blocks_checked % unload_interval == 0) {
1165 m_map->unloadUnreferencedBlocks();
1168 m_map->unloadUnreferencedBlocks();
1170 // Drop references that were added above
1171 for (v3s16 p : loaded_blocks) {
1172 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1177 m_last_clear_objects_time = m_game_time;
1179 actionstream << "ServerEnvironment::clearObjects(): "
1180 << "Finished: Cleared " << num_objs_cleared << " objects"
1181 << " in " << num_blocks_cleared << " blocks" << std::endl;
1184 void ServerEnvironment::step(float dtime)
1186 ScopeProfiler sp2(g_profiler, "ServerEnv::step()", SPT_AVG);
1187 /* Step time of day */
1188 stepTimeOfDay(dtime);
1191 // NOTE: This is kind of funny on a singleplayer game, but doesn't
1192 // really matter that much.
1193 static thread_local const float server_step =
1194 g_settings->getFloat("dedicated_server_step");
1195 m_recommended_send_interval = server_step;
1201 m_game_time_fraction_counter += dtime;
1202 u32 inc_i = (u32)m_game_time_fraction_counter;
1203 m_game_time += inc_i;
1204 m_game_time_fraction_counter -= (float)inc_i;
1211 ScopeProfiler sp(g_profiler, "ServerEnv: move players", SPT_AVG);
1212 for (RemotePlayer *player : m_players) {
1213 // Ignore disconnected players
1214 if (player->getPeerId() == PEER_ID_INEXISTENT)
1218 player->move(dtime, this, 100 * BS);
1222 if (m_database_check_interval.step(dtime, 10.0f)) {
1223 m_auth_database->pingDatabase();
1224 m_player_database->pingDatabase();
1225 m_map->pingDatabase();
1228 Manage active block list
1230 if (m_active_blocks_management_interval.step(dtime, m_cache_active_block_mgmt_interval)) {
1231 ScopeProfiler sp(g_profiler, "ServerEnv: update active blocks", SPT_AVG);
1233 Get player block positions
1235 std::vector<PlayerSAO*> players;
1236 for (RemotePlayer *player: m_players) {
1237 // Ignore disconnected players
1238 if (player->getPeerId() == PEER_ID_INEXISTENT)
1241 PlayerSAO *playersao = player->getPlayerSAO();
1244 players.push_back(playersao);
1248 Update list of active blocks, collecting changes
1250 // use active_object_send_range_blocks since that is max distance
1251 // for active objects sent the client anyway
1252 static thread_local const s16 active_object_range =
1253 g_settings->getS16("active_object_send_range_blocks");
1254 static thread_local const s16 active_block_range =
1255 g_settings->getS16("active_block_range");
1256 std::set<v3s16> blocks_removed;
1257 std::set<v3s16> blocks_added;
1258 m_active_blocks.update(players, active_block_range, active_object_range,
1259 blocks_removed, blocks_added);
1262 Handle removed blocks
1265 // Convert active objects that are no more in active blocks to static
1266 deactivateFarObjects(false);
1268 for (const v3s16 &p: blocks_removed) {
1269 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1273 // Set current time as timestamp (and let it set ChangedFlag)
1274 block->setTimestamp(m_game_time);
1281 for (const v3s16 &p: blocks_added) {
1282 MapBlock *block = m_map->getBlockOrEmerge(p);
1284 m_active_blocks.m_list.erase(p);
1285 m_active_blocks.m_abm_list.erase(p);
1289 activateBlock(block);
1294 Mess around in active blocks
1296 if (m_active_blocks_nodemetadata_interval.step(dtime, m_cache_nodetimer_interval)) {
1297 ScopeProfiler sp(g_profiler, "ServerEnv: Run node timers", SPT_AVG);
1299 float dtime = m_cache_nodetimer_interval;
1301 for (const v3s16 &p: m_active_blocks.m_list) {
1302 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1306 // Reset block usage timer
1307 block->resetUsageTimer();
1309 // Set current time as timestamp
1310 block->setTimestampNoChangedFlag(m_game_time);
1311 // If time has changed much from the one on disk,
1312 // set block to be saved when it is unloaded
1313 if(block->getTimestamp() > block->getDiskTimestamp() + 60)
1314 block->raiseModified(MOD_STATE_WRITE_AT_UNLOAD,
1315 MOD_REASON_BLOCK_EXPIRED);
1318 std::vector<NodeTimer> elapsed_timers = block->m_node_timers.step(dtime);
1319 if (!elapsed_timers.empty()) {
1322 for (const NodeTimer &elapsed_timer: elapsed_timers) {
1323 n = block->getNodeNoEx(elapsed_timer.position);
1324 p2 = elapsed_timer.position + block->getPosRelative();
1325 if (m_script->node_on_timer(p2, n, elapsed_timer.elapsed)) {
1326 block->setNodeTimer(NodeTimer(
1327 elapsed_timer.timeout, 0, elapsed_timer.position));
1334 if (m_active_block_modifier_interval.step(dtime, m_cache_abm_interval)) {
1335 ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg per interval", SPT_AVG);
1336 TimeTaker timer("modify in active blocks per interval");
1338 // Initialize handling of ActiveBlockModifiers
1339 ABMHandler abmhandler(m_abms, m_cache_abm_interval, this, true);
1341 int blocks_scanned = 0;
1343 int blocks_cached = 0;
1345 std::vector<v3s16> output(m_active_blocks.m_abm_list.size());
1347 // Shuffle the active blocks so that each block gets an equal chance
1348 // of having its ABMs run.
1349 std::copy(m_active_blocks.m_abm_list.begin(), m_active_blocks.m_abm_list.end(), output.begin());
1350 std::shuffle(output.begin(), output.end(), m_rgen);
1353 // The time budget for ABMs is 20%.
1354 u32 max_time_ms = m_cache_abm_interval * 1000 / 5;
1355 for (const v3s16 &p : output) {
1356 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1362 // Set current time as timestamp
1363 block->setTimestampNoChangedFlag(m_game_time);
1365 /* Handle ActiveBlockModifiers */
1366 abmhandler.apply(block, blocks_scanned, abms_run, blocks_cached);
1368 u32 time_ms = timer.getTimerTime();
1370 if (time_ms > max_time_ms) {
1371 warningstream << "active block modifiers took "
1372 << time_ms << "ms (processed " << i << " of "
1373 << output.size() << " active blocks)" << std::endl;
1377 g_profiler->avg("ServerEnv: active blocks", m_active_blocks.m_abm_list.size());
1378 g_profiler->avg("ServerEnv: active blocks cached", blocks_cached);
1379 g_profiler->avg("ServerEnv: active blocks scanned for ABMs", blocks_scanned);
1380 g_profiler->avg("ServerEnv: ABMs run", abms_run);
1386 Step script environment (run global on_step())
1388 m_script->environment_Step(dtime);
1394 ScopeProfiler sp(g_profiler, "ServerEnv: Run SAO::step()", SPT_AVG);
1396 // This helps the objects to send data at the same time
1397 bool send_recommended = false;
1398 m_send_recommended_timer += dtime;
1399 if (m_send_recommended_timer > getSendRecommendedInterval()) {
1400 m_send_recommended_timer -= getSendRecommendedInterval();
1401 send_recommended = true;
1404 auto cb_state = [this, dtime, send_recommended] (ServerActiveObject *obj) {
1409 obj->step(dtime, send_recommended);
1410 // Read messages from object
1411 obj->dumpAOMessagesToQueue(m_active_object_messages);
1413 m_ao_manager.step(dtime, cb_state);
1417 Manage active objects
1419 if (m_object_management_interval.step(dtime, 0.5)) {
1420 removeRemovedObjects();
1424 Manage particle spawner expiration
1426 if (m_particle_management_interval.step(dtime, 1.0)) {
1427 for (std::unordered_map<u32, float>::iterator i = m_particle_spawners.begin();
1428 i != m_particle_spawners.end(); ) {
1429 //non expiring spawners
1430 if (i->second == PARTICLE_SPAWNER_NO_EXPIRY) {
1436 if (i->second <= 0.f)
1437 m_particle_spawners.erase(i++);
1443 // Send outdated player inventories
1444 for (RemotePlayer *player : m_players) {
1445 if (player->getPeerId() == PEER_ID_INEXISTENT)
1448 PlayerSAO *sao = player->getPlayerSAO();
1449 if (sao && player->inventory.checkModified())
1450 m_server->SendInventory(sao, true);
1453 // Send outdated detached inventories
1454 m_server->sendDetachedInventories(PEER_ID_INEXISTENT, true);
1457 u32 ServerEnvironment::addParticleSpawner(float exptime)
1459 // Timers with lifetime 0 do not expire
1460 float time = exptime > 0.f ? exptime : PARTICLE_SPAWNER_NO_EXPIRY;
1463 for (;;) { // look for unused particlespawner id
1465 std::unordered_map<u32, float>::iterator f = m_particle_spawners.find(id);
1466 if (f == m_particle_spawners.end()) {
1467 m_particle_spawners[id] = time;
1474 u32 ServerEnvironment::addParticleSpawner(float exptime, u16 attached_id)
1476 u32 id = addParticleSpawner(exptime);
1477 m_particle_spawner_attachments[id] = attached_id;
1478 if (ServerActiveObject *obj = getActiveObject(attached_id)) {
1479 obj->attachParticleSpawner(id);
1484 void ServerEnvironment::deleteParticleSpawner(u32 id, bool remove_from_object)
1486 m_particle_spawners.erase(id);
1487 const auto &it = m_particle_spawner_attachments.find(id);
1488 if (it != m_particle_spawner_attachments.end()) {
1489 u16 obj_id = it->second;
1490 ServerActiveObject *sao = getActiveObject(obj_id);
1491 if (sao != NULL && remove_from_object) {
1492 sao->detachParticleSpawner(id);
1494 m_particle_spawner_attachments.erase(id);
1498 u16 ServerEnvironment::addActiveObject(ServerActiveObject *object)
1500 assert(object); // Pre-condition
1502 u16 id = addActiveObjectRaw(object, true, 0);
1507 Finds out what new objects have been added to
1508 inside a radius around a position
1510 void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius,
1512 std::set<u16> ¤t_objects,
1513 std::queue<u16> &added_objects)
1515 f32 radius_f = radius * BS;
1516 f32 player_radius_f = player_radius * BS;
1518 if (player_radius_f < 0.0f)
1519 player_radius_f = 0.0f;
1521 m_ao_manager.getAddedActiveObjectsAroundPos(playersao->getBasePosition(), radius_f,
1522 player_radius_f, current_objects, added_objects);
1526 Finds out what objects have been removed from
1527 inside a radius around a position
1529 void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius,
1531 std::set<u16> ¤t_objects,
1532 std::queue<u16> &removed_objects)
1534 f32 radius_f = radius * BS;
1535 f32 player_radius_f = player_radius * BS;
1537 if (player_radius_f < 0)
1538 player_radius_f = 0;
1540 Go through current_objects; object is removed if:
1541 - object is not found in m_active_objects (this is actually an
1542 error condition; objects should be removed only after all clients
1543 have been informed about removal), or
1544 - object is to be removed or deactivated, or
1545 - object is too far away
1547 for (u16 id : current_objects) {
1548 ServerActiveObject *object = getActiveObject(id);
1550 if (object == NULL) {
1551 infostream << "ServerEnvironment::getRemovedActiveObjects():"
1552 << " object in current_objects is NULL" << std::endl;
1553 removed_objects.push(id);
1557 if (object->isGone()) {
1558 removed_objects.push(id);
1562 f32 distance_f = object->getBasePosition().getDistanceFrom(playersao->getBasePosition());
1563 if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1564 if (distance_f <= player_radius_f || player_radius_f == 0)
1566 } else if (distance_f <= radius_f)
1569 // Object is no longer visible
1570 removed_objects.push(id);
1574 void ServerEnvironment::setStaticForActiveObjectsInBlock(
1575 v3s16 blockpos, bool static_exists, v3s16 static_block)
1577 MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos);
1581 for (auto &so_it : block->m_static_objects.m_active) {
1582 // Get the ServerActiveObject counterpart to this StaticObject
1583 ServerActiveObject *sao = m_ao_manager.getActiveObject(so_it.first);
1585 // If this ever happens, there must be some kind of nasty bug.
1586 errorstream << "ServerEnvironment::setStaticForObjectsInBlock(): "
1587 "Object from MapBlock::m_static_objects::m_active not found "
1588 "in m_active_objects";
1592 sao->m_static_exists = static_exists;
1593 sao->m_static_block = static_block;
1597 ActiveObjectMessage ServerEnvironment::getActiveObjectMessage()
1599 if(m_active_object_messages.empty())
1600 return ActiveObjectMessage(0);
1602 ActiveObjectMessage message = m_active_object_messages.front();
1603 m_active_object_messages.pop();
1607 void ServerEnvironment::getSelectedActiveObjects(
1608 const core::line3d<f32> &shootline_on_map,
1609 std::vector<PointedThing> &objects)
1611 std::vector<ServerActiveObject *> objs;
1612 getObjectsInsideRadius(objs, shootline_on_map.start,
1613 shootline_on_map.getLength() + 10.0f, nullptr);
1614 const v3f line_vector = shootline_on_map.getVector();
1616 for (auto obj : objs) {
1617 aabb3f selection_box;
1618 if (!obj->getSelectionBox(&selection_box))
1621 v3f pos = obj->getBasePosition();
1623 aabb3f offsetted_box(selection_box.MinEdge + pos,
1624 selection_box.MaxEdge + pos);
1626 v3f current_intersection;
1627 v3s16 current_normal;
1628 if (boxLineCollision(offsetted_box, shootline_on_map.start, line_vector,
1629 ¤t_intersection, ¤t_normal)) {
1630 objects.emplace_back(
1631 (s16) obj->getId(), current_intersection, current_normal,
1632 (current_intersection - shootline_on_map.start).getLengthSQ());
1638 ************ Private methods *************
1641 u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object,
1642 bool set_changed, u32 dtime_s)
1644 if (!m_ao_manager.registerObject(object)) {
1648 // Register reference in scripting api (must be done before post-init)
1649 m_script->addObjectReference(object);
1650 // Post-initialize object
1651 object->addedToEnvironment(dtime_s);
1653 // Add static data to block
1654 if (object->isStaticAllowed()) {
1655 // Add static object to active static list of the block
1656 v3f objectpos = object->getBasePosition();
1657 StaticObject s_obj(object, objectpos);
1658 // Add to the block where the object is located in
1659 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
1660 MapBlock *block = m_map->emergeBlock(blockpos);
1662 block->m_static_objects.m_active[object->getId()] = s_obj;
1663 object->m_static_exists = true;
1664 object->m_static_block = blockpos;
1667 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1668 MOD_REASON_ADD_ACTIVE_OBJECT_RAW);
1670 v3s16 p = floatToInt(objectpos, BS);
1671 errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
1672 <<"could not emerge block for storing id="<<object->getId()
1673 <<" statically (pos="<<PP(p)<<")"<<std::endl;
1677 return object->getId();
1681 Remove objects that satisfy (isGone() && m_known_by_count==0)
1683 void ServerEnvironment::removeRemovedObjects()
1685 ScopeProfiler sp(g_profiler, "ServerEnvironment::removeRemovedObjects()", SPT_AVG);
1687 auto clear_cb = [this] (ServerActiveObject *obj, u16 id) {
1688 // This shouldn't happen but check it
1690 errorstream << "ServerEnvironment::removeRemovedObjects(): "
1691 << "NULL object found. id=" << id << std::endl;
1696 We will handle objects marked for removal or deactivation
1702 Delete static data from block if removed
1704 if (obj->m_pending_removal)
1705 deleteStaticFromBlock(obj, id, MOD_REASON_REMOVE_OBJECTS_REMOVE, false);
1707 // If still known by clients, don't actually remove. On some future
1708 // invocation this will be 0, which is when removal will continue.
1709 if(obj->m_known_by_count > 0)
1713 Move static data from active to stored if deactivated
1715 if (!obj->m_pending_removal && obj->m_static_exists) {
1716 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
1718 const auto i = block->m_static_objects.m_active.find(id);
1719 if (i != block->m_static_objects.m_active.end()) {
1720 block->m_static_objects.m_stored.push_back(i->second);
1721 block->m_static_objects.m_active.erase(id);
1722 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1723 MOD_REASON_REMOVE_OBJECTS_DEACTIVATE);
1725 warningstream << "ServerEnvironment::removeRemovedObjects(): "
1726 << "id=" << id << " m_static_exists=true but "
1727 << "static data doesn't actually exist in "
1728 << PP(obj->m_static_block) << std::endl;
1731 infostream << "Failed to emerge block from which an object to "
1732 << "be deactivated was loaded from. id=" << id << std::endl;
1736 // Tell the object about removal
1737 obj->removingFromEnvironment();
1738 // Deregister in scripting api
1739 m_script->removeObjectReference(obj);
1742 if (obj->environmentDeletes())
1748 m_ao_manager.clear(clear_cb);
1751 static void print_hexdump(std::ostream &o, const std::string &data)
1753 const int linelength = 16;
1754 for(int l=0; ; l++){
1755 int i0 = linelength * l;
1756 bool at_end = false;
1757 int thislinelength = linelength;
1758 if(i0 + thislinelength > (int)data.size()){
1759 thislinelength = data.size() - i0;
1762 for(int di=0; di<linelength; di++){
1765 if(di<thislinelength)
1766 porting::mt_snprintf(buf, sizeof(buf), "%.2x ", data[i]);
1768 porting::mt_snprintf(buf, sizeof(buf), " ");
1772 for(int di=0; di<thislinelength; di++){
1785 ServerActiveObject* ServerEnvironment::createSAO(ActiveObjectType type, v3f pos,
1786 const std::string &data)
1789 case ACTIVEOBJECT_TYPE_LUAENTITY:
1790 return new LuaEntitySAO(this, pos, data);
1792 warningstream << "ServerActiveObject: No factory for type=" << type << std::endl;
1798 Convert stored objects from blocks near the players to active.
1800 void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s)
1805 // Ignore if no stored objects (to not set changed flag)
1806 if(block->m_static_objects.m_stored.empty())
1809 verbosestream<<"ServerEnvironment::activateObjects(): "
1810 <<"activating objects of block "<<PP(block->getPos())
1811 <<" ("<<block->m_static_objects.m_stored.size()
1812 <<" objects)"<<std::endl;
1813 bool large_amount = (block->m_static_objects.m_stored.size() > g_settings->getU16("max_objects_per_block"));
1815 errorstream<<"suspiciously large amount of objects detected: "
1816 <<block->m_static_objects.m_stored.size()<<" in "
1817 <<PP(block->getPos())
1818 <<"; removing all of them."<<std::endl;
1819 // Clear stored list
1820 block->m_static_objects.m_stored.clear();
1821 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1822 MOD_REASON_TOO_MANY_OBJECTS);
1826 // Activate stored objects
1827 std::vector<StaticObject> new_stored;
1828 for (const StaticObject &s_obj : block->m_static_objects.m_stored) {
1829 // Create an active object from the data
1830 ServerActiveObject *obj = createSAO((ActiveObjectType) s_obj.type, s_obj.pos,
1832 // If couldn't create object, store static data back.
1834 errorstream<<"ServerEnvironment::activateObjects(): "
1835 <<"failed to create active object from static object "
1836 <<"in block "<<PP(s_obj.pos/BS)
1837 <<" type="<<(int)s_obj.type<<" data:"<<std::endl;
1838 print_hexdump(verbosestream, s_obj.data);
1840 new_stored.push_back(s_obj);
1843 verbosestream<<"ServerEnvironment::activateObjects(): "
1844 <<"activated static object pos="<<PP(s_obj.pos/BS)
1845 <<" type="<<(int)s_obj.type<<std::endl;
1846 // This will also add the object to the active static list
1847 addActiveObjectRaw(obj, false, dtime_s);
1850 // Clear stored list
1851 block->m_static_objects.m_stored.clear();
1852 // Add leftover failed stuff to stored list
1853 for (const StaticObject &s_obj : new_stored) {
1854 block->m_static_objects.m_stored.push_back(s_obj);
1858 Note: Block hasn't really been modified here.
1859 The objects have just been activated and moved from the stored
1860 static list to the active static list.
1861 As such, the block is essentially the same.
1862 Thus, do not call block->raiseModified(MOD_STATE_WRITE_NEEDED).
1863 Otherwise there would be a huge amount of unnecessary I/O.
1868 Convert objects that are not standing inside active blocks to static.
1870 If m_known_by_count != 0, active object is not deleted, but static
1871 data is still updated.
1873 If force_delete is set, active object is deleted nevertheless. It
1874 shall only be set so in the destructor of the environment.
1876 If block wasn't generated (not in memory or on disk),
1878 void ServerEnvironment::deactivateFarObjects(bool _force_delete)
1880 auto cb_deactivate = [this, _force_delete] (ServerActiveObject *obj, u16 id) {
1881 // force_delete might be overriden per object
1882 bool force_delete = _force_delete;
1884 // Do not deactivate if static data creation not allowed
1885 if (!force_delete && !obj->isStaticAllowed())
1888 // removeRemovedObjects() is responsible for these
1889 if (!force_delete && obj->isGone())
1892 const v3f &objectpos = obj->getBasePosition();
1894 // The block in which the object resides in
1895 v3s16 blockpos_o = getNodeBlockPos(floatToInt(objectpos, BS));
1897 // If object's static data is stored in a deactivated block and object
1898 // is actually located in an active block, re-save to the block in
1899 // which the object is actually located in.
1900 if (!force_delete && obj->m_static_exists &&
1901 !m_active_blocks.contains(obj->m_static_block) &&
1902 m_active_blocks.contains(blockpos_o)) {
1903 // Delete from block where object was located
1904 deleteStaticFromBlock(obj, id, MOD_REASON_STATIC_DATA_REMOVED, false);
1906 StaticObject s_obj(obj, objectpos);
1907 // Save to block where object is located
1908 saveStaticToBlock(blockpos_o, id, obj, s_obj, MOD_REASON_STATIC_DATA_ADDED);
1913 // If block is still active, don't remove
1914 if (!force_delete && m_active_blocks.contains(blockpos_o))
1917 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1918 << "deactivating object id=" << id << " on inactive block "
1919 << PP(blockpos_o) << std::endl;
1921 // If known by some client, don't immediately delete.
1922 bool pending_delete = (obj->m_known_by_count > 0 && !force_delete);
1925 Update the static data
1927 if (obj->isStaticAllowed()) {
1928 // Create new static object
1929 StaticObject s_obj(obj, objectpos);
1931 bool stays_in_same_block = false;
1932 bool data_changed = true;
1934 // Check if static data has changed considerably
1935 if (obj->m_static_exists) {
1936 if (obj->m_static_block == blockpos_o)
1937 stays_in_same_block = true;
1939 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
1942 const auto n = block->m_static_objects.m_active.find(id);
1943 if (n != block->m_static_objects.m_active.end()) {
1944 StaticObject static_old = n->second;
1946 float save_movem = obj->getMinimumSavedMovement();
1948 if (static_old.data == s_obj.data &&
1949 (static_old.pos - objectpos).getLength() < save_movem)
1950 data_changed = false;
1952 warningstream << "ServerEnvironment::deactivateFarObjects(): "
1953 << "id=" << id << " m_static_exists=true but "
1954 << "static data doesn't actually exist in "
1955 << PP(obj->m_static_block) << std::endl;
1961 While changes are always saved, blocks are only marked as modified
1962 if the object has moved or different staticdata. (see above)
1964 bool shall_be_written = (!stays_in_same_block || data_changed);
1965 u32 reason = shall_be_written ? MOD_REASON_STATIC_DATA_CHANGED : MOD_REASON_UNKNOWN;
1967 // Delete old static object
1968 deleteStaticFromBlock(obj, id, reason, false);
1970 // Add to the block where the object is located in
1971 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
1972 u16 store_id = pending_delete ? id : 0;
1973 if (!saveStaticToBlock(blockpos, store_id, obj, s_obj, reason))
1974 force_delete = true;
1978 If known by some client, set pending deactivation.
1979 Otherwise delete it immediately.
1981 if (pending_delete && !force_delete) {
1982 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1983 << "object id=" << id << " is known by clients"
1984 << "; not deleting yet" << std::endl;
1986 obj->m_pending_deactivation = true;
1990 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1991 << "object id=" << id << " is not known by clients"
1992 << "; deleting" << std::endl;
1994 // Tell the object about removal
1995 obj->removingFromEnvironment();
1996 // Deregister in scripting api
1997 m_script->removeObjectReference(obj);
1999 // Delete active object
2000 if (obj->environmentDeletes())
2006 m_ao_manager.clear(cb_deactivate);
2009 void ServerEnvironment::deleteStaticFromBlock(
2010 ServerActiveObject *obj, u16 id, u32 mod_reason, bool no_emerge)
2012 if (!obj->m_static_exists)
2017 block = m_map->getBlockNoCreateNoEx(obj->m_static_block);
2019 block = m_map->emergeBlock(obj->m_static_block, false);
2022 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2023 << " when deleting static data of object from it. id=" << id << std::endl;
2027 block->m_static_objects.remove(id);
2028 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2029 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2031 obj->m_static_exists = false;
2034 bool ServerEnvironment::saveStaticToBlock(
2035 v3s16 blockpos, u16 store_id,
2036 ServerActiveObject *obj, const StaticObject &s_obj,
2039 MapBlock *block = nullptr;
2041 block = m_map->emergeBlock(blockpos);
2042 } catch (InvalidPositionException &e) {
2043 // Handled via NULL pointer
2044 // NOTE: emergeBlock's failure is usually determined by it
2045 // actually returning NULL
2049 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2050 << " when saving static data of object to it. id=" << store_id << std::endl;
2053 if (block->m_static_objects.m_stored.size() >= g_settings->getU16("max_objects_per_block")) {
2054 warningstream << "ServerEnv: Trying to store id = " << store_id
2055 << " statically but block " << PP(blockpos)
2056 << " already contains "
2057 << block->m_static_objects.m_stored.size()
2058 << " objects." << std::endl;
2062 block->m_static_objects.insert(store_id, s_obj);
2063 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2064 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2066 obj->m_static_exists = true;
2067 obj->m_static_block = blockpos;
2072 PlayerDatabase *ServerEnvironment::openPlayerDatabase(const std::string &name,
2073 const std::string &savedir, const Settings &conf)
2076 if (name == "sqlite3")
2077 return new PlayerDatabaseSQLite3(savedir);
2079 if (name == "dummy")
2080 return new Database_Dummy();
2082 if (name == "postgresql") {
2083 std::string connect_string;
2084 conf.getNoEx("pgsql_player_connection", connect_string);
2085 return new PlayerDatabasePostgreSQL(connect_string);
2088 if (name == "files")
2089 return new PlayerDatabaseFiles(savedir + DIR_DELIM + "players");
2091 throw BaseException(std::string("Database backend ") + name + " not supported.");
2094 bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params,
2095 const Settings &cmd_args)
2097 std::string migrate_to = cmd_args.get("migrate-players");
2099 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2100 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2101 errorstream << "Cannot read world.mt!" << std::endl;
2105 if (!world_mt.exists("player_backend")) {
2106 errorstream << "Please specify your current backend in world.mt:"
2108 << " player_backend = {files|sqlite3|postgresql}"
2113 std::string backend = world_mt.get("player_backend");
2114 if (backend == migrate_to) {
2115 errorstream << "Cannot migrate: new backend is same"
2116 << " as the old one" << std::endl;
2120 const std::string players_backup_path = game_params.world_path + DIR_DELIM
2123 if (backend == "files") {
2124 // Create backup directory
2125 fs::CreateDir(players_backup_path);
2129 PlayerDatabase *srcdb = ServerEnvironment::openPlayerDatabase(backend,
2130 game_params.world_path, world_mt);
2131 PlayerDatabase *dstdb = ServerEnvironment::openPlayerDatabase(migrate_to,
2132 game_params.world_path, world_mt);
2134 std::vector<std::string> player_list;
2135 srcdb->listPlayers(player_list);
2136 for (std::vector<std::string>::const_iterator it = player_list.begin();
2137 it != player_list.end(); ++it) {
2138 actionstream << "Migrating player " << it->c_str() << std::endl;
2139 RemotePlayer player(it->c_str(), NULL);
2140 PlayerSAO playerSAO(NULL, &player, 15000, false);
2142 srcdb->loadPlayer(&player, &playerSAO);
2144 playerSAO.finalize(&player, std::set<std::string>());
2145 player.setPlayerSAO(&playerSAO);
2147 dstdb->savePlayer(&player);
2149 // For files source, move player files to backup dir
2150 if (backend == "files") {
2152 game_params.world_path + DIR_DELIM + "players" + DIR_DELIM + (*it),
2153 players_backup_path + DIR_DELIM + (*it));
2157 actionstream << "Successfully migrated " << player_list.size() << " players"
2159 world_mt.set("player_backend", migrate_to);
2160 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2161 errorstream << "Failed to update world.mt!" << std::endl;
2163 actionstream << "world.mt updated" << std::endl;
2165 // When migration is finished from file backend, remove players directory if empty
2166 if (backend == "files") {
2167 fs::DeleteSingleFileOrEmptyDirectory(game_params.world_path + DIR_DELIM
2174 } catch (BaseException &e) {
2175 errorstream << "An error occurred during migration: " << e.what() << std::endl;
2181 AuthDatabase *ServerEnvironment::openAuthDatabase(
2182 const std::string &name, const std::string &savedir, const Settings &conf)
2184 if (name == "sqlite3")
2185 return new AuthDatabaseSQLite3(savedir);
2187 if (name == "files")
2188 return new AuthDatabaseFiles(savedir);
2190 throw BaseException(std::string("Database backend ") + name + " not supported.");
2193 bool ServerEnvironment::migrateAuthDatabase(
2194 const GameParams &game_params, const Settings &cmd_args)
2196 std::string migrate_to = cmd_args.get("migrate-auth");
2198 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2199 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2200 errorstream << "Cannot read world.mt!" << std::endl;
2204 std::string backend = "files";
2205 if (world_mt.exists("auth_backend"))
2206 backend = world_mt.get("auth_backend");
2208 warningstream << "No auth_backend found in world.mt, "
2209 "assuming \"files\"." << std::endl;
2211 if (backend == migrate_to) {
2212 errorstream << "Cannot migrate: new backend is same"
2213 << " as the old one" << std::endl;
2218 const std::unique_ptr<AuthDatabase> srcdb(ServerEnvironment::openAuthDatabase(
2219 backend, game_params.world_path, world_mt));
2220 const std::unique_ptr<AuthDatabase> dstdb(ServerEnvironment::openAuthDatabase(
2221 migrate_to, game_params.world_path, world_mt));
2223 std::vector<std::string> names_list;
2224 srcdb->listNames(names_list);
2225 for (const std::string &name : names_list) {
2226 actionstream << "Migrating auth entry for " << name << std::endl;
2228 AuthEntry authEntry;
2229 success = srcdb->getAuth(name, authEntry);
2230 success = success && dstdb->createAuth(authEntry);
2232 errorstream << "Failed to migrate " << name << std::endl;
2235 actionstream << "Successfully migrated " << names_list.size()
2236 << " auth entries" << std::endl;
2237 world_mt.set("auth_backend", migrate_to);
2238 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2239 errorstream << "Failed to update world.mt!" << std::endl;
2241 actionstream << "world.mt updated" << std::endl;
2243 if (backend == "files") {
2244 // special-case files migration:
2245 // move auth.txt to auth.txt.bak if possible
2246 std::string auth_txt_path =
2247 game_params.world_path + DIR_DELIM + "auth.txt";
2248 std::string auth_bak_path = auth_txt_path + ".bak";
2249 if (!fs::PathExists(auth_bak_path))
2250 if (fs::Rename(auth_txt_path, auth_bak_path))
2251 actionstream << "Renamed auth.txt to auth.txt.bak"
2254 errorstream << "Could not rename auth.txt to "
2255 "auth.txt.bak" << std::endl;
2257 warningstream << "auth.txt.bak already exists, auth.txt "
2258 "not renamed" << std::endl;
2261 } catch (BaseException &e) {
2262 errorstream << "An error occurred during migration: " << e.what()