3 Copyright (C) 2010-2017 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "serverenvironment.h"
26 #include "nodemetadata.h"
32 #include "remoteplayer.h"
33 #include "scripting_server.h"
35 #include "util/serialize.h"
36 #include "util/basic_macros.h"
37 #include "util/pointedthing.h"
38 #include "threading/mutex_auto_lock.h"
40 #include "gameparams.h"
41 #include "database/database-dummy.h"
42 #include "database/database-files.h"
43 #include "database/database-sqlite3.h"
45 #include "database/database-postgresql.h"
48 #include "database/database-leveldb.h"
50 #include "server/luaentity_sao.h"
51 #include "server/player_sao.h"
53 #define LBM_NAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_:"
55 // A number that is much smaller than the timeout for particle spawners should/could ever be
56 #define PARTICLE_SPAWNER_NO_EXPIRY -1024.f
62 ABMWithState::ABMWithState(ActiveBlockModifier *abm_):
65 // Initialize timer to random value to spread processing
66 float itv = abm->getTriggerInterval();
67 itv = MYMAX(0.001, itv); // No less than 1ms
68 int minval = MYMAX(-0.51*itv, -60); // Clamp to
69 int maxval = MYMIN(0.51*itv, 60); // +-60 seconds
70 timer = myrand_range(minval, maxval);
77 void LBMContentMapping::deleteContents()
79 for (auto &it : lbm_list) {
84 void LBMContentMapping::addLBM(LoadingBlockModifierDef *lbm_def, IGameDef *gamedef)
86 // Add the lbm_def to the LBMContentMapping.
87 // Unknown names get added to the global NameIdMapping.
88 const NodeDefManager *nodedef = gamedef->ndef();
90 lbm_list.push_back(lbm_def);
92 for (const std::string &nodeTrigger: lbm_def->trigger_contents) {
93 std::vector<content_t> c_ids;
94 bool found = nodedef->getIds(nodeTrigger, c_ids);
96 content_t c_id = gamedef->allocateUnknownNodeId(nodeTrigger);
97 if (c_id == CONTENT_IGNORE) {
98 // Seems it can't be allocated.
99 warningstream << "Could not internalize node name \"" << nodeTrigger
100 << "\" while loading LBM \"" << lbm_def->name << "\"." << std::endl;
103 c_ids.push_back(c_id);
106 for (content_t c_id : c_ids) {
107 map[c_id].push_back(lbm_def);
112 const std::vector<LoadingBlockModifierDef *> *
113 LBMContentMapping::lookup(content_t c) const
115 lbm_map::const_iterator it = map.find(c);
118 // This first dereferences the iterator, returning
119 // a std::vector<LoadingBlockModifierDef *>
120 // reference, then we convert it to a pointer.
121 return &(it->second);
124 LBMManager::~LBMManager()
126 for (auto &m_lbm_def : m_lbm_defs) {
127 delete m_lbm_def.second;
130 for (auto &it : m_lbm_lookup) {
131 (it.second).deleteContents();
135 void LBMManager::addLBMDef(LoadingBlockModifierDef *lbm_def)
137 // Precondition, in query mode the map isn't used anymore
138 FATAL_ERROR_IF(m_query_mode,
139 "attempted to modify LBMManager in query mode");
141 if (!string_allowed(lbm_def->name, LBM_NAME_ALLOWED_CHARS)) {
142 throw ModError("Error adding LBM \"" + lbm_def->name +
143 "\": Does not follow naming conventions: "
144 "Only characters [a-z0-9_:] are allowed.");
147 m_lbm_defs[lbm_def->name] = lbm_def;
150 void LBMManager::loadIntroductionTimes(const std::string ×,
151 IGameDef *gamedef, u32 now)
156 // Storing it in a map first instead of
157 // handling the stuff directly in the loop
158 // removes all duplicate entries.
159 // TODO make this std::unordered_map
160 std::map<std::string, u32> introduction_times;
163 The introduction times string consists of name~time entries,
164 with each entry terminated by a semicolon. The time is decimal.
169 while ((idx_new = times.find(';', idx)) != std::string::npos) {
170 std::string entry = times.substr(idx, idx_new - idx);
171 std::vector<std::string> components = str_split(entry, '~');
172 if (components.size() != 2)
173 throw SerializationError("Introduction times entry \""
174 + entry + "\" requires exactly one '~'!");
175 const std::string &name = components[0];
176 u32 time = from_string<u32>(components[1]);
177 introduction_times[name] = time;
181 // Put stuff from introduction_times into m_lbm_lookup
182 for (std::map<std::string, u32>::const_iterator it = introduction_times.begin();
183 it != introduction_times.end(); ++it) {
184 const std::string &name = it->first;
185 u32 time = it->second;
187 std::map<std::string, LoadingBlockModifierDef *>::iterator def_it =
188 m_lbm_defs.find(name);
189 if (def_it == m_lbm_defs.end()) {
190 // This seems to be an LBM entry for
191 // an LBM we haven't loaded. Discard it.
194 LoadingBlockModifierDef *lbm_def = def_it->second;
195 if (lbm_def->run_at_every_load) {
196 // This seems to be an LBM entry for
197 // an LBM that runs at every load.
198 // Don't add it just yet.
202 m_lbm_lookup[time].addLBM(lbm_def, gamedef);
204 // Erase the entry so that we know later
205 // what elements didn't get put into m_lbm_lookup
206 m_lbm_defs.erase(name);
209 // Now also add the elements from m_lbm_defs to m_lbm_lookup
210 // that weren't added in the previous step.
211 // They are introduced first time to this world,
212 // or are run at every load (introducement time hardcoded to U32_MAX).
214 LBMContentMapping &lbms_we_introduce_now = m_lbm_lookup[now];
215 LBMContentMapping &lbms_running_always = m_lbm_lookup[U32_MAX];
217 for (auto &m_lbm_def : m_lbm_defs) {
218 if (m_lbm_def.second->run_at_every_load) {
219 lbms_running_always.addLBM(m_lbm_def.second, gamedef);
221 lbms_we_introduce_now.addLBM(m_lbm_def.second, gamedef);
225 // Clear the list, so that we don't delete remaining elements
226 // twice in the destructor
230 std::string LBMManager::createIntroductionTimesString()
232 // Precondition, we must be in query mode
233 FATAL_ERROR_IF(!m_query_mode,
234 "attempted to query on non fully set up LBMManager");
236 std::ostringstream oss;
237 for (const auto &it : m_lbm_lookup) {
239 const std::vector<LoadingBlockModifierDef *> &lbm_list = it.second.lbm_list;
240 for (const auto &lbm_def : lbm_list) {
241 // Don't add if the LBM runs at every load,
242 // then introducement time is hardcoded
243 // and doesn't need to be stored
244 if (lbm_def->run_at_every_load)
246 oss << lbm_def->name << "~" << time << ";";
252 void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, u32 stamp)
254 // Precondition, we need m_lbm_lookup to be initialized
255 FATAL_ERROR_IF(!m_query_mode,
256 "attempted to query on non fully set up LBMManager");
257 v3s16 pos_of_block = block->getPosRelative();
261 lbm_lookup_map::const_iterator it = getLBMsIntroducedAfter(stamp);
262 for (; it != m_lbm_lookup.end(); ++it) {
263 // Cache previous version to speedup lookup which has a very high performance
264 // penalty on each call
265 content_t previous_c{};
266 std::vector<LoadingBlockModifierDef *> *lbm_list = nullptr;
268 for (pos.X = 0; pos.X < MAP_BLOCKSIZE; pos.X++)
269 for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++)
270 for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++) {
271 n = block->getNodeNoEx(pos);
274 // If content_t are not matching perform an LBM lookup
275 if (previous_c != c) {
276 lbm_list = (std::vector<LoadingBlockModifierDef *> *)
277 it->second.lookup(c);
283 for (auto lbmdef : *lbm_list) {
284 lbmdef->trigger(env, pos + pos_of_block, n);
294 void fillRadiusBlock(v3s16 p0, s16 r, std::set<v3s16> &list)
297 for(p.X=p0.X-r; p.X<=p0.X+r; p.X++)
298 for(p.Y=p0.Y-r; p.Y<=p0.Y+r; p.Y++)
299 for(p.Z=p0.Z-r; p.Z<=p0.Z+r; p.Z++)
302 if (p.getDistanceFrom(p0) <= r) {
309 void fillViewConeBlock(v3s16 p0,
311 const v3f camera_pos,
312 const v3f camera_dir,
313 const float camera_fov,
314 std::set<v3s16> &list)
317 const s16 r_nodes = r * BS * MAP_BLOCKSIZE;
318 for (p.X = p0.X - r; p.X <= p0.X+r; p.X++)
319 for (p.Y = p0.Y - r; p.Y <= p0.Y+r; p.Y++)
320 for (p.Z = p0.Z - r; p.Z <= p0.Z+r; p.Z++) {
321 if (isBlockInSight(p, camera_pos, camera_dir, camera_fov, r_nodes)) {
327 void ActiveBlockList::update(std::vector<PlayerSAO*> &active_players,
328 s16 active_block_range,
329 s16 active_object_range,
330 std::set<v3s16> &blocks_removed,
331 std::set<v3s16> &blocks_added)
336 std::set<v3s16> newlist = m_forceloaded_list;
337 m_abm_list = m_forceloaded_list;
338 for (const PlayerSAO *playersao : active_players) {
339 v3s16 pos = getNodeBlockPos(floatToInt(playersao->getBasePosition(), BS));
340 fillRadiusBlock(pos, active_block_range, m_abm_list);
341 fillRadiusBlock(pos, active_block_range, newlist);
343 s16 player_ao_range = std::min(active_object_range, playersao->getWantedRange());
344 // only do this if this would add blocks
345 if (player_ao_range > active_block_range) {
346 v3f camera_dir = v3f(0,0,1);
347 camera_dir.rotateYZBy(playersao->getLookPitch());
348 camera_dir.rotateXZBy(playersao->getRotation().Y);
349 fillViewConeBlock(pos,
351 playersao->getEyePosition(),
359 Find out which blocks on the old list are not on the new list
361 // Go through old list
362 for (v3s16 p : m_list) {
363 // If not on new list, it's been removed
364 if (newlist.find(p) == newlist.end())
365 blocks_removed.insert(p);
369 Find out which blocks on the new list are not on the old list
371 // Go through new list
372 for (v3s16 p : newlist) {
373 // If not on old list, it's been added
374 if(m_list.find(p) == m_list.end())
375 blocks_added.insert(p);
382 for (v3s16 p : newlist) {
391 // Random device to seed pseudo random generators.
392 static std::random_device seed;
394 ServerEnvironment::ServerEnvironment(ServerMap *map,
395 ServerScripting *scriptIface, Server *server,
396 const std::string &path_world):
399 m_script(scriptIface),
401 m_path_world(path_world),
404 // Determine which database backend to use
405 std::string conf_path = path_world + DIR_DELIM + "world.mt";
408 std::string player_backend_name = "sqlite3";
409 std::string auth_backend_name = "sqlite3";
411 bool succeeded = conf.readConfigFile(conf_path.c_str());
413 // If we open world.mt read the backend configurations.
415 // Read those values before setting defaults
416 bool player_backend_exists = conf.exists("player_backend");
417 bool auth_backend_exists = conf.exists("auth_backend");
419 // player backend is not set, assume it's legacy file backend.
420 if (!player_backend_exists) {
421 // fall back to files
422 conf.set("player_backend", "files");
423 player_backend_name = "files";
425 if (!conf.updateConfigFile(conf_path.c_str())) {
426 errorstream << "ServerEnvironment::ServerEnvironment(): "
427 << "Failed to update world.mt!" << std::endl;
430 conf.getNoEx("player_backend", player_backend_name);
433 // auth backend is not set, assume it's legacy file backend.
434 if (!auth_backend_exists) {
435 conf.set("auth_backend", "files");
436 auth_backend_name = "files";
438 if (!conf.updateConfigFile(conf_path.c_str())) {
439 errorstream << "ServerEnvironment::ServerEnvironment(): "
440 << "Failed to update world.mt!" << std::endl;
443 conf.getNoEx("auth_backend", auth_backend_name);
447 if (player_backend_name == "files") {
448 warningstream << "/!\\ You are using old player file backend. "
449 << "This backend is deprecated and will be removed in a future release /!\\"
450 << std::endl << "Switching to SQLite3 or PostgreSQL is advised, "
451 << "please read http://wiki.minetest.net/Database_backends." << std::endl;
454 if (auth_backend_name == "files") {
455 warningstream << "/!\\ You are using old auth file backend. "
456 << "This backend is deprecated and will be removed in a future release /!\\"
457 << std::endl << "Switching to SQLite3 is advised, "
458 << "please read http://wiki.minetest.net/Database_backends." << std::endl;
461 m_player_database = openPlayerDatabase(player_backend_name, path_world, conf);
462 m_auth_database = openAuthDatabase(auth_backend_name, path_world, conf);
465 ServerEnvironment::~ServerEnvironment()
467 // Clear active block list.
468 // This makes the next one delete all active objects.
469 m_active_blocks.clear();
471 // Convert all objects to static and delete the active objects
472 deactivateFarObjects(true);
477 // Delete ActiveBlockModifiers
478 for (ABMWithState &m_abm : m_abms) {
482 // Deallocate players
483 for (RemotePlayer *m_player : m_players) {
487 delete m_player_database;
488 delete m_auth_database;
491 Map & ServerEnvironment::getMap()
496 ServerMap & ServerEnvironment::getServerMap()
501 RemotePlayer *ServerEnvironment::getPlayer(const session_t peer_id)
503 for (RemotePlayer *player : m_players) {
504 if (player->getPeerId() == peer_id)
510 RemotePlayer *ServerEnvironment::getPlayer(const char* name)
512 for (RemotePlayer *player : m_players) {
513 if (strcmp(player->getName(), name) == 0)
519 void ServerEnvironment::addPlayer(RemotePlayer *player)
522 Check that peer_ids are unique.
523 Also check that names are unique.
524 Exception: there can be multiple players with peer_id=0
526 // If peer id is non-zero, it has to be unique.
527 if (player->getPeerId() != PEER_ID_INEXISTENT)
528 FATAL_ERROR_IF(getPlayer(player->getPeerId()) != NULL, "Peer id not unique");
529 // Name has to be unique.
530 FATAL_ERROR_IF(getPlayer(player->getName()) != NULL, "Player name not unique");
532 m_players.push_back(player);
535 void ServerEnvironment::removePlayer(RemotePlayer *player)
537 for (std::vector<RemotePlayer *>::iterator it = m_players.begin();
538 it != m_players.end(); ++it) {
539 if ((*it) == player) {
547 bool ServerEnvironment::removePlayerFromDatabase(const std::string &name)
549 return m_player_database->removePlayer(name);
552 void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason,
553 const std::string &str_reason, bool reconnect)
555 for (RemotePlayer *player : m_players) {
556 m_server->DenyAccessVerCompliant(player->getPeerId(),
557 player->protocol_version, reason, str_reason, reconnect);
561 void ServerEnvironment::saveLoadedPlayers(bool force)
563 for (RemotePlayer *player : m_players) {
564 if (force || player->checkModified() || (player->getPlayerSAO() &&
565 player->getPlayerSAO()->getMeta().isModified())) {
567 m_player_database->savePlayer(player);
568 } catch (DatabaseException &e) {
569 errorstream << "Failed to save player " << player->getName() << " exception: "
570 << e.what() << std::endl;
577 void ServerEnvironment::savePlayer(RemotePlayer *player)
580 m_player_database->savePlayer(player);
581 } catch (DatabaseException &e) {
582 errorstream << "Failed to save player " << player->getName() << " exception: "
583 << e.what() << std::endl;
588 PlayerSAO *ServerEnvironment::loadPlayer(RemotePlayer *player, bool *new_player,
589 session_t peer_id, bool is_singleplayer)
591 PlayerSAO *playersao = new PlayerSAO(this, player, peer_id, is_singleplayer);
592 // Create player if it doesn't exist
593 if (!m_player_database->loadPlayer(player, playersao)) {
595 // Set player position
596 infostream << "Server: Finding spawn place for player \""
597 << player->getName() << "\"" << std::endl;
598 playersao->setBasePosition(m_server->findSpawnPos());
600 // Make sure the player is saved
601 player->setModified(true);
603 // If the player exists, ensure that they respawn inside legal bounds
604 // This fixes an assert crash when the player can't be added
605 // to the environment
606 if (objectpos_over_limit(playersao->getBasePosition())) {
607 actionstream << "Respawn position for player \""
608 << player->getName() << "\" outside limits, resetting" << std::endl;
609 playersao->setBasePosition(m_server->findSpawnPos());
613 // Add player to environment
616 /* Clean up old HUD elements from previous sessions */
619 /* Add object to environment */
620 addActiveObject(playersao);
625 void ServerEnvironment::saveMeta()
627 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
629 // Open file and serialize
630 std::ostringstream ss(std::ios_base::binary);
633 args.setU64("game_time", m_game_time);
634 args.setU64("time_of_day", getTimeOfDay());
635 args.setU64("last_clear_objects_time", m_last_clear_objects_time);
636 args.setU64("lbm_introduction_times_version", 1);
637 args.set("lbm_introduction_times",
638 m_lbm_mgr.createIntroductionTimesString());
639 args.setU64("day_count", m_day_count);
643 if(!fs::safeWriteToFile(path, ss.str()))
645 infostream<<"ServerEnvironment::saveMeta(): Failed to write "
647 throw SerializationError("Couldn't save env meta");
651 void ServerEnvironment::loadMeta()
653 // If file doesn't exist, load default environment metadata
654 if (!fs::PathExists(m_path_world + DIR_DELIM "env_meta.txt")) {
655 infostream << "ServerEnvironment: Loading default environment metadata"
661 infostream << "ServerEnvironment: Loading environment metadata" << std::endl;
663 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
665 // Open file and deserialize
666 std::ifstream is(path.c_str(), std::ios_base::binary);
668 infostream << "ServerEnvironment::loadMeta(): Failed to open "
669 << path << std::endl;
670 throw SerializationError("Couldn't load env meta");
675 if (!args.parseConfigLines(is, "EnvArgsEnd")) {
676 throw SerializationError("ServerEnvironment::loadMeta(): "
677 "EnvArgsEnd not found!");
681 m_game_time = args.getU64("game_time");
682 } catch (SettingNotFoundException &e) {
683 // Getting this is crucial, otherwise timestamps are useless
684 throw SerializationError("Couldn't load env meta game_time");
687 setTimeOfDay(args.exists("time_of_day") ?
688 // set day to early morning by default
689 args.getU64("time_of_day") : 5250);
691 m_last_clear_objects_time = args.exists("last_clear_objects_time") ?
692 // If missing, do as if clearObjects was never called
693 args.getU64("last_clear_objects_time") : 0;
695 std::string lbm_introduction_times;
697 u64 ver = args.getU64("lbm_introduction_times_version");
699 lbm_introduction_times = args.get("lbm_introduction_times");
701 infostream << "ServerEnvironment::loadMeta(): Non-supported"
702 << " introduction time version " << ver << std::endl;
704 } catch (SettingNotFoundException &e) {
705 // No problem, this is expected. Just continue with an empty string
707 m_lbm_mgr.loadIntroductionTimes(lbm_introduction_times, m_server, m_game_time);
709 m_day_count = args.exists("day_count") ?
710 args.getU64("day_count") : 0;
714 * called if env_meta.txt doesn't exist (e.g. new world)
716 void ServerEnvironment::loadDefaultMeta()
718 m_lbm_mgr.loadIntroductionTimes("", m_server, m_game_time);
723 ActiveBlockModifier *abm;
725 std::vector<content_t> required_neighbors;
726 bool check_required_neighbors; // false if required_neighbors is known to be empty
732 ServerEnvironment *m_env;
733 std::vector<std::vector<ActiveABM> *> m_aabms;
735 ABMHandler(std::vector<ABMWithState> &abms,
736 float dtime_s, ServerEnvironment *env,
742 const NodeDefManager *ndef = env->getGameDef()->ndef();
743 for (ABMWithState &abmws : abms) {
744 ActiveBlockModifier *abm = abmws.abm;
745 float trigger_interval = abm->getTriggerInterval();
746 if(trigger_interval < 0.001)
747 trigger_interval = 0.001;
748 float actual_interval = dtime_s;
750 abmws.timer += dtime_s;
751 if(abmws.timer < trigger_interval)
753 abmws.timer -= trigger_interval;
754 actual_interval = trigger_interval;
756 float chance = abm->getTriggerChance();
761 if (abm->getSimpleCatchUp()) {
762 float intervals = actual_interval / trigger_interval;
765 aabm.chance = chance / intervals;
769 aabm.chance = chance;
773 const std::vector<std::string> &required_neighbors_s =
774 abm->getRequiredNeighbors();
775 for (const std::string &required_neighbor_s : required_neighbors_s) {
776 ndef->getIds(required_neighbor_s, aabm.required_neighbors);
778 aabm.check_required_neighbors = !required_neighbors_s.empty();
781 const std::vector<std::string> &contents_s = abm->getTriggerContents();
782 for (const std::string &content_s : contents_s) {
783 std::vector<content_t> ids;
784 ndef->getIds(content_s, ids);
785 for (content_t c : ids) {
786 if (c >= m_aabms.size())
787 m_aabms.resize(c + 256, NULL);
789 m_aabms[c] = new std::vector<ActiveABM>;
790 m_aabms[c]->push_back(aabm);
798 for (auto &aabms : m_aabms)
802 // Find out how many objects the given block and its neighbours contain.
803 // Returns the number of objects in the block, and also in 'wider' the
804 // number of objects in the block and all its neighbours. The latter
805 // may an estimate if any neighbours are unloaded.
806 u32 countObjects(MapBlock *block, ServerMap * map, u32 &wider)
809 u32 wider_unknown_count = 0;
810 for(s16 x=-1; x<=1; x++)
811 for(s16 y=-1; y<=1; y++)
812 for(s16 z=-1; z<=1; z++)
814 MapBlock *block2 = map->getBlockNoCreateNoEx(
815 block->getPos() + v3s16(x,y,z));
817 wider_unknown_count++;
820 wider += block2->m_static_objects.m_active.size()
821 + block2->m_static_objects.m_stored.size();
824 u32 active_object_count = block->m_static_objects.m_active.size();
825 u32 wider_known_count = 3*3*3 - wider_unknown_count;
826 wider += wider_unknown_count * wider / wider_known_count;
827 return active_object_count;
830 void apply(MapBlock *block, int &blocks_scanned, int &abms_run, int &blocks_cached)
832 if(m_aabms.empty() || block->isDummy())
835 // Check the content type cache first
836 // to see whether there are any ABMs
837 // to be run at all for this block.
838 if (block->contents_cached) {
840 bool run_abms = false;
841 for (content_t c : block->contents) {
842 if (c < m_aabms.size() && m_aabms[c]) {
851 block->contents.clear();
855 ServerMap *map = &m_env->getServerMap();
857 u32 active_object_count_wider;
858 u32 active_object_count = this->countObjects(block, map, active_object_count_wider);
859 m_env->m_added_objects = 0;
862 for(p0.X=0; p0.X<MAP_BLOCKSIZE; p0.X++)
863 for(p0.Y=0; p0.Y<MAP_BLOCKSIZE; p0.Y++)
864 for(p0.Z=0; p0.Z<MAP_BLOCKSIZE; p0.Z++)
866 const MapNode &n = block->getNodeUnsafe(p0);
867 content_t c = n.getContent();
868 // Cache content types as we go
869 if (!block->contents_cached && !block->do_not_cache_contents) {
870 block->contents.insert(c);
871 if (block->contents.size() > 64) {
872 // Too many different nodes... don't try to cache
873 block->do_not_cache_contents = true;
874 block->contents.clear();
878 if (c >= m_aabms.size() || !m_aabms[c])
881 v3s16 p = p0 + block->getPosRelative();
882 for (ActiveABM &aabm : *m_aabms[c]) {
883 if (myrand() % aabm.chance != 0)
887 if (aabm.check_required_neighbors) {
889 for(p1.X = p0.X-1; p1.X <= p0.X+1; p1.X++)
890 for(p1.Y = p0.Y-1; p1.Y <= p0.Y+1; p1.Y++)
891 for(p1.Z = p0.Z-1; p1.Z <= p0.Z+1; p1.Z++)
896 if (block->isValidPosition(p1)) {
897 // if the neighbor is found on the same map block
898 // get it straight from there
899 const MapNode &n = block->getNodeUnsafe(p1);
902 // otherwise consult the map
903 MapNode n = map->getNode(p1 + block->getPosRelative());
906 if (CONTAINS(aabm.required_neighbors, c))
909 // No required neighbor found
915 // Call all the trigger variations
916 aabm.abm->trigger(m_env, p, n);
917 aabm.abm->trigger(m_env, p, n,
918 active_object_count, active_object_count_wider);
920 // Count surrounding objects again if the abms added any
921 if(m_env->m_added_objects > 0) {
922 active_object_count = countObjects(block, map, active_object_count_wider);
923 m_env->m_added_objects = 0;
927 block->contents_cached = !block->do_not_cache_contents;
931 void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime)
933 // Reset usage timer immediately, otherwise a block that becomes active
934 // again at around the same time as it would normally be unloaded will
935 // get unloaded incorrectly. (I think this still leaves a small possibility
936 // of a race condition between this and server::AsyncRunStep, which only
937 // some kind of synchronisation will fix, but it at least reduces the window
938 // of opportunity for it to break from seconds to nanoseconds)
939 block->resetUsageTimer();
941 // Get time difference
943 u32 stamp = block->getTimestamp();
944 if (m_game_time > stamp && stamp != BLOCK_TIMESTAMP_UNDEFINED)
945 dtime_s = m_game_time - stamp;
946 dtime_s += additional_dtime;
948 /*infostream<<"ServerEnvironment::activateBlock(): block timestamp: "
949 <<stamp<<", game time: "<<m_game_time<<std::endl;*/
951 // Remove stored static objects if clearObjects was called since block's timestamp
952 if (stamp == BLOCK_TIMESTAMP_UNDEFINED || stamp < m_last_clear_objects_time) {
953 block->m_static_objects.m_stored.clear();
954 // do not set changed flag to avoid unnecessary mapblock writes
957 // Set current time as timestamp
958 block->setTimestampNoChangedFlag(m_game_time);
960 /*infostream<<"ServerEnvironment::activateBlock(): block is "
961 <<dtime_s<<" seconds old."<<std::endl;*/
963 // Activate stored objects
964 activateObjects(block, dtime_s);
966 /* Handle LoadingBlockModifiers */
967 m_lbm_mgr.applyLBMs(this, block, stamp);
970 std::vector<NodeTimer> elapsed_timers =
971 block->m_node_timers.step((float)dtime_s);
972 if (!elapsed_timers.empty()) {
974 for (const NodeTimer &elapsed_timer : elapsed_timers) {
975 n = block->getNodeNoEx(elapsed_timer.position);
976 v3s16 p = elapsed_timer.position + block->getPosRelative();
977 if (m_script->node_on_timer(p, n, elapsed_timer.elapsed))
978 block->setNodeTimer(NodeTimer(elapsed_timer.timeout, 0,
979 elapsed_timer.position));
984 void ServerEnvironment::addActiveBlockModifier(ActiveBlockModifier *abm)
986 m_abms.emplace_back(abm);
989 void ServerEnvironment::addLoadingBlockModifierDef(LoadingBlockModifierDef *lbm)
991 m_lbm_mgr.addLBMDef(lbm);
994 bool ServerEnvironment::setNode(v3s16 p, const MapNode &n)
996 const NodeDefManager *ndef = m_server->ndef();
997 MapNode n_old = m_map->getNode(p);
999 const ContentFeatures &cf_old = ndef->get(n_old);
1002 if (cf_old.has_on_destruct)
1003 m_script->node_on_destruct(p, n_old);
1006 if (!m_map->addNodeWithEvent(p, n))
1009 // Update active VoxelManipulator if a mapgen thread
1010 m_map->updateVManip(p);
1012 // Call post-destructor
1013 if (cf_old.has_after_destruct)
1014 m_script->node_after_destruct(p, n_old);
1016 // Retrieve node content features
1017 // if new node is same as old, reuse old definition to prevent a lookup
1018 const ContentFeatures &cf_new = n_old == n ? cf_old : ndef->get(n);
1021 if (cf_new.has_on_construct)
1022 m_script->node_on_construct(p, n);
1027 bool ServerEnvironment::removeNode(v3s16 p)
1029 const NodeDefManager *ndef = m_server->ndef();
1030 MapNode n_old = m_map->getNode(p);
1033 if (ndef->get(n_old).has_on_destruct)
1034 m_script->node_on_destruct(p, n_old);
1037 // This is slightly optimized compared to addNodeWithEvent(air)
1038 if (!m_map->removeNodeWithEvent(p))
1041 // Update active VoxelManipulator if a mapgen thread
1042 m_map->updateVManip(p);
1044 // Call post-destructor
1045 if (ndef->get(n_old).has_after_destruct)
1046 m_script->node_after_destruct(p, n_old);
1048 // Air doesn't require constructor
1052 bool ServerEnvironment::swapNode(v3s16 p, const MapNode &n)
1054 if (!m_map->addNodeWithEvent(p, n, false))
1057 // Update active VoxelManipulator if a mapgen thread
1058 m_map->updateVManip(p);
1063 void ServerEnvironment::clearObjects(ClearObjectsMode mode)
1065 infostream << "ServerEnvironment::clearObjects(): "
1066 << "Removing all active objects" << std::endl;
1067 auto cb_removal = [this] (ServerActiveObject *obj, u16 id) {
1068 if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER)
1071 // Delete static object if block is loaded
1072 deleteStaticFromBlock(obj, id, MOD_REASON_CLEAR_ALL_OBJECTS, true);
1074 // If known by some client, don't delete immediately
1075 if (obj->m_known_by_count > 0) {
1076 obj->m_pending_removal = true;
1080 // Tell the object about removal
1081 obj->removingFromEnvironment();
1082 // Deregister in scripting api
1083 m_script->removeObjectReference(obj);
1085 // Delete active object
1086 if (obj->environmentDeletes())
1092 m_ao_manager.clear(cb_removal);
1094 // Get list of loaded blocks
1095 std::vector<v3s16> loaded_blocks;
1096 infostream << "ServerEnvironment::clearObjects(): "
1097 << "Listing all loaded blocks" << std::endl;
1098 m_map->listAllLoadedBlocks(loaded_blocks);
1099 infostream << "ServerEnvironment::clearObjects(): "
1100 << "Done listing all loaded blocks: "
1101 << loaded_blocks.size()<<std::endl;
1103 // Get list of loadable blocks
1104 std::vector<v3s16> loadable_blocks;
1105 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1106 infostream << "ServerEnvironment::clearObjects(): "
1107 << "Listing all loadable blocks" << std::endl;
1108 m_map->listAllLoadableBlocks(loadable_blocks);
1109 infostream << "ServerEnvironment::clearObjects(): "
1110 << "Done listing all loadable blocks: "
1111 << loadable_blocks.size() << std::endl;
1113 loadable_blocks = loaded_blocks;
1116 actionstream << "ServerEnvironment::clearObjects(): "
1117 << "Now clearing objects in " << loadable_blocks.size()
1118 << " blocks" << std::endl;
1120 // Grab a reference on each loaded block to avoid unloading it
1121 for (v3s16 p : loaded_blocks) {
1122 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1123 assert(block != NULL);
1127 // Remove objects in all loadable blocks
1128 u32 unload_interval = U32_MAX;
1129 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1130 unload_interval = g_settings->getS32("max_clearobjects_extra_loaded_blocks");
1131 unload_interval = MYMAX(unload_interval, 1);
1133 u32 report_interval = loadable_blocks.size() / 10;
1134 u32 num_blocks_checked = 0;
1135 u32 num_blocks_cleared = 0;
1136 u32 num_objs_cleared = 0;
1137 for (auto i = loadable_blocks.begin();
1138 i != loadable_blocks.end(); ++i) {
1140 MapBlock *block = m_map->emergeBlock(p, false);
1142 errorstream << "ServerEnvironment::clearObjects(): "
1143 << "Failed to emerge block " << PP(p) << std::endl;
1146 u32 num_stored = block->m_static_objects.m_stored.size();
1147 u32 num_active = block->m_static_objects.m_active.size();
1148 if (num_stored != 0 || num_active != 0) {
1149 block->m_static_objects.m_stored.clear();
1150 block->m_static_objects.m_active.clear();
1151 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1152 MOD_REASON_CLEAR_ALL_OBJECTS);
1153 num_objs_cleared += num_stored + num_active;
1154 num_blocks_cleared++;
1156 num_blocks_checked++;
1158 if (report_interval != 0 &&
1159 num_blocks_checked % report_interval == 0) {
1160 float percent = 100.0 * (float)num_blocks_checked /
1161 loadable_blocks.size();
1162 actionstream << "ServerEnvironment::clearObjects(): "
1163 << "Cleared " << num_objs_cleared << " objects"
1164 << " in " << num_blocks_cleared << " blocks ("
1165 << percent << "%)" << std::endl;
1167 if (num_blocks_checked % unload_interval == 0) {
1168 m_map->unloadUnreferencedBlocks();
1171 m_map->unloadUnreferencedBlocks();
1173 // Drop references that were added above
1174 for (v3s16 p : loaded_blocks) {
1175 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1180 m_last_clear_objects_time = m_game_time;
1182 actionstream << "ServerEnvironment::clearObjects(): "
1183 << "Finished: Cleared " << num_objs_cleared << " objects"
1184 << " in " << num_blocks_cleared << " blocks" << std::endl;
1187 void ServerEnvironment::step(float dtime)
1189 ScopeProfiler sp2(g_profiler, "ServerEnv::step()", SPT_AVG);
1190 /* Step time of day */
1191 stepTimeOfDay(dtime);
1194 // NOTE: This is kind of funny on a singleplayer game, but doesn't
1195 // really matter that much.
1196 static thread_local const float server_step =
1197 g_settings->getFloat("dedicated_server_step");
1198 m_recommended_send_interval = server_step;
1204 m_game_time_fraction_counter += dtime;
1205 u32 inc_i = (u32)m_game_time_fraction_counter;
1206 m_game_time += inc_i;
1207 m_game_time_fraction_counter -= (float)inc_i;
1214 ScopeProfiler sp(g_profiler, "ServerEnv: move players", SPT_AVG);
1215 for (RemotePlayer *player : m_players) {
1216 // Ignore disconnected players
1217 if (player->getPeerId() == PEER_ID_INEXISTENT)
1221 player->move(dtime, this, 100 * BS);
1225 if (m_database_check_interval.step(dtime, 10.0f)) {
1226 m_auth_database->pingDatabase();
1227 m_player_database->pingDatabase();
1228 m_map->pingDatabase();
1231 Manage active block list
1233 if (m_active_blocks_management_interval.step(dtime, m_cache_active_block_mgmt_interval)) {
1234 ScopeProfiler sp(g_profiler, "ServerEnv: update active blocks", SPT_AVG);
1236 Get player block positions
1238 std::vector<PlayerSAO*> players;
1239 for (RemotePlayer *player: m_players) {
1240 // Ignore disconnected players
1241 if (player->getPeerId() == PEER_ID_INEXISTENT)
1244 PlayerSAO *playersao = player->getPlayerSAO();
1247 players.push_back(playersao);
1251 Update list of active blocks, collecting changes
1253 // use active_object_send_range_blocks since that is max distance
1254 // for active objects sent the client anyway
1255 static thread_local const s16 active_object_range =
1256 g_settings->getS16("active_object_send_range_blocks");
1257 static thread_local const s16 active_block_range =
1258 g_settings->getS16("active_block_range");
1259 std::set<v3s16> blocks_removed;
1260 std::set<v3s16> blocks_added;
1261 m_active_blocks.update(players, active_block_range, active_object_range,
1262 blocks_removed, blocks_added);
1265 Handle removed blocks
1268 // Convert active objects that are no more in active blocks to static
1269 deactivateFarObjects(false);
1271 for (const v3s16 &p: blocks_removed) {
1272 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1276 // Set current time as timestamp (and let it set ChangedFlag)
1277 block->setTimestamp(m_game_time);
1284 for (const v3s16 &p: blocks_added) {
1285 MapBlock *block = m_map->getBlockOrEmerge(p);
1287 m_active_blocks.m_list.erase(p);
1288 m_active_blocks.m_abm_list.erase(p);
1292 activateBlock(block);
1297 Mess around in active blocks
1299 if (m_active_blocks_nodemetadata_interval.step(dtime, m_cache_nodetimer_interval)) {
1300 ScopeProfiler sp(g_profiler, "ServerEnv: Run node timers", SPT_AVG);
1302 float dtime = m_cache_nodetimer_interval;
1304 for (const v3s16 &p: m_active_blocks.m_list) {
1305 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1309 // Reset block usage timer
1310 block->resetUsageTimer();
1312 // Set current time as timestamp
1313 block->setTimestampNoChangedFlag(m_game_time);
1314 // If time has changed much from the one on disk,
1315 // set block to be saved when it is unloaded
1316 if(block->getTimestamp() > block->getDiskTimestamp() + 60)
1317 block->raiseModified(MOD_STATE_WRITE_AT_UNLOAD,
1318 MOD_REASON_BLOCK_EXPIRED);
1321 std::vector<NodeTimer> elapsed_timers = block->m_node_timers.step(dtime);
1322 if (!elapsed_timers.empty()) {
1325 for (const NodeTimer &elapsed_timer: elapsed_timers) {
1326 n = block->getNodeNoEx(elapsed_timer.position);
1327 p2 = elapsed_timer.position + block->getPosRelative();
1328 if (m_script->node_on_timer(p2, n, elapsed_timer.elapsed)) {
1329 block->setNodeTimer(NodeTimer(
1330 elapsed_timer.timeout, 0, elapsed_timer.position));
1337 if (m_active_block_modifier_interval.step(dtime, m_cache_abm_interval)) {
1338 ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg per interval", SPT_AVG);
1339 TimeTaker timer("modify in active blocks per interval");
1341 // Initialize handling of ActiveBlockModifiers
1342 ABMHandler abmhandler(m_abms, m_cache_abm_interval, this, true);
1344 int blocks_scanned = 0;
1346 int blocks_cached = 0;
1348 std::vector<v3s16> output(m_active_blocks.m_abm_list.size());
1350 // Shuffle the active blocks so that each block gets an equal chance
1351 // of having its ABMs run.
1352 std::copy(m_active_blocks.m_abm_list.begin(), m_active_blocks.m_abm_list.end(), output.begin());
1353 std::shuffle(output.begin(), output.end(), m_rgen);
1356 // The time budget for ABMs is 20%.
1357 u32 max_time_ms = m_cache_abm_interval * 1000 / 5;
1358 for (const v3s16 &p : output) {
1359 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1365 // Set current time as timestamp
1366 block->setTimestampNoChangedFlag(m_game_time);
1368 /* Handle ActiveBlockModifiers */
1369 abmhandler.apply(block, blocks_scanned, abms_run, blocks_cached);
1371 u32 time_ms = timer.getTimerTime();
1373 if (time_ms > max_time_ms) {
1374 warningstream << "active block modifiers took "
1375 << time_ms << "ms (processed " << i << " of "
1376 << output.size() << " active blocks)" << std::endl;
1380 g_profiler->avg("ServerEnv: active blocks", m_active_blocks.m_abm_list.size());
1381 g_profiler->avg("ServerEnv: active blocks cached", blocks_cached);
1382 g_profiler->avg("ServerEnv: active blocks scanned for ABMs", blocks_scanned);
1383 g_profiler->avg("ServerEnv: ABMs run", abms_run);
1389 Step script environment (run global on_step())
1391 m_script->environment_Step(dtime);
1397 ScopeProfiler sp(g_profiler, "ServerEnv: Run SAO::step()", SPT_AVG);
1399 // This helps the objects to send data at the same time
1400 bool send_recommended = false;
1401 m_send_recommended_timer += dtime;
1402 if (m_send_recommended_timer > getSendRecommendedInterval()) {
1403 m_send_recommended_timer -= getSendRecommendedInterval();
1404 send_recommended = true;
1407 auto cb_state = [this, dtime, send_recommended] (ServerActiveObject *obj) {
1412 obj->step(dtime, send_recommended);
1413 // Read messages from object
1414 obj->dumpAOMessagesToQueue(m_active_object_messages);
1416 m_ao_manager.step(dtime, cb_state);
1420 Manage active objects
1422 if (m_object_management_interval.step(dtime, 0.5)) {
1423 removeRemovedObjects();
1427 Manage particle spawner expiration
1429 if (m_particle_management_interval.step(dtime, 1.0)) {
1430 for (std::unordered_map<u32, float>::iterator i = m_particle_spawners.begin();
1431 i != m_particle_spawners.end(); ) {
1432 //non expiring spawners
1433 if (i->second == PARTICLE_SPAWNER_NO_EXPIRY) {
1439 if (i->second <= 0.f)
1440 m_particle_spawners.erase(i++);
1446 // Send outdated player inventories
1447 for (RemotePlayer *player : m_players) {
1448 if (player->getPeerId() == PEER_ID_INEXISTENT)
1451 PlayerSAO *sao = player->getPlayerSAO();
1452 if (sao && player->inventory.checkModified())
1453 m_server->SendInventory(sao, true);
1456 // Send outdated detached inventories
1457 m_server->sendDetachedInventories(PEER_ID_INEXISTENT, true);
1460 u32 ServerEnvironment::addParticleSpawner(float exptime)
1462 // Timers with lifetime 0 do not expire
1463 float time = exptime > 0.f ? exptime : PARTICLE_SPAWNER_NO_EXPIRY;
1466 for (;;) { // look for unused particlespawner id
1468 std::unordered_map<u32, float>::iterator f = m_particle_spawners.find(id);
1469 if (f == m_particle_spawners.end()) {
1470 m_particle_spawners[id] = time;
1477 u32 ServerEnvironment::addParticleSpawner(float exptime, u16 attached_id)
1479 u32 id = addParticleSpawner(exptime);
1480 m_particle_spawner_attachments[id] = attached_id;
1481 if (ServerActiveObject *obj = getActiveObject(attached_id)) {
1482 obj->attachParticleSpawner(id);
1487 void ServerEnvironment::deleteParticleSpawner(u32 id, bool remove_from_object)
1489 m_particle_spawners.erase(id);
1490 const auto &it = m_particle_spawner_attachments.find(id);
1491 if (it != m_particle_spawner_attachments.end()) {
1492 u16 obj_id = it->second;
1493 ServerActiveObject *sao = getActiveObject(obj_id);
1494 if (sao != NULL && remove_from_object) {
1495 sao->detachParticleSpawner(id);
1497 m_particle_spawner_attachments.erase(id);
1501 u16 ServerEnvironment::addActiveObject(ServerActiveObject *object)
1503 assert(object); // Pre-condition
1505 u16 id = addActiveObjectRaw(object, true, 0);
1510 Finds out what new objects have been added to
1511 inside a radius around a position
1513 void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius,
1515 std::set<u16> ¤t_objects,
1516 std::queue<u16> &added_objects)
1518 f32 radius_f = radius * BS;
1519 f32 player_radius_f = player_radius * BS;
1521 if (player_radius_f < 0.0f)
1522 player_radius_f = 0.0f;
1524 m_ao_manager.getAddedActiveObjectsAroundPos(playersao->getBasePosition(), radius_f,
1525 player_radius_f, current_objects, added_objects);
1529 Finds out what objects have been removed from
1530 inside a radius around a position
1532 void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius,
1534 std::set<u16> ¤t_objects,
1535 std::queue<u16> &removed_objects)
1537 f32 radius_f = radius * BS;
1538 f32 player_radius_f = player_radius * BS;
1540 if (player_radius_f < 0)
1541 player_radius_f = 0;
1543 Go through current_objects; object is removed if:
1544 - object is not found in m_active_objects (this is actually an
1545 error condition; objects should be removed only after all clients
1546 have been informed about removal), or
1547 - object is to be removed or deactivated, or
1548 - object is too far away
1550 for (u16 id : current_objects) {
1551 ServerActiveObject *object = getActiveObject(id);
1553 if (object == NULL) {
1554 infostream << "ServerEnvironment::getRemovedActiveObjects():"
1555 << " object in current_objects is NULL" << std::endl;
1556 removed_objects.push(id);
1560 if (object->isGone()) {
1561 removed_objects.push(id);
1565 f32 distance_f = object->getBasePosition().getDistanceFrom(playersao->getBasePosition());
1566 if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1567 if (distance_f <= player_radius_f || player_radius_f == 0)
1569 } else if (distance_f <= radius_f)
1572 // Object is no longer visible
1573 removed_objects.push(id);
1577 void ServerEnvironment::setStaticForActiveObjectsInBlock(
1578 v3s16 blockpos, bool static_exists, v3s16 static_block)
1580 MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos);
1584 for (auto &so_it : block->m_static_objects.m_active) {
1585 // Get the ServerActiveObject counterpart to this StaticObject
1586 ServerActiveObject *sao = m_ao_manager.getActiveObject(so_it.first);
1588 // If this ever happens, there must be some kind of nasty bug.
1589 errorstream << "ServerEnvironment::setStaticForObjectsInBlock(): "
1590 "Object from MapBlock::m_static_objects::m_active not found "
1591 "in m_active_objects";
1595 sao->m_static_exists = static_exists;
1596 sao->m_static_block = static_block;
1600 ActiveObjectMessage ServerEnvironment::getActiveObjectMessage()
1602 if(m_active_object_messages.empty())
1603 return ActiveObjectMessage(0);
1605 ActiveObjectMessage message = m_active_object_messages.front();
1606 m_active_object_messages.pop();
1610 void ServerEnvironment::getSelectedActiveObjects(
1611 const core::line3d<f32> &shootline_on_map,
1612 std::vector<PointedThing> &objects)
1614 std::vector<ServerActiveObject *> objs;
1615 getObjectsInsideRadius(objs, shootline_on_map.start,
1616 shootline_on_map.getLength() + 10.0f, nullptr);
1617 const v3f line_vector = shootline_on_map.getVector();
1619 for (auto obj : objs) {
1620 aabb3f selection_box;
1621 if (!obj->getSelectionBox(&selection_box))
1624 v3f pos = obj->getBasePosition();
1626 aabb3f offsetted_box(selection_box.MinEdge + pos,
1627 selection_box.MaxEdge + pos);
1629 v3f current_intersection;
1630 v3s16 current_normal;
1631 if (boxLineCollision(offsetted_box, shootline_on_map.start, line_vector,
1632 ¤t_intersection, ¤t_normal)) {
1633 objects.emplace_back(
1634 (s16) obj->getId(), current_intersection, current_normal,
1635 (current_intersection - shootline_on_map.start).getLengthSQ());
1641 ************ Private methods *************
1644 u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object,
1645 bool set_changed, u32 dtime_s)
1647 if (!m_ao_manager.registerObject(object)) {
1651 // Register reference in scripting api (must be done before post-init)
1652 m_script->addObjectReference(object);
1653 // Post-initialize object
1654 object->addedToEnvironment(dtime_s);
1656 // Add static data to block
1657 if (object->isStaticAllowed()) {
1658 // Add static object to active static list of the block
1659 v3f objectpos = object->getBasePosition();
1660 StaticObject s_obj(object, objectpos);
1661 // Add to the block where the object is located in
1662 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
1663 MapBlock *block = m_map->emergeBlock(blockpos);
1665 block->m_static_objects.m_active[object->getId()] = s_obj;
1666 object->m_static_exists = true;
1667 object->m_static_block = blockpos;
1670 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1671 MOD_REASON_ADD_ACTIVE_OBJECT_RAW);
1673 v3s16 p = floatToInt(objectpos, BS);
1674 errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
1675 <<"could not emerge block for storing id="<<object->getId()
1676 <<" statically (pos="<<PP(p)<<")"<<std::endl;
1680 return object->getId();
1684 Remove objects that satisfy (isGone() && m_known_by_count==0)
1686 void ServerEnvironment::removeRemovedObjects()
1688 ScopeProfiler sp(g_profiler, "ServerEnvironment::removeRemovedObjects()", SPT_AVG);
1690 auto clear_cb = [this] (ServerActiveObject *obj, u16 id) {
1691 // This shouldn't happen but check it
1693 errorstream << "ServerEnvironment::removeRemovedObjects(): "
1694 << "NULL object found. id=" << id << std::endl;
1699 We will handle objects marked for removal or deactivation
1705 Delete static data from block if removed
1707 if (obj->m_pending_removal)
1708 deleteStaticFromBlock(obj, id, MOD_REASON_REMOVE_OBJECTS_REMOVE, false);
1710 // If still known by clients, don't actually remove. On some future
1711 // invocation this will be 0, which is when removal will continue.
1712 if(obj->m_known_by_count > 0)
1716 Move static data from active to stored if deactivated
1718 if (!obj->m_pending_removal && obj->m_static_exists) {
1719 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
1721 const auto i = block->m_static_objects.m_active.find(id);
1722 if (i != block->m_static_objects.m_active.end()) {
1723 block->m_static_objects.m_stored.push_back(i->second);
1724 block->m_static_objects.m_active.erase(id);
1725 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1726 MOD_REASON_REMOVE_OBJECTS_DEACTIVATE);
1728 warningstream << "ServerEnvironment::removeRemovedObjects(): "
1729 << "id=" << id << " m_static_exists=true but "
1730 << "static data doesn't actually exist in "
1731 << PP(obj->m_static_block) << std::endl;
1734 infostream << "Failed to emerge block from which an object to "
1735 << "be deactivated was loaded from. id=" << id << std::endl;
1739 // Tell the object about removal
1740 obj->removingFromEnvironment();
1741 // Deregister in scripting api
1742 m_script->removeObjectReference(obj);
1745 if (obj->environmentDeletes())
1751 m_ao_manager.clear(clear_cb);
1754 static void print_hexdump(std::ostream &o, const std::string &data)
1756 const int linelength = 16;
1757 for(int l=0; ; l++){
1758 int i0 = linelength * l;
1759 bool at_end = false;
1760 int thislinelength = linelength;
1761 if(i0 + thislinelength > (int)data.size()){
1762 thislinelength = data.size() - i0;
1765 for(int di=0; di<linelength; di++){
1768 if(di<thislinelength)
1769 porting::mt_snprintf(buf, sizeof(buf), "%.2x ", data[i]);
1771 porting::mt_snprintf(buf, sizeof(buf), " ");
1775 for(int di=0; di<thislinelength; di++){
1788 ServerActiveObject* ServerEnvironment::createSAO(ActiveObjectType type, v3f pos,
1789 const std::string &data)
1792 case ACTIVEOBJECT_TYPE_LUAENTITY:
1793 return new LuaEntitySAO(this, pos, data);
1795 warningstream << "ServerActiveObject: No factory for type=" << type << std::endl;
1801 Convert stored objects from blocks near the players to active.
1803 void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s)
1808 // Ignore if no stored objects (to not set changed flag)
1809 if(block->m_static_objects.m_stored.empty())
1812 verbosestream<<"ServerEnvironment::activateObjects(): "
1813 <<"activating objects of block "<<PP(block->getPos())
1814 <<" ("<<block->m_static_objects.m_stored.size()
1815 <<" objects)"<<std::endl;
1816 bool large_amount = (block->m_static_objects.m_stored.size() > g_settings->getU16("max_objects_per_block"));
1818 errorstream<<"suspiciously large amount of objects detected: "
1819 <<block->m_static_objects.m_stored.size()<<" in "
1820 <<PP(block->getPos())
1821 <<"; removing all of them."<<std::endl;
1822 // Clear stored list
1823 block->m_static_objects.m_stored.clear();
1824 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1825 MOD_REASON_TOO_MANY_OBJECTS);
1829 // Activate stored objects
1830 std::vector<StaticObject> new_stored;
1831 for (const StaticObject &s_obj : block->m_static_objects.m_stored) {
1832 // Create an active object from the data
1833 ServerActiveObject *obj = createSAO((ActiveObjectType) s_obj.type, s_obj.pos,
1835 // If couldn't create object, store static data back.
1837 errorstream<<"ServerEnvironment::activateObjects(): "
1838 <<"failed to create active object from static object "
1839 <<"in block "<<PP(s_obj.pos/BS)
1840 <<" type="<<(int)s_obj.type<<" data:"<<std::endl;
1841 print_hexdump(verbosestream, s_obj.data);
1843 new_stored.push_back(s_obj);
1846 verbosestream<<"ServerEnvironment::activateObjects(): "
1847 <<"activated static object pos="<<PP(s_obj.pos/BS)
1848 <<" type="<<(int)s_obj.type<<std::endl;
1849 // This will also add the object to the active static list
1850 addActiveObjectRaw(obj, false, dtime_s);
1853 // Clear stored list
1854 block->m_static_objects.m_stored.clear();
1855 // Add leftover failed stuff to stored list
1856 for (const StaticObject &s_obj : new_stored) {
1857 block->m_static_objects.m_stored.push_back(s_obj);
1861 Note: Block hasn't really been modified here.
1862 The objects have just been activated and moved from the stored
1863 static list to the active static list.
1864 As such, the block is essentially the same.
1865 Thus, do not call block->raiseModified(MOD_STATE_WRITE_NEEDED).
1866 Otherwise there would be a huge amount of unnecessary I/O.
1871 Convert objects that are not standing inside active blocks to static.
1873 If m_known_by_count != 0, active object is not deleted, but static
1874 data is still updated.
1876 If force_delete is set, active object is deleted nevertheless. It
1877 shall only be set so in the destructor of the environment.
1879 If block wasn't generated (not in memory or on disk),
1881 void ServerEnvironment::deactivateFarObjects(bool _force_delete)
1883 auto cb_deactivate = [this, _force_delete] (ServerActiveObject *obj, u16 id) {
1884 // force_delete might be overriden per object
1885 bool force_delete = _force_delete;
1887 // Do not deactivate if static data creation not allowed
1888 if (!force_delete && !obj->isStaticAllowed())
1891 // removeRemovedObjects() is responsible for these
1892 if (!force_delete && obj->isGone())
1895 const v3f &objectpos = obj->getBasePosition();
1897 // The block in which the object resides in
1898 v3s16 blockpos_o = getNodeBlockPos(floatToInt(objectpos, BS));
1900 // If object's static data is stored in a deactivated block and object
1901 // is actually located in an active block, re-save to the block in
1902 // which the object is actually located in.
1903 if (!force_delete && obj->m_static_exists &&
1904 !m_active_blocks.contains(obj->m_static_block) &&
1905 m_active_blocks.contains(blockpos_o)) {
1906 // Delete from block where object was located
1907 deleteStaticFromBlock(obj, id, MOD_REASON_STATIC_DATA_REMOVED, false);
1909 StaticObject s_obj(obj, objectpos);
1910 // Save to block where object is located
1911 saveStaticToBlock(blockpos_o, id, obj, s_obj, MOD_REASON_STATIC_DATA_ADDED);
1916 // If block is still active, don't remove
1917 if (!force_delete && m_active_blocks.contains(blockpos_o))
1920 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1921 << "deactivating object id=" << id << " on inactive block "
1922 << PP(blockpos_o) << std::endl;
1924 // If known by some client, don't immediately delete.
1925 bool pending_delete = (obj->m_known_by_count > 0 && !force_delete);
1928 Update the static data
1930 if (obj->isStaticAllowed()) {
1931 // Create new static object
1932 StaticObject s_obj(obj, objectpos);
1934 bool stays_in_same_block = false;
1935 bool data_changed = true;
1937 // Check if static data has changed considerably
1938 if (obj->m_static_exists) {
1939 if (obj->m_static_block == blockpos_o)
1940 stays_in_same_block = true;
1942 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
1945 const auto n = block->m_static_objects.m_active.find(id);
1946 if (n != block->m_static_objects.m_active.end()) {
1947 StaticObject static_old = n->second;
1949 float save_movem = obj->getMinimumSavedMovement();
1951 if (static_old.data == s_obj.data &&
1952 (static_old.pos - objectpos).getLength() < save_movem)
1953 data_changed = false;
1955 warningstream << "ServerEnvironment::deactivateFarObjects(): "
1956 << "id=" << id << " m_static_exists=true but "
1957 << "static data doesn't actually exist in "
1958 << PP(obj->m_static_block) << std::endl;
1964 While changes are always saved, blocks are only marked as modified
1965 if the object has moved or different staticdata. (see above)
1967 bool shall_be_written = (!stays_in_same_block || data_changed);
1968 u32 reason = shall_be_written ? MOD_REASON_STATIC_DATA_CHANGED : MOD_REASON_UNKNOWN;
1970 // Delete old static object
1971 deleteStaticFromBlock(obj, id, reason, false);
1973 // Add to the block where the object is located in
1974 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
1975 u16 store_id = pending_delete ? id : 0;
1976 if (!saveStaticToBlock(blockpos, store_id, obj, s_obj, reason))
1977 force_delete = true;
1981 If known by some client, set pending deactivation.
1982 Otherwise delete it immediately.
1984 if (pending_delete && !force_delete) {
1985 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1986 << "object id=" << id << " is known by clients"
1987 << "; not deleting yet" << std::endl;
1989 obj->m_pending_deactivation = true;
1993 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1994 << "object id=" << id << " is not known by clients"
1995 << "; deleting" << std::endl;
1997 // Tell the object about removal
1998 obj->removingFromEnvironment();
1999 // Deregister in scripting api
2000 m_script->removeObjectReference(obj);
2002 // Delete active object
2003 if (obj->environmentDeletes())
2009 m_ao_manager.clear(cb_deactivate);
2012 void ServerEnvironment::deleteStaticFromBlock(
2013 ServerActiveObject *obj, u16 id, u32 mod_reason, bool no_emerge)
2015 if (!obj->m_static_exists)
2020 block = m_map->getBlockNoCreateNoEx(obj->m_static_block);
2022 block = m_map->emergeBlock(obj->m_static_block, false);
2025 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2026 << " when deleting static data of object from it. id=" << id << std::endl;
2030 block->m_static_objects.remove(id);
2031 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2032 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2034 obj->m_static_exists = false;
2037 bool ServerEnvironment::saveStaticToBlock(
2038 v3s16 blockpos, u16 store_id,
2039 ServerActiveObject *obj, const StaticObject &s_obj,
2042 MapBlock *block = nullptr;
2044 block = m_map->emergeBlock(blockpos);
2045 } catch (InvalidPositionException &e) {
2046 // Handled via NULL pointer
2047 // NOTE: emergeBlock's failure is usually determined by it
2048 // actually returning NULL
2052 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2053 << " when saving static data of object to it. id=" << store_id << std::endl;
2056 if (block->m_static_objects.m_stored.size() >= g_settings->getU16("max_objects_per_block")) {
2057 warningstream << "ServerEnv: Trying to store id = " << store_id
2058 << " statically but block " << PP(blockpos)
2059 << " already contains "
2060 << block->m_static_objects.m_stored.size()
2061 << " objects." << std::endl;
2065 block->m_static_objects.insert(store_id, s_obj);
2066 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2067 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2069 obj->m_static_exists = true;
2070 obj->m_static_block = blockpos;
2075 PlayerDatabase *ServerEnvironment::openPlayerDatabase(const std::string &name,
2076 const std::string &savedir, const Settings &conf)
2079 if (name == "sqlite3")
2080 return new PlayerDatabaseSQLite3(savedir);
2082 if (name == "dummy")
2083 return new Database_Dummy();
2085 if (name == "postgresql") {
2086 std::string connect_string;
2087 conf.getNoEx("pgsql_player_connection", connect_string);
2088 return new PlayerDatabasePostgreSQL(connect_string);
2091 if (name == "files")
2092 return new PlayerDatabaseFiles(savedir + DIR_DELIM + "players");
2094 throw BaseException(std::string("Database backend ") + name + " not supported.");
2097 bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params,
2098 const Settings &cmd_args)
2100 std::string migrate_to = cmd_args.get("migrate-players");
2102 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2103 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2104 errorstream << "Cannot read world.mt!" << std::endl;
2108 if (!world_mt.exists("player_backend")) {
2109 errorstream << "Please specify your current backend in world.mt:"
2111 << " player_backend = {files|sqlite3|postgresql}"
2116 std::string backend = world_mt.get("player_backend");
2117 if (backend == migrate_to) {
2118 errorstream << "Cannot migrate: new backend is same"
2119 << " as the old one" << std::endl;
2123 const std::string players_backup_path = game_params.world_path + DIR_DELIM
2126 if (backend == "files") {
2127 // Create backup directory
2128 fs::CreateDir(players_backup_path);
2132 PlayerDatabase *srcdb = ServerEnvironment::openPlayerDatabase(backend,
2133 game_params.world_path, world_mt);
2134 PlayerDatabase *dstdb = ServerEnvironment::openPlayerDatabase(migrate_to,
2135 game_params.world_path, world_mt);
2137 std::vector<std::string> player_list;
2138 srcdb->listPlayers(player_list);
2139 for (std::vector<std::string>::const_iterator it = player_list.begin();
2140 it != player_list.end(); ++it) {
2141 actionstream << "Migrating player " << it->c_str() << std::endl;
2142 RemotePlayer player(it->c_str(), NULL);
2143 PlayerSAO playerSAO(NULL, &player, 15000, false);
2145 srcdb->loadPlayer(&player, &playerSAO);
2147 playerSAO.finalize(&player, std::set<std::string>());
2148 player.setPlayerSAO(&playerSAO);
2150 dstdb->savePlayer(&player);
2152 // For files source, move player files to backup dir
2153 if (backend == "files") {
2155 game_params.world_path + DIR_DELIM + "players" + DIR_DELIM + (*it),
2156 players_backup_path + DIR_DELIM + (*it));
2160 actionstream << "Successfully migrated " << player_list.size() << " players"
2162 world_mt.set("player_backend", migrate_to);
2163 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2164 errorstream << "Failed to update world.mt!" << std::endl;
2166 actionstream << "world.mt updated" << std::endl;
2168 // When migration is finished from file backend, remove players directory if empty
2169 if (backend == "files") {
2170 fs::DeleteSingleFileOrEmptyDirectory(game_params.world_path + DIR_DELIM
2177 } catch (BaseException &e) {
2178 errorstream << "An error occurred during migration: " << e.what() << std::endl;
2184 AuthDatabase *ServerEnvironment::openAuthDatabase(
2185 const std::string &name, const std::string &savedir, const Settings &conf)
2187 if (name == "sqlite3")
2188 return new AuthDatabaseSQLite3(savedir);
2191 if (name == "postgresql") {
2192 std::string connect_string;
2193 conf.getNoEx("pgsql_auth_connection", connect_string);
2194 return new AuthDatabasePostgreSQL(connect_string);
2198 if (name == "files")
2199 return new AuthDatabaseFiles(savedir);
2202 if (name == "leveldb")
2203 return new AuthDatabaseLevelDB(savedir);
2206 throw BaseException(std::string("Database backend ") + name + " not supported.");
2209 bool ServerEnvironment::migrateAuthDatabase(
2210 const GameParams &game_params, const Settings &cmd_args)
2212 std::string migrate_to = cmd_args.get("migrate-auth");
2214 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2215 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2216 errorstream << "Cannot read world.mt!" << std::endl;
2220 std::string backend = "files";
2221 if (world_mt.exists("auth_backend"))
2222 backend = world_mt.get("auth_backend");
2224 warningstream << "No auth_backend found in world.mt, "
2225 "assuming \"files\"." << std::endl;
2227 if (backend == migrate_to) {
2228 errorstream << "Cannot migrate: new backend is same"
2229 << " as the old one" << std::endl;
2234 const std::unique_ptr<AuthDatabase> srcdb(ServerEnvironment::openAuthDatabase(
2235 backend, game_params.world_path, world_mt));
2236 const std::unique_ptr<AuthDatabase> dstdb(ServerEnvironment::openAuthDatabase(
2237 migrate_to, game_params.world_path, world_mt));
2239 std::vector<std::string> names_list;
2240 srcdb->listNames(names_list);
2241 for (const std::string &name : names_list) {
2242 actionstream << "Migrating auth entry for " << name << std::endl;
2244 AuthEntry authEntry;
2245 success = srcdb->getAuth(name, authEntry);
2246 success = success && dstdb->createAuth(authEntry);
2248 errorstream << "Failed to migrate " << name << std::endl;
2251 actionstream << "Successfully migrated " << names_list.size()
2252 << " auth entries" << std::endl;
2253 world_mt.set("auth_backend", migrate_to);
2254 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2255 errorstream << "Failed to update world.mt!" << std::endl;
2257 actionstream << "world.mt updated" << std::endl;
2259 if (backend == "files") {
2260 // special-case files migration:
2261 // move auth.txt to auth.txt.bak if possible
2262 std::string auth_txt_path =
2263 game_params.world_path + DIR_DELIM + "auth.txt";
2264 std::string auth_bak_path = auth_txt_path + ".bak";
2265 if (!fs::PathExists(auth_bak_path))
2266 if (fs::Rename(auth_txt_path, auth_bak_path))
2267 actionstream << "Renamed auth.txt to auth.txt.bak"
2270 errorstream << "Could not rename auth.txt to "
2271 "auth.txt.bak" << std::endl;
2273 warningstream << "auth.txt.bak already exists, auth.txt "
2274 "not renamed" << std::endl;
2277 } catch (BaseException &e) {
2278 errorstream << "An error occurred during migration: " << e.what()