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/player_sao.h"
49 #define LBM_NAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_:"
51 // A number that is much smaller than the timeout for particle spawners should/could ever be
52 #define PARTICLE_SPAWNER_NO_EXPIRY -1024.f
58 ABMWithState::ABMWithState(ActiveBlockModifier *abm_):
61 // Initialize timer to random value to spread processing
62 float itv = abm->getTriggerInterval();
63 itv = MYMAX(0.001, itv); // No less than 1ms
64 int minval = MYMAX(-0.51*itv, -60); // Clamp to
65 int maxval = MYMIN(0.51*itv, 60); // +-60 seconds
66 timer = myrand_range(minval, maxval);
73 void LBMContentMapping::deleteContents()
75 for (auto &it : lbm_list) {
80 void LBMContentMapping::addLBM(LoadingBlockModifierDef *lbm_def, IGameDef *gamedef)
82 // Add the lbm_def to the LBMContentMapping.
83 // Unknown names get added to the global NameIdMapping.
84 const NodeDefManager *nodedef = gamedef->ndef();
86 lbm_list.push_back(lbm_def);
88 for (const std::string &nodeTrigger: lbm_def->trigger_contents) {
89 std::vector<content_t> c_ids;
90 bool found = nodedef->getIds(nodeTrigger, c_ids);
92 content_t c_id = gamedef->allocateUnknownNodeId(nodeTrigger);
93 if (c_id == CONTENT_IGNORE) {
94 // Seems it can't be allocated.
95 warningstream << "Could not internalize node name \"" << nodeTrigger
96 << "\" while loading LBM \"" << lbm_def->name << "\"." << std::endl;
99 c_ids.push_back(c_id);
102 for (content_t c_id : c_ids) {
103 map[c_id].push_back(lbm_def);
108 const std::vector<LoadingBlockModifierDef *> *
109 LBMContentMapping::lookup(content_t c) const
111 lbm_map::const_iterator it = map.find(c);
114 // This first dereferences the iterator, returning
115 // a std::vector<LoadingBlockModifierDef *>
116 // reference, then we convert it to a pointer.
117 return &(it->second);
120 LBMManager::~LBMManager()
122 for (auto &m_lbm_def : m_lbm_defs) {
123 delete m_lbm_def.second;
126 for (auto &it : m_lbm_lookup) {
127 (it.second).deleteContents();
131 void LBMManager::addLBMDef(LoadingBlockModifierDef *lbm_def)
133 // Precondition, in query mode the map isn't used anymore
134 FATAL_ERROR_IF(m_query_mode,
135 "attempted to modify LBMManager in query mode");
137 if (!string_allowed(lbm_def->name, LBM_NAME_ALLOWED_CHARS)) {
138 throw ModError("Error adding LBM \"" + lbm_def->name +
139 "\": Does not follow naming conventions: "
140 "Only characters [a-z0-9_:] are allowed.");
143 m_lbm_defs[lbm_def->name] = lbm_def;
146 void LBMManager::loadIntroductionTimes(const std::string ×,
147 IGameDef *gamedef, u32 now)
152 // Storing it in a map first instead of
153 // handling the stuff directly in the loop
154 // removes all duplicate entries.
155 // TODO make this std::unordered_map
156 std::map<std::string, u32> introduction_times;
159 The introduction times string consists of name~time entries,
160 with each entry terminated by a semicolon. The time is decimal.
165 while ((idx_new = times.find(';', idx)) != std::string::npos) {
166 std::string entry = times.substr(idx, idx_new - idx);
167 std::vector<std::string> components = str_split(entry, '~');
168 if (components.size() != 2)
169 throw SerializationError("Introduction times entry \""
170 + entry + "\" requires exactly one '~'!");
171 const std::string &name = components[0];
172 u32 time = from_string<u32>(components[1]);
173 introduction_times[name] = time;
177 // Put stuff from introduction_times into m_lbm_lookup
178 for (std::map<std::string, u32>::const_iterator it = introduction_times.begin();
179 it != introduction_times.end(); ++it) {
180 const std::string &name = it->first;
181 u32 time = it->second;
183 std::map<std::string, LoadingBlockModifierDef *>::iterator def_it =
184 m_lbm_defs.find(name);
185 if (def_it == m_lbm_defs.end()) {
186 // This seems to be an LBM entry for
187 // an LBM we haven't loaded. Discard it.
190 LoadingBlockModifierDef *lbm_def = def_it->second;
191 if (lbm_def->run_at_every_load) {
192 // This seems to be an LBM entry for
193 // an LBM that runs at every load.
194 // Don't add it just yet.
198 m_lbm_lookup[time].addLBM(lbm_def, gamedef);
200 // Erase the entry so that we know later
201 // what elements didn't get put into m_lbm_lookup
202 m_lbm_defs.erase(name);
205 // Now also add the elements from m_lbm_defs to m_lbm_lookup
206 // that weren't added in the previous step.
207 // They are introduced first time to this world,
208 // or are run at every load (introducement time hardcoded to U32_MAX).
210 LBMContentMapping &lbms_we_introduce_now = m_lbm_lookup[now];
211 LBMContentMapping &lbms_running_always = m_lbm_lookup[U32_MAX];
213 for (auto &m_lbm_def : m_lbm_defs) {
214 if (m_lbm_def.second->run_at_every_load) {
215 lbms_running_always.addLBM(m_lbm_def.second, gamedef);
217 lbms_we_introduce_now.addLBM(m_lbm_def.second, gamedef);
221 // Clear the list, so that we don't delete remaining elements
222 // twice in the destructor
226 std::string LBMManager::createIntroductionTimesString()
228 // Precondition, we must be in query mode
229 FATAL_ERROR_IF(!m_query_mode,
230 "attempted to query on non fully set up LBMManager");
232 std::ostringstream oss;
233 for (const auto &it : m_lbm_lookup) {
235 const std::vector<LoadingBlockModifierDef *> &lbm_list = it.second.lbm_list;
236 for (const auto &lbm_def : lbm_list) {
237 // Don't add if the LBM runs at every load,
238 // then introducement time is hardcoded
239 // and doesn't need to be stored
240 if (lbm_def->run_at_every_load)
242 oss << lbm_def->name << "~" << time << ";";
248 void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, u32 stamp)
250 // Precondition, we need m_lbm_lookup to be initialized
251 FATAL_ERROR_IF(!m_query_mode,
252 "attempted to query on non fully set up LBMManager");
253 v3s16 pos_of_block = block->getPosRelative();
257 lbm_lookup_map::const_iterator it = getLBMsIntroducedAfter(stamp);
258 for (; it != m_lbm_lookup.end(); ++it) {
259 // Cache previous version to speedup lookup which has a very high performance
260 // penalty on each call
261 content_t previous_c{};
262 std::vector<LoadingBlockModifierDef *> *lbm_list = nullptr;
264 for (pos.X = 0; pos.X < MAP_BLOCKSIZE; pos.X++)
265 for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++)
266 for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++) {
267 n = block->getNodeNoEx(pos);
270 // If content_t are not matching perform an LBM lookup
271 if (previous_c != c) {
272 lbm_list = (std::vector<LoadingBlockModifierDef *> *)
273 it->second.lookup(c);
279 for (auto lbmdef : *lbm_list) {
280 lbmdef->trigger(env, pos + pos_of_block, n);
290 void fillRadiusBlock(v3s16 p0, s16 r, std::set<v3s16> &list)
293 for(p.X=p0.X-r; p.X<=p0.X+r; p.X++)
294 for(p.Y=p0.Y-r; p.Y<=p0.Y+r; p.Y++)
295 for(p.Z=p0.Z-r; p.Z<=p0.Z+r; p.Z++)
298 if (p.getDistanceFrom(p0) <= r) {
305 void fillViewConeBlock(v3s16 p0,
307 const v3f camera_pos,
308 const v3f camera_dir,
309 const float camera_fov,
310 std::set<v3s16> &list)
313 const s16 r_nodes = r * BS * MAP_BLOCKSIZE;
314 for (p.X = p0.X - r; p.X <= p0.X+r; p.X++)
315 for (p.Y = p0.Y - r; p.Y <= p0.Y+r; p.Y++)
316 for (p.Z = p0.Z - r; p.Z <= p0.Z+r; p.Z++) {
317 if (isBlockInSight(p, camera_pos, camera_dir, camera_fov, r_nodes)) {
323 void ActiveBlockList::update(std::vector<PlayerSAO*> &active_players,
324 s16 active_block_range,
325 s16 active_object_range,
326 std::set<v3s16> &blocks_removed,
327 std::set<v3s16> &blocks_added)
332 std::set<v3s16> newlist = m_forceloaded_list;
333 m_abm_list = m_forceloaded_list;
334 for (const PlayerSAO *playersao : active_players) {
335 v3s16 pos = getNodeBlockPos(floatToInt(playersao->getBasePosition(), BS));
336 fillRadiusBlock(pos, active_block_range, m_abm_list);
337 fillRadiusBlock(pos, active_block_range, newlist);
339 s16 player_ao_range = std::min(active_object_range, playersao->getWantedRange());
340 // only do this if this would add blocks
341 if (player_ao_range > active_block_range) {
342 v3f camera_dir = v3f(0,0,1);
343 camera_dir.rotateYZBy(playersao->getLookPitch());
344 camera_dir.rotateXZBy(playersao->getRotation().Y);
345 fillViewConeBlock(pos,
347 playersao->getEyePosition(),
355 Find out which blocks on the old list are not on the new list
357 // Go through old list
358 for (v3s16 p : m_list) {
359 // If not on new list, it's been removed
360 if (newlist.find(p) == newlist.end())
361 blocks_removed.insert(p);
365 Find out which blocks on the new list are not on the old list
367 // Go through new list
368 for (v3s16 p : newlist) {
369 // If not on old list, it's been added
370 if(m_list.find(p) == m_list.end())
371 blocks_added.insert(p);
378 for (v3s16 p : newlist) {
387 // Random device to seed pseudo random generators.
388 static std::random_device seed;
390 ServerEnvironment::ServerEnvironment(ServerMap *map,
391 ServerScripting *scriptIface, Server *server,
392 const std::string &path_world):
395 m_script(scriptIface),
397 m_path_world(path_world),
400 // Determine which database backend to use
401 std::string conf_path = path_world + DIR_DELIM + "world.mt";
404 std::string player_backend_name = "sqlite3";
405 std::string auth_backend_name = "sqlite3";
407 bool succeeded = conf.readConfigFile(conf_path.c_str());
409 // If we open world.mt read the backend configurations.
411 // Read those values before setting defaults
412 bool player_backend_exists = conf.exists("player_backend");
413 bool auth_backend_exists = conf.exists("auth_backend");
415 // player backend is not set, assume it's legacy file backend.
416 if (!player_backend_exists) {
417 // fall back to files
418 conf.set("player_backend", "files");
419 player_backend_name = "files";
421 if (!conf.updateConfigFile(conf_path.c_str())) {
422 errorstream << "ServerEnvironment::ServerEnvironment(): "
423 << "Failed to update world.mt!" << std::endl;
426 conf.getNoEx("player_backend", player_backend_name);
429 // auth backend is not set, assume it's legacy file backend.
430 if (!auth_backend_exists) {
431 conf.set("auth_backend", "files");
432 auth_backend_name = "files";
434 if (!conf.updateConfigFile(conf_path.c_str())) {
435 errorstream << "ServerEnvironment::ServerEnvironment(): "
436 << "Failed to update world.mt!" << std::endl;
439 conf.getNoEx("auth_backend", auth_backend_name);
443 if (player_backend_name == "files") {
444 warningstream << "/!\\ You are using old player file backend. "
445 << "This backend is deprecated and will be removed in a future release /!\\"
446 << std::endl << "Switching to SQLite3 or PostgreSQL is advised, "
447 << "please read http://wiki.minetest.net/Database_backends." << std::endl;
450 if (auth_backend_name == "files") {
451 warningstream << "/!\\ You are using old auth file backend. "
452 << "This backend is deprecated and will be removed in a future release /!\\"
453 << std::endl << "Switching to SQLite3 is advised, "
454 << "please read http://wiki.minetest.net/Database_backends." << std::endl;
457 m_player_database = openPlayerDatabase(player_backend_name, path_world, conf);
458 m_auth_database = openAuthDatabase(auth_backend_name, path_world, conf);
461 ServerEnvironment::~ServerEnvironment()
463 // Clear active block list.
464 // This makes the next one delete all active objects.
465 m_active_blocks.clear();
467 // Convert all objects to static and delete the active objects
468 deactivateFarObjects(true);
473 // Delete ActiveBlockModifiers
474 for (ABMWithState &m_abm : m_abms) {
478 // Deallocate players
479 for (RemotePlayer *m_player : m_players) {
483 delete m_player_database;
484 delete m_auth_database;
487 Map & ServerEnvironment::getMap()
492 ServerMap & ServerEnvironment::getServerMap()
497 RemotePlayer *ServerEnvironment::getPlayer(const session_t peer_id)
499 for (RemotePlayer *player : m_players) {
500 if (player->getPeerId() == peer_id)
506 RemotePlayer *ServerEnvironment::getPlayer(const char* name)
508 for (RemotePlayer *player : m_players) {
509 if (strcmp(player->getName(), name) == 0)
515 void ServerEnvironment::addPlayer(RemotePlayer *player)
518 Check that peer_ids are unique.
519 Also check that names are unique.
520 Exception: there can be multiple players with peer_id=0
522 // If peer id is non-zero, it has to be unique.
523 if (player->getPeerId() != PEER_ID_INEXISTENT)
524 FATAL_ERROR_IF(getPlayer(player->getPeerId()) != NULL, "Peer id not unique");
525 // Name has to be unique.
526 FATAL_ERROR_IF(getPlayer(player->getName()) != NULL, "Player name not unique");
528 m_players.push_back(player);
531 void ServerEnvironment::removePlayer(RemotePlayer *player)
533 for (std::vector<RemotePlayer *>::iterator it = m_players.begin();
534 it != m_players.end(); ++it) {
535 if ((*it) == player) {
543 bool ServerEnvironment::removePlayerFromDatabase(const std::string &name)
545 return m_player_database->removePlayer(name);
548 void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason,
549 const std::string &str_reason, bool reconnect)
551 for (RemotePlayer *player : m_players) {
552 m_server->DenyAccessVerCompliant(player->getPeerId(),
553 player->protocol_version, reason, str_reason, reconnect);
557 void ServerEnvironment::saveLoadedPlayers(bool force)
559 for (RemotePlayer *player : m_players) {
560 if (force || player->checkModified() || (player->getPlayerSAO() &&
561 player->getPlayerSAO()->getMeta().isModified())) {
563 m_player_database->savePlayer(player);
564 } catch (DatabaseException &e) {
565 errorstream << "Failed to save player " << player->getName() << " exception: "
566 << e.what() << std::endl;
573 void ServerEnvironment::savePlayer(RemotePlayer *player)
576 m_player_database->savePlayer(player);
577 } catch (DatabaseException &e) {
578 errorstream << "Failed to save player " << player->getName() << " exception: "
579 << e.what() << std::endl;
584 PlayerSAO *ServerEnvironment::loadPlayer(RemotePlayer *player, bool *new_player,
585 session_t peer_id, bool is_singleplayer)
587 PlayerSAO *playersao = new PlayerSAO(this, player, peer_id, is_singleplayer);
588 // Create player if it doesn't exist
589 if (!m_player_database->loadPlayer(player, playersao)) {
591 // Set player position
592 infostream << "Server: Finding spawn place for player \""
593 << player->getName() << "\"" << std::endl;
594 playersao->setBasePosition(m_server->findSpawnPos());
596 // Make sure the player is saved
597 player->setModified(true);
599 // If the player exists, ensure that they respawn inside legal bounds
600 // This fixes an assert crash when the player can't be added
601 // to the environment
602 if (objectpos_over_limit(playersao->getBasePosition())) {
603 actionstream << "Respawn position for player \""
604 << player->getName() << "\" outside limits, resetting" << std::endl;
605 playersao->setBasePosition(m_server->findSpawnPos());
609 // Add player to environment
612 /* Clean up old HUD elements from previous sessions */
615 /* Add object to environment */
616 addActiveObject(playersao);
621 void ServerEnvironment::saveMeta()
623 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
625 // Open file and serialize
626 std::ostringstream ss(std::ios_base::binary);
629 args.setU64("game_time", m_game_time);
630 args.setU64("time_of_day", getTimeOfDay());
631 args.setU64("last_clear_objects_time", m_last_clear_objects_time);
632 args.setU64("lbm_introduction_times_version", 1);
633 args.set("lbm_introduction_times",
634 m_lbm_mgr.createIntroductionTimesString());
635 args.setU64("day_count", m_day_count);
639 if(!fs::safeWriteToFile(path, ss.str()))
641 infostream<<"ServerEnvironment::saveMeta(): Failed to write "
643 throw SerializationError("Couldn't save env meta");
647 void ServerEnvironment::loadMeta()
649 // If file doesn't exist, load default environment metadata
650 if (!fs::PathExists(m_path_world + DIR_DELIM "env_meta.txt")) {
651 infostream << "ServerEnvironment: Loading default environment metadata"
657 infostream << "ServerEnvironment: Loading environment metadata" << std::endl;
659 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
661 // Open file and deserialize
662 std::ifstream is(path.c_str(), std::ios_base::binary);
664 infostream << "ServerEnvironment::loadMeta(): Failed to open "
665 << path << std::endl;
666 throw SerializationError("Couldn't load env meta");
671 if (!args.parseConfigLines(is, "EnvArgsEnd")) {
672 throw SerializationError("ServerEnvironment::loadMeta(): "
673 "EnvArgsEnd not found!");
677 m_game_time = args.getU64("game_time");
678 } catch (SettingNotFoundException &e) {
679 // Getting this is crucial, otherwise timestamps are useless
680 throw SerializationError("Couldn't load env meta game_time");
683 setTimeOfDay(args.exists("time_of_day") ?
684 // set day to early morning by default
685 args.getU64("time_of_day") : 5250);
687 m_last_clear_objects_time = args.exists("last_clear_objects_time") ?
688 // If missing, do as if clearObjects was never called
689 args.getU64("last_clear_objects_time") : 0;
691 std::string lbm_introduction_times;
693 u64 ver = args.getU64("lbm_introduction_times_version");
695 lbm_introduction_times = args.get("lbm_introduction_times");
697 infostream << "ServerEnvironment::loadMeta(): Non-supported"
698 << " introduction time version " << ver << std::endl;
700 } catch (SettingNotFoundException &e) {
701 // No problem, this is expected. Just continue with an empty string
703 m_lbm_mgr.loadIntroductionTimes(lbm_introduction_times, m_server, m_game_time);
705 m_day_count = args.exists("day_count") ?
706 args.getU64("day_count") : 0;
710 * called if env_meta.txt doesn't exist (e.g. new world)
712 void ServerEnvironment::loadDefaultMeta()
714 m_lbm_mgr.loadIntroductionTimes("", m_server, m_game_time);
719 ActiveBlockModifier *abm;
721 std::vector<content_t> required_neighbors;
722 bool check_required_neighbors; // false if required_neighbors is known to be empty
728 ServerEnvironment *m_env;
729 std::vector<std::vector<ActiveABM> *> m_aabms;
731 ABMHandler(std::vector<ABMWithState> &abms,
732 float dtime_s, ServerEnvironment *env,
738 const NodeDefManager *ndef = env->getGameDef()->ndef();
739 for (ABMWithState &abmws : abms) {
740 ActiveBlockModifier *abm = abmws.abm;
741 float trigger_interval = abm->getTriggerInterval();
742 if(trigger_interval < 0.001)
743 trigger_interval = 0.001;
744 float actual_interval = dtime_s;
746 abmws.timer += dtime_s;
747 if(abmws.timer < trigger_interval)
749 abmws.timer -= trigger_interval;
750 actual_interval = trigger_interval;
752 float chance = abm->getTriggerChance();
757 if (abm->getSimpleCatchUp()) {
758 float intervals = actual_interval / trigger_interval;
761 aabm.chance = chance / intervals;
765 aabm.chance = chance;
769 const std::vector<std::string> &required_neighbors_s =
770 abm->getRequiredNeighbors();
771 for (const std::string &required_neighbor_s : required_neighbors_s) {
772 ndef->getIds(required_neighbor_s, aabm.required_neighbors);
774 aabm.check_required_neighbors = !required_neighbors_s.empty();
777 const std::vector<std::string> &contents_s = abm->getTriggerContents();
778 for (const std::string &content_s : contents_s) {
779 std::vector<content_t> ids;
780 ndef->getIds(content_s, ids);
781 for (content_t c : ids) {
782 if (c >= m_aabms.size())
783 m_aabms.resize(c + 256, NULL);
785 m_aabms[c] = new std::vector<ActiveABM>;
786 m_aabms[c]->push_back(aabm);
794 for (auto &aabms : m_aabms)
798 // Find out how many objects the given block and its neighbours contain.
799 // Returns the number of objects in the block, and also in 'wider' the
800 // number of objects in the block and all its neighbours. The latter
801 // may an estimate if any neighbours are unloaded.
802 u32 countObjects(MapBlock *block, ServerMap * map, u32 &wider)
805 u32 wider_unknown_count = 0;
806 for(s16 x=-1; x<=1; x++)
807 for(s16 y=-1; y<=1; y++)
808 for(s16 z=-1; z<=1; z++)
810 MapBlock *block2 = map->getBlockNoCreateNoEx(
811 block->getPos() + v3s16(x,y,z));
813 wider_unknown_count++;
816 wider += block2->m_static_objects.m_active.size()
817 + block2->m_static_objects.m_stored.size();
820 u32 active_object_count = block->m_static_objects.m_active.size();
821 u32 wider_known_count = 3*3*3 - wider_unknown_count;
822 wider += wider_unknown_count * wider / wider_known_count;
823 return active_object_count;
826 void apply(MapBlock *block, int &blocks_scanned, int &abms_run, int &blocks_cached)
828 if(m_aabms.empty() || block->isDummy())
831 // Check the content type cache first
832 // to see whether there are any ABMs
833 // to be run at all for this block.
834 if (block->contents_cached) {
836 bool run_abms = false;
837 for (content_t c : block->contents) {
838 if (c < m_aabms.size() && m_aabms[c]) {
847 block->contents.clear();
851 ServerMap *map = &m_env->getServerMap();
853 u32 active_object_count_wider;
854 u32 active_object_count = this->countObjects(block, map, active_object_count_wider);
855 m_env->m_added_objects = 0;
858 for(p0.X=0; p0.X<MAP_BLOCKSIZE; p0.X++)
859 for(p0.Y=0; p0.Y<MAP_BLOCKSIZE; p0.Y++)
860 for(p0.Z=0; p0.Z<MAP_BLOCKSIZE; p0.Z++)
862 const MapNode &n = block->getNodeUnsafe(p0);
863 content_t c = n.getContent();
864 // Cache content types as we go
865 if (!block->contents_cached && !block->do_not_cache_contents) {
866 block->contents.insert(c);
867 if (block->contents.size() > 64) {
868 // Too many different nodes... don't try to cache
869 block->do_not_cache_contents = true;
870 block->contents.clear();
874 if (c >= m_aabms.size() || !m_aabms[c])
877 v3s16 p = p0 + block->getPosRelative();
878 for (ActiveABM &aabm : *m_aabms[c]) {
879 if (myrand() % aabm.chance != 0)
883 if (aabm.check_required_neighbors) {
885 for(p1.X = p0.X-1; p1.X <= p0.X+1; p1.X++)
886 for(p1.Y = p0.Y-1; p1.Y <= p0.Y+1; p1.Y++)
887 for(p1.Z = p0.Z-1; p1.Z <= p0.Z+1; p1.Z++)
892 if (block->isValidPosition(p1)) {
893 // if the neighbor is found on the same map block
894 // get it straight from there
895 const MapNode &n = block->getNodeUnsafe(p1);
898 // otherwise consult the map
899 MapNode n = map->getNode(p1 + block->getPosRelative());
902 if (CONTAINS(aabm.required_neighbors, c))
905 // No required neighbor found
911 // Call all the trigger variations
912 aabm.abm->trigger(m_env, p, n);
913 aabm.abm->trigger(m_env, p, n,
914 active_object_count, active_object_count_wider);
916 // Count surrounding objects again if the abms added any
917 if(m_env->m_added_objects > 0) {
918 active_object_count = countObjects(block, map, active_object_count_wider);
919 m_env->m_added_objects = 0;
923 block->contents_cached = !block->do_not_cache_contents;
927 void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime)
929 // Reset usage timer immediately, otherwise a block that becomes active
930 // again at around the same time as it would normally be unloaded will
931 // get unloaded incorrectly. (I think this still leaves a small possibility
932 // of a race condition between this and server::AsyncRunStep, which only
933 // some kind of synchronisation will fix, but it at least reduces the window
934 // of opportunity for it to break from seconds to nanoseconds)
935 block->resetUsageTimer();
937 // Get time difference
939 u32 stamp = block->getTimestamp();
940 if (m_game_time > stamp && stamp != BLOCK_TIMESTAMP_UNDEFINED)
941 dtime_s = m_game_time - stamp;
942 dtime_s += additional_dtime;
944 /*infostream<<"ServerEnvironment::activateBlock(): block timestamp: "
945 <<stamp<<", game time: "<<m_game_time<<std::endl;*/
947 // Remove stored static objects if clearObjects was called since block's timestamp
948 if (stamp == BLOCK_TIMESTAMP_UNDEFINED || stamp < m_last_clear_objects_time) {
949 block->m_static_objects.m_stored.clear();
950 // do not set changed flag to avoid unnecessary mapblock writes
953 // Set current time as timestamp
954 block->setTimestampNoChangedFlag(m_game_time);
956 /*infostream<<"ServerEnvironment::activateBlock(): block is "
957 <<dtime_s<<" seconds old."<<std::endl;*/
959 // Activate stored objects
960 activateObjects(block, dtime_s);
962 /* Handle LoadingBlockModifiers */
963 m_lbm_mgr.applyLBMs(this, block, stamp);
966 std::vector<NodeTimer> elapsed_timers =
967 block->m_node_timers.step((float)dtime_s);
968 if (!elapsed_timers.empty()) {
970 for (const NodeTimer &elapsed_timer : elapsed_timers) {
971 n = block->getNodeNoEx(elapsed_timer.position);
972 v3s16 p = elapsed_timer.position + block->getPosRelative();
973 if (m_script->node_on_timer(p, n, elapsed_timer.elapsed))
974 block->setNodeTimer(NodeTimer(elapsed_timer.timeout, 0,
975 elapsed_timer.position));
980 void ServerEnvironment::addActiveBlockModifier(ActiveBlockModifier *abm)
982 m_abms.emplace_back(abm);
985 void ServerEnvironment::addLoadingBlockModifierDef(LoadingBlockModifierDef *lbm)
987 m_lbm_mgr.addLBMDef(lbm);
990 bool ServerEnvironment::setNode(v3s16 p, const MapNode &n)
992 const NodeDefManager *ndef = m_server->ndef();
993 MapNode n_old = m_map->getNode(p);
995 const ContentFeatures &cf_old = ndef->get(n_old);
998 if (cf_old.has_on_destruct)
999 m_script->node_on_destruct(p, n_old);
1002 if (!m_map->addNodeWithEvent(p, n))
1005 // Update active VoxelManipulator if a mapgen thread
1006 m_map->updateVManip(p);
1008 // Call post-destructor
1009 if (cf_old.has_after_destruct)
1010 m_script->node_after_destruct(p, n_old);
1012 // Retrieve node content features
1013 // if new node is same as old, reuse old definition to prevent a lookup
1014 const ContentFeatures &cf_new = n_old == n ? cf_old : ndef->get(n);
1017 if (cf_new.has_on_construct)
1018 m_script->node_on_construct(p, n);
1023 bool ServerEnvironment::removeNode(v3s16 p)
1025 const NodeDefManager *ndef = m_server->ndef();
1026 MapNode n_old = m_map->getNode(p);
1029 if (ndef->get(n_old).has_on_destruct)
1030 m_script->node_on_destruct(p, n_old);
1033 // This is slightly optimized compared to addNodeWithEvent(air)
1034 if (!m_map->removeNodeWithEvent(p))
1037 // Update active VoxelManipulator if a mapgen thread
1038 m_map->updateVManip(p);
1040 // Call post-destructor
1041 if (ndef->get(n_old).has_after_destruct)
1042 m_script->node_after_destruct(p, n_old);
1044 // Air doesn't require constructor
1048 bool ServerEnvironment::swapNode(v3s16 p, const MapNode &n)
1050 if (!m_map->addNodeWithEvent(p, n, false))
1053 // Update active VoxelManipulator if a mapgen thread
1054 m_map->updateVManip(p);
1059 void ServerEnvironment::clearObjects(ClearObjectsMode mode)
1061 infostream << "ServerEnvironment::clearObjects(): "
1062 << "Removing all active objects" << std::endl;
1063 auto cb_removal = [this] (ServerActiveObject *obj, u16 id) {
1064 if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER)
1067 // Delete static object if block is loaded
1068 deleteStaticFromBlock(obj, id, MOD_REASON_CLEAR_ALL_OBJECTS, true);
1070 // If known by some client, don't delete immediately
1071 if (obj->m_known_by_count > 0) {
1072 obj->m_pending_removal = true;
1076 // Tell the object about removal
1077 obj->removingFromEnvironment();
1078 // Deregister in scripting api
1079 m_script->removeObjectReference(obj);
1081 // Delete active object
1082 if (obj->environmentDeletes())
1088 m_ao_manager.clear(cb_removal);
1090 // Get list of loaded blocks
1091 std::vector<v3s16> loaded_blocks;
1092 infostream << "ServerEnvironment::clearObjects(): "
1093 << "Listing all loaded blocks" << std::endl;
1094 m_map->listAllLoadedBlocks(loaded_blocks);
1095 infostream << "ServerEnvironment::clearObjects(): "
1096 << "Done listing all loaded blocks: "
1097 << loaded_blocks.size()<<std::endl;
1099 // Get list of loadable blocks
1100 std::vector<v3s16> loadable_blocks;
1101 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1102 infostream << "ServerEnvironment::clearObjects(): "
1103 << "Listing all loadable blocks" << std::endl;
1104 m_map->listAllLoadableBlocks(loadable_blocks);
1105 infostream << "ServerEnvironment::clearObjects(): "
1106 << "Done listing all loadable blocks: "
1107 << loadable_blocks.size() << std::endl;
1109 loadable_blocks = loaded_blocks;
1112 actionstream << "ServerEnvironment::clearObjects(): "
1113 << "Now clearing objects in " << loadable_blocks.size()
1114 << " blocks" << std::endl;
1116 // Grab a reference on each loaded block to avoid unloading it
1117 for (v3s16 p : loaded_blocks) {
1118 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1119 assert(block != NULL);
1123 // Remove objects in all loadable blocks
1124 u32 unload_interval = U32_MAX;
1125 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1126 unload_interval = g_settings->getS32("max_clearobjects_extra_loaded_blocks");
1127 unload_interval = MYMAX(unload_interval, 1);
1129 u32 report_interval = loadable_blocks.size() / 10;
1130 u32 num_blocks_checked = 0;
1131 u32 num_blocks_cleared = 0;
1132 u32 num_objs_cleared = 0;
1133 for (auto i = loadable_blocks.begin();
1134 i != loadable_blocks.end(); ++i) {
1136 MapBlock *block = m_map->emergeBlock(p, false);
1138 errorstream << "ServerEnvironment::clearObjects(): "
1139 << "Failed to emerge block " << PP(p) << std::endl;
1142 u32 num_stored = block->m_static_objects.m_stored.size();
1143 u32 num_active = block->m_static_objects.m_active.size();
1144 if (num_stored != 0 || num_active != 0) {
1145 block->m_static_objects.m_stored.clear();
1146 block->m_static_objects.m_active.clear();
1147 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1148 MOD_REASON_CLEAR_ALL_OBJECTS);
1149 num_objs_cleared += num_stored + num_active;
1150 num_blocks_cleared++;
1152 num_blocks_checked++;
1154 if (report_interval != 0 &&
1155 num_blocks_checked % report_interval == 0) {
1156 float percent = 100.0 * (float)num_blocks_checked /
1157 loadable_blocks.size();
1158 actionstream << "ServerEnvironment::clearObjects(): "
1159 << "Cleared " << num_objs_cleared << " objects"
1160 << " in " << num_blocks_cleared << " blocks ("
1161 << percent << "%)" << std::endl;
1163 if (num_blocks_checked % unload_interval == 0) {
1164 m_map->unloadUnreferencedBlocks();
1167 m_map->unloadUnreferencedBlocks();
1169 // Drop references that were added above
1170 for (v3s16 p : loaded_blocks) {
1171 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1176 m_last_clear_objects_time = m_game_time;
1178 actionstream << "ServerEnvironment::clearObjects(): "
1179 << "Finished: Cleared " << num_objs_cleared << " objects"
1180 << " in " << num_blocks_cleared << " blocks" << std::endl;
1183 void ServerEnvironment::step(float dtime)
1185 ScopeProfiler sp2(g_profiler, "ServerEnv::step()", SPT_AVG);
1186 /* Step time of day */
1187 stepTimeOfDay(dtime);
1190 // NOTE: This is kind of funny on a singleplayer game, but doesn't
1191 // really matter that much.
1192 static thread_local const float server_step =
1193 g_settings->getFloat("dedicated_server_step");
1194 m_recommended_send_interval = server_step;
1200 m_game_time_fraction_counter += dtime;
1201 u32 inc_i = (u32)m_game_time_fraction_counter;
1202 m_game_time += inc_i;
1203 m_game_time_fraction_counter -= (float)inc_i;
1210 ScopeProfiler sp(g_profiler, "ServerEnv: move players", SPT_AVG);
1211 for (RemotePlayer *player : m_players) {
1212 // Ignore disconnected players
1213 if (player->getPeerId() == PEER_ID_INEXISTENT)
1217 player->move(dtime, this, 100 * BS);
1222 Manage active block list
1224 if (m_active_blocks_management_interval.step(dtime, m_cache_active_block_mgmt_interval)) {
1225 ScopeProfiler sp(g_profiler, "ServerEnv: update active blocks", SPT_AVG);
1227 Get player block positions
1229 std::vector<PlayerSAO*> players;
1230 for (RemotePlayer *player: m_players) {
1231 // Ignore disconnected players
1232 if (player->getPeerId() == PEER_ID_INEXISTENT)
1235 PlayerSAO *playersao = player->getPlayerSAO();
1238 players.push_back(playersao);
1242 Update list of active blocks, collecting changes
1244 // use active_object_send_range_blocks since that is max distance
1245 // for active objects sent the client anyway
1246 static thread_local const s16 active_object_range =
1247 g_settings->getS16("active_object_send_range_blocks");
1248 static thread_local const s16 active_block_range =
1249 g_settings->getS16("active_block_range");
1250 std::set<v3s16> blocks_removed;
1251 std::set<v3s16> blocks_added;
1252 m_active_blocks.update(players, active_block_range, active_object_range,
1253 blocks_removed, blocks_added);
1256 Handle removed blocks
1259 // Convert active objects that are no more in active blocks to static
1260 deactivateFarObjects(false);
1262 for (const v3s16 &p: blocks_removed) {
1263 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1267 // Set current time as timestamp (and let it set ChangedFlag)
1268 block->setTimestamp(m_game_time);
1275 for (const v3s16 &p: blocks_added) {
1276 MapBlock *block = m_map->getBlockOrEmerge(p);
1278 m_active_blocks.m_list.erase(p);
1279 m_active_blocks.m_abm_list.erase(p);
1283 activateBlock(block);
1288 Mess around in active blocks
1290 if (m_active_blocks_nodemetadata_interval.step(dtime, m_cache_nodetimer_interval)) {
1291 ScopeProfiler sp(g_profiler, "ServerEnv: Run node timers", SPT_AVG);
1293 float dtime = m_cache_nodetimer_interval;
1295 for (const v3s16 &p: m_active_blocks.m_list) {
1296 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1300 // Reset block usage timer
1301 block->resetUsageTimer();
1303 // Set current time as timestamp
1304 block->setTimestampNoChangedFlag(m_game_time);
1305 // If time has changed much from the one on disk,
1306 // set block to be saved when it is unloaded
1307 if(block->getTimestamp() > block->getDiskTimestamp() + 60)
1308 block->raiseModified(MOD_STATE_WRITE_AT_UNLOAD,
1309 MOD_REASON_BLOCK_EXPIRED);
1312 std::vector<NodeTimer> elapsed_timers = block->m_node_timers.step(dtime);
1313 if (!elapsed_timers.empty()) {
1316 for (const NodeTimer &elapsed_timer: elapsed_timers) {
1317 n = block->getNodeNoEx(elapsed_timer.position);
1318 p2 = elapsed_timer.position + block->getPosRelative();
1319 if (m_script->node_on_timer(p2, n, elapsed_timer.elapsed)) {
1320 block->setNodeTimer(NodeTimer(
1321 elapsed_timer.timeout, 0, elapsed_timer.position));
1328 if (m_active_block_modifier_interval.step(dtime, m_cache_abm_interval)) {
1329 ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg per interval", SPT_AVG);
1330 TimeTaker timer("modify in active blocks per interval");
1332 // Initialize handling of ActiveBlockModifiers
1333 ABMHandler abmhandler(m_abms, m_cache_abm_interval, this, true);
1335 int blocks_scanned = 0;
1337 int blocks_cached = 0;
1339 std::vector<v3s16> output(m_active_blocks.m_abm_list.size());
1341 // Shuffle the active blocks so that each block gets an equal chance
1342 // of having its ABMs run.
1343 std::copy(m_active_blocks.m_abm_list.begin(), m_active_blocks.m_abm_list.end(), output.begin());
1344 std::shuffle(output.begin(), output.end(), m_rgen);
1347 // The time budget for ABMs is 20%.
1348 u32 max_time_ms = m_cache_abm_interval * 1000 / 5;
1349 for (const v3s16 &p : output) {
1350 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1356 // Set current time as timestamp
1357 block->setTimestampNoChangedFlag(m_game_time);
1359 /* Handle ActiveBlockModifiers */
1360 abmhandler.apply(block, blocks_scanned, abms_run, blocks_cached);
1362 u32 time_ms = timer.getTimerTime();
1364 if (time_ms > max_time_ms) {
1365 warningstream << "active block modifiers took "
1366 << time_ms << "ms (processed " << i << " of "
1367 << output.size() << " active blocks)" << std::endl;
1371 g_profiler->avg("ServerEnv: active blocks", m_active_blocks.m_abm_list.size());
1372 g_profiler->avg("ServerEnv: active blocks cached", blocks_cached);
1373 g_profiler->avg("ServerEnv: active blocks scanned for ABMs", blocks_scanned);
1374 g_profiler->avg("ServerEnv: ABMs run", abms_run);
1380 Step script environment (run global on_step())
1382 m_script->environment_Step(dtime);
1388 ScopeProfiler sp(g_profiler, "ServerEnv: Run SAO::step()", SPT_AVG);
1390 // This helps the objects to send data at the same time
1391 bool send_recommended = false;
1392 m_send_recommended_timer += dtime;
1393 if (m_send_recommended_timer > getSendRecommendedInterval()) {
1394 m_send_recommended_timer -= getSendRecommendedInterval();
1395 send_recommended = true;
1398 auto cb_state = [this, dtime, send_recommended] (ServerActiveObject *obj) {
1403 obj->step(dtime, send_recommended);
1404 // Read messages from object
1405 obj->dumpAOMessagesToQueue(m_active_object_messages);
1407 m_ao_manager.step(dtime, cb_state);
1411 Manage active objects
1413 if (m_object_management_interval.step(dtime, 0.5)) {
1414 removeRemovedObjects();
1418 Manage particle spawner expiration
1420 if (m_particle_management_interval.step(dtime, 1.0)) {
1421 for (std::unordered_map<u32, float>::iterator i = m_particle_spawners.begin();
1422 i != m_particle_spawners.end(); ) {
1423 //non expiring spawners
1424 if (i->second == PARTICLE_SPAWNER_NO_EXPIRY) {
1430 if (i->second <= 0.f)
1431 m_particle_spawners.erase(i++);
1437 // Send outdated player inventories
1438 for (RemotePlayer *player : m_players) {
1439 if (player->getPeerId() == PEER_ID_INEXISTENT)
1442 PlayerSAO *sao = player->getPlayerSAO();
1443 if (sao && player->inventory.checkModified())
1444 m_server->SendInventory(sao, true);
1447 // Send outdated detached inventories
1448 m_server->sendDetachedInventories(PEER_ID_INEXISTENT, true);
1451 u32 ServerEnvironment::addParticleSpawner(float exptime)
1453 // Timers with lifetime 0 do not expire
1454 float time = exptime > 0.f ? exptime : PARTICLE_SPAWNER_NO_EXPIRY;
1457 for (;;) { // look for unused particlespawner id
1459 std::unordered_map<u32, float>::iterator f = m_particle_spawners.find(id);
1460 if (f == m_particle_spawners.end()) {
1461 m_particle_spawners[id] = time;
1468 u32 ServerEnvironment::addParticleSpawner(float exptime, u16 attached_id)
1470 u32 id = addParticleSpawner(exptime);
1471 m_particle_spawner_attachments[id] = attached_id;
1472 if (ServerActiveObject *obj = getActiveObject(attached_id)) {
1473 obj->attachParticleSpawner(id);
1478 void ServerEnvironment::deleteParticleSpawner(u32 id, bool remove_from_object)
1480 m_particle_spawners.erase(id);
1481 const auto &it = m_particle_spawner_attachments.find(id);
1482 if (it != m_particle_spawner_attachments.end()) {
1483 u16 obj_id = it->second;
1484 ServerActiveObject *sao = getActiveObject(obj_id);
1485 if (sao != NULL && remove_from_object) {
1486 sao->detachParticleSpawner(id);
1488 m_particle_spawner_attachments.erase(id);
1492 u16 ServerEnvironment::addActiveObject(ServerActiveObject *object)
1494 assert(object); // Pre-condition
1496 u16 id = addActiveObjectRaw(object, true, 0);
1501 Finds out what new objects have been added to
1502 inside a radius around a position
1504 void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius,
1506 std::set<u16> ¤t_objects,
1507 std::queue<u16> &added_objects)
1509 f32 radius_f = radius * BS;
1510 f32 player_radius_f = player_radius * BS;
1512 if (player_radius_f < 0.0f)
1513 player_radius_f = 0.0f;
1515 m_ao_manager.getAddedActiveObjectsAroundPos(playersao->getBasePosition(), radius_f,
1516 player_radius_f, current_objects, added_objects);
1520 Finds out what objects have been removed from
1521 inside a radius around a position
1523 void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius,
1525 std::set<u16> ¤t_objects,
1526 std::queue<u16> &removed_objects)
1528 f32 radius_f = radius * BS;
1529 f32 player_radius_f = player_radius * BS;
1531 if (player_radius_f < 0)
1532 player_radius_f = 0;
1534 Go through current_objects; object is removed if:
1535 - object is not found in m_active_objects (this is actually an
1536 error condition; objects should be removed only after all clients
1537 have been informed about removal), or
1538 - object is to be removed or deactivated, or
1539 - object is too far away
1541 for (u16 id : current_objects) {
1542 ServerActiveObject *object = getActiveObject(id);
1544 if (object == NULL) {
1545 infostream << "ServerEnvironment::getRemovedActiveObjects():"
1546 << " object in current_objects is NULL" << std::endl;
1547 removed_objects.push(id);
1551 if (object->isGone()) {
1552 removed_objects.push(id);
1556 f32 distance_f = object->getBasePosition().getDistanceFrom(playersao->getBasePosition());
1557 if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1558 if (distance_f <= player_radius_f || player_radius_f == 0)
1560 } else if (distance_f <= radius_f)
1563 // Object is no longer visible
1564 removed_objects.push(id);
1568 void ServerEnvironment::setStaticForActiveObjectsInBlock(
1569 v3s16 blockpos, bool static_exists, v3s16 static_block)
1571 MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos);
1575 for (auto &so_it : block->m_static_objects.m_active) {
1576 // Get the ServerActiveObject counterpart to this StaticObject
1577 ServerActiveObject *sao = m_ao_manager.getActiveObject(so_it.first);
1579 // If this ever happens, there must be some kind of nasty bug.
1580 errorstream << "ServerEnvironment::setStaticForObjectsInBlock(): "
1581 "Object from MapBlock::m_static_objects::m_active not found "
1582 "in m_active_objects";
1586 sao->m_static_exists = static_exists;
1587 sao->m_static_block = static_block;
1591 ActiveObjectMessage ServerEnvironment::getActiveObjectMessage()
1593 if(m_active_object_messages.empty())
1594 return ActiveObjectMessage(0);
1596 ActiveObjectMessage message = m_active_object_messages.front();
1597 m_active_object_messages.pop();
1601 void ServerEnvironment::getSelectedActiveObjects(
1602 const core::line3d<f32> &shootline_on_map,
1603 std::vector<PointedThing> &objects)
1605 std::vector<u16> objectIds;
1606 getObjectsInsideRadius(objectIds, shootline_on_map.start,
1607 shootline_on_map.getLength() + 10.0f);
1608 const v3f line_vector = shootline_on_map.getVector();
1610 for (u16 objectId : objectIds) {
1611 ServerActiveObject* obj = getActiveObject(objectId);
1613 aabb3f selection_box;
1614 if (!obj->getSelectionBox(&selection_box))
1617 v3f pos = obj->getBasePosition();
1619 aabb3f offsetted_box(selection_box.MinEdge + pos,
1620 selection_box.MaxEdge + pos);
1622 v3f current_intersection;
1623 v3s16 current_normal;
1624 if (boxLineCollision(offsetted_box, shootline_on_map.start, line_vector,
1625 ¤t_intersection, ¤t_normal)) {
1626 objects.emplace_back(
1627 (s16) objectId, current_intersection, current_normal,
1628 (current_intersection - shootline_on_map.start).getLengthSQ());
1634 ************ Private methods *************
1637 u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object,
1638 bool set_changed, u32 dtime_s)
1640 if (!m_ao_manager.registerObject(object)) {
1644 // Register reference in scripting api (must be done before post-init)
1645 m_script->addObjectReference(object);
1646 // Post-initialize object
1647 object->addedToEnvironment(dtime_s);
1649 // Add static data to block
1650 if (object->isStaticAllowed()) {
1651 // Add static object to active static list of the block
1652 v3f objectpos = object->getBasePosition();
1653 StaticObject s_obj(object, objectpos);
1654 // Add to the block where the object is located in
1655 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
1656 MapBlock *block = m_map->emergeBlock(blockpos);
1658 block->m_static_objects.m_active[object->getId()] = s_obj;
1659 object->m_static_exists = true;
1660 object->m_static_block = blockpos;
1663 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1664 MOD_REASON_ADD_ACTIVE_OBJECT_RAW);
1666 v3s16 p = floatToInt(objectpos, BS);
1667 errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
1668 <<"could not emerge block for storing id="<<object->getId()
1669 <<" statically (pos="<<PP(p)<<")"<<std::endl;
1673 return object->getId();
1677 Remove objects that satisfy (isGone() && m_known_by_count==0)
1679 void ServerEnvironment::removeRemovedObjects()
1681 ScopeProfiler sp(g_profiler, "ServerEnvironment::removeRemovedObjects()", SPT_AVG);
1683 auto clear_cb = [this] (ServerActiveObject *obj, u16 id) {
1684 // This shouldn't happen but check it
1686 errorstream << "ServerEnvironment::removeRemovedObjects(): "
1687 << "NULL object found. id=" << id << std::endl;
1692 We will handle objects marked for removal or deactivation
1698 Delete static data from block if removed
1700 if (obj->m_pending_removal)
1701 deleteStaticFromBlock(obj, id, MOD_REASON_REMOVE_OBJECTS_REMOVE, false);
1703 // If still known by clients, don't actually remove. On some future
1704 // invocation this will be 0, which is when removal will continue.
1705 if(obj->m_known_by_count > 0)
1709 Move static data from active to stored if deactivated
1711 if (!obj->m_pending_removal && obj->m_static_exists) {
1712 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
1714 const auto i = block->m_static_objects.m_active.find(id);
1715 if (i != block->m_static_objects.m_active.end()) {
1716 block->m_static_objects.m_stored.push_back(i->second);
1717 block->m_static_objects.m_active.erase(id);
1718 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1719 MOD_REASON_REMOVE_OBJECTS_DEACTIVATE);
1721 warningstream << "ServerEnvironment::removeRemovedObjects(): "
1722 << "id=" << id << " m_static_exists=true but "
1723 << "static data doesn't actually exist in "
1724 << PP(obj->m_static_block) << std::endl;
1727 infostream << "Failed to emerge block from which an object to "
1728 << "be deactivated was loaded from. id=" << id << std::endl;
1732 // Tell the object about removal
1733 obj->removingFromEnvironment();
1734 // Deregister in scripting api
1735 m_script->removeObjectReference(obj);
1738 if (obj->environmentDeletes())
1744 m_ao_manager.clear(clear_cb);
1747 static void print_hexdump(std::ostream &o, const std::string &data)
1749 const int linelength = 16;
1750 for(int l=0; ; l++){
1751 int i0 = linelength * l;
1752 bool at_end = false;
1753 int thislinelength = linelength;
1754 if(i0 + thislinelength > (int)data.size()){
1755 thislinelength = data.size() - i0;
1758 for(int di=0; di<linelength; di++){
1761 if(di<thislinelength)
1762 porting::mt_snprintf(buf, sizeof(buf), "%.2x ", data[i]);
1764 porting::mt_snprintf(buf, sizeof(buf), " ");
1768 for(int di=0; di<thislinelength; di++){
1782 Convert stored objects from blocks near the players to active.
1784 void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s)
1789 // Ignore if no stored objects (to not set changed flag)
1790 if(block->m_static_objects.m_stored.empty())
1793 verbosestream<<"ServerEnvironment::activateObjects(): "
1794 <<"activating objects of block "<<PP(block->getPos())
1795 <<" ("<<block->m_static_objects.m_stored.size()
1796 <<" objects)"<<std::endl;
1797 bool large_amount = (block->m_static_objects.m_stored.size() > g_settings->getU16("max_objects_per_block"));
1799 errorstream<<"suspiciously large amount of objects detected: "
1800 <<block->m_static_objects.m_stored.size()<<" in "
1801 <<PP(block->getPos())
1802 <<"; removing all of them."<<std::endl;
1803 // Clear stored list
1804 block->m_static_objects.m_stored.clear();
1805 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1806 MOD_REASON_TOO_MANY_OBJECTS);
1810 // Activate stored objects
1811 std::vector<StaticObject> new_stored;
1812 for (const StaticObject &s_obj : block->m_static_objects.m_stored) {
1813 // Create an active object from the data
1814 ServerActiveObject *obj = ServerActiveObject::create
1815 ((ActiveObjectType) s_obj.type, this, 0, s_obj.pos, s_obj.data);
1816 // If couldn't create object, store static data back.
1818 errorstream<<"ServerEnvironment::activateObjects(): "
1819 <<"failed to create active object from static object "
1820 <<"in block "<<PP(s_obj.pos/BS)
1821 <<" type="<<(int)s_obj.type<<" data:"<<std::endl;
1822 print_hexdump(verbosestream, s_obj.data);
1824 new_stored.push_back(s_obj);
1827 verbosestream<<"ServerEnvironment::activateObjects(): "
1828 <<"activated static object pos="<<PP(s_obj.pos/BS)
1829 <<" type="<<(int)s_obj.type<<std::endl;
1830 // This will also add the object to the active static list
1831 addActiveObjectRaw(obj, false, dtime_s);
1834 // Clear stored list
1835 block->m_static_objects.m_stored.clear();
1836 // Add leftover failed stuff to stored list
1837 for (const StaticObject &s_obj : new_stored) {
1838 block->m_static_objects.m_stored.push_back(s_obj);
1842 Note: Block hasn't really been modified here.
1843 The objects have just been activated and moved from the stored
1844 static list to the active static list.
1845 As such, the block is essentially the same.
1846 Thus, do not call block->raiseModified(MOD_STATE_WRITE_NEEDED).
1847 Otherwise there would be a huge amount of unnecessary I/O.
1852 Convert objects that are not standing inside active blocks to static.
1854 If m_known_by_count != 0, active object is not deleted, but static
1855 data is still updated.
1857 If force_delete is set, active object is deleted nevertheless. It
1858 shall only be set so in the destructor of the environment.
1860 If block wasn't generated (not in memory or on disk),
1862 void ServerEnvironment::deactivateFarObjects(bool _force_delete)
1864 auto cb_deactivate = [this, _force_delete] (ServerActiveObject *obj, u16 id) {
1865 // force_delete might be overriden per object
1866 bool force_delete = _force_delete;
1868 // Do not deactivate if static data creation not allowed
1869 if (!force_delete && !obj->isStaticAllowed())
1872 // removeRemovedObjects() is responsible for these
1873 if (!force_delete && obj->isGone())
1876 const v3f &objectpos = obj->getBasePosition();
1878 // The block in which the object resides in
1879 v3s16 blockpos_o = getNodeBlockPos(floatToInt(objectpos, BS));
1881 // If object's static data is stored in a deactivated block and object
1882 // is actually located in an active block, re-save to the block in
1883 // which the object is actually located in.
1884 if (!force_delete && obj->m_static_exists &&
1885 !m_active_blocks.contains(obj->m_static_block) &&
1886 m_active_blocks.contains(blockpos_o)) {
1887 // Delete from block where object was located
1888 deleteStaticFromBlock(obj, id, MOD_REASON_STATIC_DATA_REMOVED, false);
1890 StaticObject s_obj(obj, objectpos);
1891 // Save to block where object is located
1892 saveStaticToBlock(blockpos_o, id, obj, s_obj, MOD_REASON_STATIC_DATA_ADDED);
1897 // If block is still active, don't remove
1898 if (!force_delete && m_active_blocks.contains(blockpos_o))
1901 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1902 << "deactivating object id=" << id << " on inactive block "
1903 << PP(blockpos_o) << std::endl;
1905 // If known by some client, don't immediately delete.
1906 bool pending_delete = (obj->m_known_by_count > 0 && !force_delete);
1909 Update the static data
1911 if (obj->isStaticAllowed()) {
1912 // Create new static object
1913 StaticObject s_obj(obj, objectpos);
1915 bool stays_in_same_block = false;
1916 bool data_changed = true;
1918 // Check if static data has changed considerably
1919 if (obj->m_static_exists) {
1920 if (obj->m_static_block == blockpos_o)
1921 stays_in_same_block = true;
1923 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
1926 const auto n = block->m_static_objects.m_active.find(id);
1927 if (n != block->m_static_objects.m_active.end()) {
1928 StaticObject static_old = n->second;
1930 float save_movem = obj->getMinimumSavedMovement();
1932 if (static_old.data == s_obj.data &&
1933 (static_old.pos - objectpos).getLength() < save_movem)
1934 data_changed = false;
1936 warningstream << "ServerEnvironment::deactivateFarObjects(): "
1937 << "id=" << id << " m_static_exists=true but "
1938 << "static data doesn't actually exist in "
1939 << PP(obj->m_static_block) << std::endl;
1945 While changes are always saved, blocks are only marked as modified
1946 if the object has moved or different staticdata. (see above)
1948 bool shall_be_written = (!stays_in_same_block || data_changed);
1949 u32 reason = shall_be_written ? MOD_REASON_STATIC_DATA_CHANGED : MOD_REASON_UNKNOWN;
1951 // Delete old static object
1952 deleteStaticFromBlock(obj, id, reason, false);
1954 // Add to the block where the object is located in
1955 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
1956 u16 store_id = pending_delete ? id : 0;
1957 if (!saveStaticToBlock(blockpos, store_id, obj, s_obj, reason))
1958 force_delete = true;
1962 If known by some client, set pending deactivation.
1963 Otherwise delete it immediately.
1965 if (pending_delete && !force_delete) {
1966 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1967 << "object id=" << id << " is known by clients"
1968 << "; not deleting yet" << std::endl;
1970 obj->m_pending_deactivation = true;
1974 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1975 << "object id=" << id << " is not known by clients"
1976 << "; deleting" << std::endl;
1978 // Tell the object about removal
1979 obj->removingFromEnvironment();
1980 // Deregister in scripting api
1981 m_script->removeObjectReference(obj);
1983 // Delete active object
1984 if (obj->environmentDeletes())
1990 m_ao_manager.clear(cb_deactivate);
1993 void ServerEnvironment::deleteStaticFromBlock(
1994 ServerActiveObject *obj, u16 id, u32 mod_reason, bool no_emerge)
1996 if (!obj->m_static_exists)
2001 block = m_map->getBlockNoCreateNoEx(obj->m_static_block);
2003 block = m_map->emergeBlock(obj->m_static_block, false);
2006 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2007 << " when deleting static data of object from it. id=" << id << std::endl;
2011 block->m_static_objects.remove(id);
2012 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2013 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2015 obj->m_static_exists = false;
2018 bool ServerEnvironment::saveStaticToBlock(
2019 v3s16 blockpos, u16 store_id,
2020 ServerActiveObject *obj, const StaticObject &s_obj,
2023 MapBlock *block = nullptr;
2025 block = m_map->emergeBlock(blockpos);
2026 } catch (InvalidPositionException &e) {
2027 // Handled via NULL pointer
2028 // NOTE: emergeBlock's failure is usually determined by it
2029 // actually returning NULL
2033 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2034 << " when saving static data of object to it. id=" << store_id << std::endl;
2037 if (block->m_static_objects.m_stored.size() >= g_settings->getU16("max_objects_per_block")) {
2038 warningstream << "ServerEnv: Trying to store id = " << store_id
2039 << " statically but block " << PP(blockpos)
2040 << " already contains "
2041 << block->m_static_objects.m_stored.size()
2042 << " objects." << std::endl;
2046 block->m_static_objects.insert(store_id, s_obj);
2047 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2048 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2050 obj->m_static_exists = true;
2051 obj->m_static_block = blockpos;
2056 PlayerDatabase *ServerEnvironment::openPlayerDatabase(const std::string &name,
2057 const std::string &savedir, const Settings &conf)
2060 if (name == "sqlite3")
2061 return new PlayerDatabaseSQLite3(savedir);
2063 if (name == "dummy")
2064 return new Database_Dummy();
2066 if (name == "postgresql") {
2067 std::string connect_string;
2068 conf.getNoEx("pgsql_player_connection", connect_string);
2069 return new PlayerDatabasePostgreSQL(connect_string);
2072 if (name == "files")
2073 return new PlayerDatabaseFiles(savedir + DIR_DELIM + "players");
2075 throw BaseException(std::string("Database backend ") + name + " not supported.");
2078 bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params,
2079 const Settings &cmd_args)
2081 std::string migrate_to = cmd_args.get("migrate-players");
2083 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2084 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2085 errorstream << "Cannot read world.mt!" << std::endl;
2089 if (!world_mt.exists("player_backend")) {
2090 errorstream << "Please specify your current backend in world.mt:"
2092 << " player_backend = {files|sqlite3|postgresql}"
2097 std::string backend = world_mt.get("player_backend");
2098 if (backend == migrate_to) {
2099 errorstream << "Cannot migrate: new backend is same"
2100 << " as the old one" << std::endl;
2104 const std::string players_backup_path = game_params.world_path + DIR_DELIM
2107 if (backend == "files") {
2108 // Create backup directory
2109 fs::CreateDir(players_backup_path);
2113 PlayerDatabase *srcdb = ServerEnvironment::openPlayerDatabase(backend,
2114 game_params.world_path, world_mt);
2115 PlayerDatabase *dstdb = ServerEnvironment::openPlayerDatabase(migrate_to,
2116 game_params.world_path, world_mt);
2118 std::vector<std::string> player_list;
2119 srcdb->listPlayers(player_list);
2120 for (std::vector<std::string>::const_iterator it = player_list.begin();
2121 it != player_list.end(); ++it) {
2122 actionstream << "Migrating player " << it->c_str() << std::endl;
2123 RemotePlayer player(it->c_str(), NULL);
2124 PlayerSAO playerSAO(NULL, &player, 15000, false);
2126 srcdb->loadPlayer(&player, &playerSAO);
2128 playerSAO.finalize(&player, std::set<std::string>());
2129 player.setPlayerSAO(&playerSAO);
2131 dstdb->savePlayer(&player);
2133 // For files source, move player files to backup dir
2134 if (backend == "files") {
2136 game_params.world_path + DIR_DELIM + "players" + DIR_DELIM + (*it),
2137 players_backup_path + DIR_DELIM + (*it));
2141 actionstream << "Successfully migrated " << player_list.size() << " players"
2143 world_mt.set("player_backend", migrate_to);
2144 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2145 errorstream << "Failed to update world.mt!" << std::endl;
2147 actionstream << "world.mt updated" << std::endl;
2149 // When migration is finished from file backend, remove players directory if empty
2150 if (backend == "files") {
2151 fs::DeleteSingleFileOrEmptyDirectory(game_params.world_path + DIR_DELIM
2158 } catch (BaseException &e) {
2159 errorstream << "An error occurred during migration: " << e.what() << std::endl;
2165 AuthDatabase *ServerEnvironment::openAuthDatabase(
2166 const std::string &name, const std::string &savedir, const Settings &conf)
2168 if (name == "sqlite3")
2169 return new AuthDatabaseSQLite3(savedir);
2171 if (name == "files")
2172 return new AuthDatabaseFiles(savedir);
2174 throw BaseException(std::string("Database backend ") + name + " not supported.");
2177 bool ServerEnvironment::migrateAuthDatabase(
2178 const GameParams &game_params, const Settings &cmd_args)
2180 std::string migrate_to = cmd_args.get("migrate-auth");
2182 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2183 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2184 errorstream << "Cannot read world.mt!" << std::endl;
2188 std::string backend = "files";
2189 if (world_mt.exists("auth_backend"))
2190 backend = world_mt.get("auth_backend");
2192 warningstream << "No auth_backend found in world.mt, "
2193 "assuming \"files\"." << std::endl;
2195 if (backend == migrate_to) {
2196 errorstream << "Cannot migrate: new backend is same"
2197 << " as the old one" << std::endl;
2202 const std::unique_ptr<AuthDatabase> srcdb(ServerEnvironment::openAuthDatabase(
2203 backend, game_params.world_path, world_mt));
2204 const std::unique_ptr<AuthDatabase> dstdb(ServerEnvironment::openAuthDatabase(
2205 migrate_to, game_params.world_path, world_mt));
2207 std::vector<std::string> names_list;
2208 srcdb->listNames(names_list);
2209 for (const std::string &name : names_list) {
2210 actionstream << "Migrating auth entry for " << name << std::endl;
2212 AuthEntry authEntry;
2213 success = srcdb->getAuth(name, authEntry);
2214 success = success && dstdb->createAuth(authEntry);
2216 errorstream << "Failed to migrate " << name << std::endl;
2219 actionstream << "Successfully migrated " << names_list.size()
2220 << " auth entries" << std::endl;
2221 world_mt.set("auth_backend", migrate_to);
2222 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2223 errorstream << "Failed to update world.mt!" << std::endl;
2225 actionstream << "world.mt updated" << std::endl;
2227 if (backend == "files") {
2228 // special-case files migration:
2229 // move auth.txt to auth.txt.bak if possible
2230 std::string auth_txt_path =
2231 game_params.world_path + DIR_DELIM + "auth.txt";
2232 std::string auth_bak_path = auth_txt_path + ".bak";
2233 if (!fs::PathExists(auth_bak_path))
2234 if (fs::Rename(auth_txt_path, auth_bak_path))
2235 actionstream << "Renamed auth.txt to auth.txt.bak"
2238 errorstream << "Could not rename auth.txt to "
2239 "auth.txt.bak" << std::endl;
2241 warningstream << "auth.txt.bak already exists, auth.txt "
2242 "not renamed" << std::endl;
2245 } catch (BaseException &e) {
2246 errorstream << "An error occurred during migration: " << e.what()