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.
20 #include "serverenvironment.h"
21 #include "content_sao.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"
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 ServerEnvironment::ServerEnvironment(ServerMap *map,
388 ServerScripting *scriptIface, Server *server,
389 const std::string &path_world):
392 m_script(scriptIface),
394 m_path_world(path_world)
396 // Determine which database backend to use
397 std::string conf_path = path_world + DIR_DELIM + "world.mt";
400 std::string player_backend_name = "sqlite3";
401 std::string auth_backend_name = "sqlite3";
403 bool succeeded = conf.readConfigFile(conf_path.c_str());
405 // If we open world.mt read the backend configurations.
407 // Read those values before setting defaults
408 bool player_backend_exists = conf.exists("player_backend");
409 bool auth_backend_exists = conf.exists("auth_backend");
411 // player backend is not set, assume it's legacy file backend.
412 if (!player_backend_exists) {
413 // fall back to files
414 conf.set("player_backend", "files");
415 player_backend_name = "files";
417 if (!conf.updateConfigFile(conf_path.c_str())) {
418 errorstream << "ServerEnvironment::ServerEnvironment(): "
419 << "Failed to update world.mt!" << std::endl;
422 conf.getNoEx("player_backend", player_backend_name);
425 // auth backend is not set, assume it's legacy file backend.
426 if (!auth_backend_exists) {
427 conf.set("auth_backend", "files");
428 auth_backend_name = "files";
430 if (!conf.updateConfigFile(conf_path.c_str())) {
431 errorstream << "ServerEnvironment::ServerEnvironment(): "
432 << "Failed to update world.mt!" << std::endl;
435 conf.getNoEx("auth_backend", auth_backend_name);
439 if (player_backend_name == "files") {
440 warningstream << "/!\\ You are using old player file backend. "
441 << "This backend is deprecated and will be removed in a future release /!\\"
442 << std::endl << "Switching to SQLite3 or PostgreSQL is advised, "
443 << "please read http://wiki.minetest.net/Database_backends." << std::endl;
446 if (auth_backend_name == "files") {
447 warningstream << "/!\\ You are using old auth file backend. "
448 << "This backend is deprecated and will be removed in a future release /!\\"
449 << std::endl << "Switching to SQLite3 is advised, "
450 << "please read http://wiki.minetest.net/Database_backends." << std::endl;
453 m_player_database = openPlayerDatabase(player_backend_name, path_world, conf);
454 m_auth_database = openAuthDatabase(auth_backend_name, path_world, conf);
457 ServerEnvironment::~ServerEnvironment()
459 // Clear active block list.
460 // This makes the next one delete all active objects.
461 m_active_blocks.clear();
463 // Convert all objects to static and delete the active objects
464 deactivateFarObjects(true);
469 // Delete ActiveBlockModifiers
470 for (ABMWithState &m_abm : m_abms) {
474 // Deallocate players
475 for (RemotePlayer *m_player : m_players) {
479 delete m_player_database;
480 delete m_auth_database;
483 Map & ServerEnvironment::getMap()
488 ServerMap & ServerEnvironment::getServerMap()
493 RemotePlayer *ServerEnvironment::getPlayer(const session_t peer_id)
495 for (RemotePlayer *player : m_players) {
496 if (player->getPeerId() == peer_id)
502 RemotePlayer *ServerEnvironment::getPlayer(const char* name)
504 for (RemotePlayer *player : m_players) {
505 if (strcmp(player->getName(), name) == 0)
511 void ServerEnvironment::addPlayer(RemotePlayer *player)
514 Check that peer_ids are unique.
515 Also check that names are unique.
516 Exception: there can be multiple players with peer_id=0
518 // If peer id is non-zero, it has to be unique.
519 if (player->getPeerId() != PEER_ID_INEXISTENT)
520 FATAL_ERROR_IF(getPlayer(player->getPeerId()) != NULL, "Peer id not unique");
521 // Name has to be unique.
522 FATAL_ERROR_IF(getPlayer(player->getName()) != NULL, "Player name not unique");
524 m_players.push_back(player);
527 void ServerEnvironment::removePlayer(RemotePlayer *player)
529 for (std::vector<RemotePlayer *>::iterator it = m_players.begin();
530 it != m_players.end(); ++it) {
531 if ((*it) == player) {
539 bool ServerEnvironment::removePlayerFromDatabase(const std::string &name)
541 return m_player_database->removePlayer(name);
544 bool ServerEnvironment::line_of_sight(v3f pos1, v3f pos2, v3s16 *p)
546 // Iterate trough nodes on the line
547 voxalgo::VoxelLineIterator iterator(pos1 / BS, (pos2 - pos1) / BS);
549 MapNode n = getMap().getNodeNoEx(iterator.m_current_node_pos);
552 if (n.param0 != CONTENT_AIR) {
554 *p = iterator.m_current_node_pos;
558 } while (iterator.m_current_index <= iterator.m_last_index);
562 void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason,
563 const std::string &str_reason, bool reconnect)
565 for (RemotePlayer *player : m_players) {
566 m_server->DenyAccessVerCompliant(player->getPeerId(),
567 player->protocol_version, reason, str_reason, reconnect);
571 void ServerEnvironment::saveLoadedPlayers(bool force)
573 for (RemotePlayer *player : m_players) {
574 if (force || player->checkModified() || (player->getPlayerSAO() &&
575 player->getPlayerSAO()->getMeta().isModified())) {
577 m_player_database->savePlayer(player);
578 } catch (DatabaseException &e) {
579 errorstream << "Failed to save player " << player->getName() << " exception: "
580 << e.what() << std::endl;
587 void ServerEnvironment::savePlayer(RemotePlayer *player)
590 m_player_database->savePlayer(player);
591 } catch (DatabaseException &e) {
592 errorstream << "Failed to save player " << player->getName() << " exception: "
593 << e.what() << std::endl;
598 PlayerSAO *ServerEnvironment::loadPlayer(RemotePlayer *player, bool *new_player,
599 session_t peer_id, bool is_singleplayer)
601 PlayerSAO *playersao = new PlayerSAO(this, player, peer_id, is_singleplayer);
602 // Create player if it doesn't exist
603 if (!m_player_database->loadPlayer(player, playersao)) {
605 // Set player position
606 infostream << "Server: Finding spawn place for player \""
607 << player->getName() << "\"" << std::endl;
608 playersao->setBasePosition(m_server->findSpawnPos());
610 // Make sure the player is saved
611 player->setModified(true);
613 // If the player exists, ensure that they respawn inside legal bounds
614 // This fixes an assert crash when the player can't be added
615 // to the environment
616 if (objectpos_over_limit(playersao->getBasePosition())) {
617 actionstream << "Respawn position for player \""
618 << player->getName() << "\" outside limits, resetting" << std::endl;
619 playersao->setBasePosition(m_server->findSpawnPos());
623 // Add player to environment
626 /* Clean up old HUD elements from previous sessions */
629 /* Add object to environment */
630 addActiveObject(playersao);
635 void ServerEnvironment::saveMeta()
637 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
639 // Open file and serialize
640 std::ostringstream ss(std::ios_base::binary);
643 args.setU64("game_time", m_game_time);
644 args.setU64("time_of_day", getTimeOfDay());
645 args.setU64("last_clear_objects_time", m_last_clear_objects_time);
646 args.setU64("lbm_introduction_times_version", 1);
647 args.set("lbm_introduction_times",
648 m_lbm_mgr.createIntroductionTimesString());
649 args.setU64("day_count", m_day_count);
653 if(!fs::safeWriteToFile(path, ss.str()))
655 infostream<<"ServerEnvironment::saveMeta(): Failed to write "
657 throw SerializationError("Couldn't save env meta");
661 void ServerEnvironment::loadMeta()
663 // If file doesn't exist, load default environment metadata
664 if (!fs::PathExists(m_path_world + DIR_DELIM "env_meta.txt")) {
665 infostream << "ServerEnvironment: Loading default environment metadata"
671 infostream << "ServerEnvironment: Loading environment metadata" << std::endl;
673 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
675 // Open file and deserialize
676 std::ifstream is(path.c_str(), std::ios_base::binary);
678 infostream << "ServerEnvironment::loadMeta(): Failed to open "
679 << path << std::endl;
680 throw SerializationError("Couldn't load env meta");
685 if (!args.parseConfigLines(is, "EnvArgsEnd")) {
686 throw SerializationError("ServerEnvironment::loadMeta(): "
687 "EnvArgsEnd not found!");
691 m_game_time = args.getU64("game_time");
692 } catch (SettingNotFoundException &e) {
693 // Getting this is crucial, otherwise timestamps are useless
694 throw SerializationError("Couldn't load env meta game_time");
697 setTimeOfDay(args.exists("time_of_day") ?
698 // set day to early morning by default
699 args.getU64("time_of_day") : 5250);
701 m_last_clear_objects_time = args.exists("last_clear_objects_time") ?
702 // If missing, do as if clearObjects was never called
703 args.getU64("last_clear_objects_time") : 0;
705 std::string lbm_introduction_times;
707 u64 ver = args.getU64("lbm_introduction_times_version");
709 lbm_introduction_times = args.get("lbm_introduction_times");
711 infostream << "ServerEnvironment::loadMeta(): Non-supported"
712 << " introduction time version " << ver << std::endl;
714 } catch (SettingNotFoundException &e) {
715 // No problem, this is expected. Just continue with an empty string
717 m_lbm_mgr.loadIntroductionTimes(lbm_introduction_times, m_server, m_game_time);
719 m_day_count = args.exists("day_count") ?
720 args.getU64("day_count") : 0;
724 * called if env_meta.txt doesn't exist (e.g. new world)
726 void ServerEnvironment::loadDefaultMeta()
728 m_lbm_mgr.loadIntroductionTimes("", m_server, m_game_time);
733 ActiveBlockModifier *abm;
735 std::vector<content_t> required_neighbors;
736 bool check_required_neighbors; // false if required_neighbors is known to be empty
742 ServerEnvironment *m_env;
743 std::vector<std::vector<ActiveABM> *> m_aabms;
745 ABMHandler(std::vector<ABMWithState> &abms,
746 float dtime_s, ServerEnvironment *env,
752 const NodeDefManager *ndef = env->getGameDef()->ndef();
753 for (ABMWithState &abmws : abms) {
754 ActiveBlockModifier *abm = abmws.abm;
755 float trigger_interval = abm->getTriggerInterval();
756 if(trigger_interval < 0.001)
757 trigger_interval = 0.001;
758 float actual_interval = dtime_s;
760 abmws.timer += dtime_s;
761 if(abmws.timer < trigger_interval)
763 abmws.timer -= trigger_interval;
764 actual_interval = trigger_interval;
766 float chance = abm->getTriggerChance();
771 if (abm->getSimpleCatchUp()) {
772 float intervals = actual_interval / trigger_interval;
775 aabm.chance = chance / intervals;
779 aabm.chance = chance;
783 const std::vector<std::string> &required_neighbors_s =
784 abm->getRequiredNeighbors();
785 for (const std::string &required_neighbor_s : required_neighbors_s) {
786 ndef->getIds(required_neighbor_s, aabm.required_neighbors);
788 aabm.check_required_neighbors = !required_neighbors_s.empty();
791 const std::vector<std::string> &contents_s = abm->getTriggerContents();
792 for (const std::string &content_s : contents_s) {
793 std::vector<content_t> ids;
794 ndef->getIds(content_s, ids);
795 for (content_t c : ids) {
796 if (c >= m_aabms.size())
797 m_aabms.resize(c + 256, NULL);
799 m_aabms[c] = new std::vector<ActiveABM>;
800 m_aabms[c]->push_back(aabm);
808 for (auto &aabms : m_aabms)
812 // Find out how many objects the given block and its neighbours contain.
813 // Returns the number of objects in the block, and also in 'wider' the
814 // number of objects in the block and all its neighbours. The latter
815 // may an estimate if any neighbours are unloaded.
816 u32 countObjects(MapBlock *block, ServerMap * map, u32 &wider)
819 u32 wider_unknown_count = 0;
820 for(s16 x=-1; x<=1; x++)
821 for(s16 y=-1; y<=1; y++)
822 for(s16 z=-1; z<=1; z++)
824 MapBlock *block2 = map->getBlockNoCreateNoEx(
825 block->getPos() + v3s16(x,y,z));
827 wider_unknown_count++;
830 wider += block2->m_static_objects.m_active.size()
831 + block2->m_static_objects.m_stored.size();
834 u32 active_object_count = block->m_static_objects.m_active.size();
835 u32 wider_known_count = 3*3*3 - wider_unknown_count;
836 wider += wider_unknown_count * wider / wider_known_count;
837 return active_object_count;
840 void apply(MapBlock *block, int &blocks_scanned, int &abms_run, int &blocks_cached)
842 if(m_aabms.empty() || block->isDummy())
845 // Check the content type cache first
846 // to see whether there are any ABMs
847 // to be run at all for this block.
848 if (block->contents_cached) {
850 bool run_abms = false;
851 for (content_t c : block->contents) {
852 if (c < m_aabms.size() && m_aabms[c]) {
861 block->contents.clear();
865 ServerMap *map = &m_env->getServerMap();
867 u32 active_object_count_wider;
868 u32 active_object_count = this->countObjects(block, map, active_object_count_wider);
869 m_env->m_added_objects = 0;
872 for(p0.X=0; p0.X<MAP_BLOCKSIZE; p0.X++)
873 for(p0.Y=0; p0.Y<MAP_BLOCKSIZE; p0.Y++)
874 for(p0.Z=0; p0.Z<MAP_BLOCKSIZE; p0.Z++)
876 const MapNode &n = block->getNodeUnsafe(p0);
877 content_t c = n.getContent();
878 // Cache content types as we go
879 if (!block->contents_cached && !block->do_not_cache_contents) {
880 block->contents.insert(c);
881 if (block->contents.size() > 64) {
882 // Too many different nodes... don't try to cache
883 block->do_not_cache_contents = true;
884 block->contents.clear();
888 if (c >= m_aabms.size() || !m_aabms[c])
891 v3s16 p = p0 + block->getPosRelative();
892 for (ActiveABM &aabm : *m_aabms[c]) {
893 if (myrand() % aabm.chance != 0)
897 if (aabm.check_required_neighbors) {
899 for(p1.X = p0.X-1; p1.X <= p0.X+1; p1.X++)
900 for(p1.Y = p0.Y-1; p1.Y <= p0.Y+1; p1.Y++)
901 for(p1.Z = p0.Z-1; p1.Z <= p0.Z+1; p1.Z++)
906 if (block->isValidPosition(p1)) {
907 // if the neighbor is found on the same map block
908 // get it straight from there
909 const MapNode &n = block->getNodeUnsafe(p1);
912 // otherwise consult the map
913 MapNode n = map->getNodeNoEx(p1 + block->getPosRelative());
916 if (CONTAINS(aabm.required_neighbors, c))
919 // No required neighbor found
925 // Call all the trigger variations
926 aabm.abm->trigger(m_env, p, n);
927 aabm.abm->trigger(m_env, p, n,
928 active_object_count, active_object_count_wider);
930 // Count surrounding objects again if the abms added any
931 if(m_env->m_added_objects > 0) {
932 active_object_count = countObjects(block, map, active_object_count_wider);
933 m_env->m_added_objects = 0;
937 block->contents_cached = !block->do_not_cache_contents;
941 void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime)
943 // Reset usage timer immediately, otherwise a block that becomes active
944 // again at around the same time as it would normally be unloaded will
945 // get unloaded incorrectly. (I think this still leaves a small possibility
946 // of a race condition between this and server::AsyncRunStep, which only
947 // some kind of synchronisation will fix, but it at least reduces the window
948 // of opportunity for it to break from seconds to nanoseconds)
949 block->resetUsageTimer();
951 // Get time difference
953 u32 stamp = block->getTimestamp();
954 if (m_game_time > stamp && stamp != BLOCK_TIMESTAMP_UNDEFINED)
955 dtime_s = m_game_time - stamp;
956 dtime_s += additional_dtime;
958 /*infostream<<"ServerEnvironment::activateBlock(): block timestamp: "
959 <<stamp<<", game time: "<<m_game_time<<std::endl;*/
961 // Remove stored static objects if clearObjects was called since block's timestamp
962 if (stamp == BLOCK_TIMESTAMP_UNDEFINED || stamp < m_last_clear_objects_time) {
963 block->m_static_objects.m_stored.clear();
964 // do not set changed flag to avoid unnecessary mapblock writes
967 // Set current time as timestamp
968 block->setTimestampNoChangedFlag(m_game_time);
970 /*infostream<<"ServerEnvironment::activateBlock(): block is "
971 <<dtime_s<<" seconds old."<<std::endl;*/
973 // Activate stored objects
974 activateObjects(block, dtime_s);
976 /* Handle LoadingBlockModifiers */
977 m_lbm_mgr.applyLBMs(this, block, stamp);
980 std::vector<NodeTimer> elapsed_timers =
981 block->m_node_timers.step((float)dtime_s);
982 if (!elapsed_timers.empty()) {
984 for (const NodeTimer &elapsed_timer : elapsed_timers) {
985 n = block->getNodeNoEx(elapsed_timer.position);
986 v3s16 p = elapsed_timer.position + block->getPosRelative();
987 if (m_script->node_on_timer(p, n, elapsed_timer.elapsed))
988 block->setNodeTimer(NodeTimer(elapsed_timer.timeout, 0,
989 elapsed_timer.position));
994 void ServerEnvironment::addActiveBlockModifier(ActiveBlockModifier *abm)
996 m_abms.emplace_back(abm);
999 void ServerEnvironment::addLoadingBlockModifierDef(LoadingBlockModifierDef *lbm)
1001 m_lbm_mgr.addLBMDef(lbm);
1004 bool ServerEnvironment::setNode(v3s16 p, const MapNode &n)
1006 const NodeDefManager *ndef = m_server->ndef();
1007 MapNode n_old = m_map->getNodeNoEx(p);
1009 const ContentFeatures &cf_old = ndef->get(n_old);
1012 if (cf_old.has_on_destruct)
1013 m_script->node_on_destruct(p, n_old);
1016 if (!m_map->addNodeWithEvent(p, n))
1019 // Update active VoxelManipulator if a mapgen thread
1020 m_map->updateVManip(p);
1022 // Call post-destructor
1023 if (cf_old.has_after_destruct)
1024 m_script->node_after_destruct(p, n_old);
1026 // Retrieve node content features
1027 // if new node is same as old, reuse old definition to prevent a lookup
1028 const ContentFeatures &cf_new = n_old == n ? cf_old : ndef->get(n);
1031 if (cf_new.has_on_construct)
1032 m_script->node_on_construct(p, n);
1037 bool ServerEnvironment::removeNode(v3s16 p)
1039 const NodeDefManager *ndef = m_server->ndef();
1040 MapNode n_old = m_map->getNodeNoEx(p);
1043 if (ndef->get(n_old).has_on_destruct)
1044 m_script->node_on_destruct(p, n_old);
1047 // This is slightly optimized compared to addNodeWithEvent(air)
1048 if (!m_map->removeNodeWithEvent(p))
1051 // Update active VoxelManipulator if a mapgen thread
1052 m_map->updateVManip(p);
1054 // Call post-destructor
1055 if (ndef->get(n_old).has_after_destruct)
1056 m_script->node_after_destruct(p, n_old);
1058 // Air doesn't require constructor
1062 bool ServerEnvironment::swapNode(v3s16 p, const MapNode &n)
1064 if (!m_map->addNodeWithEvent(p, n, false))
1067 // Update active VoxelManipulator if a mapgen thread
1068 m_map->updateVManip(p);
1073 void ServerEnvironment::clearObjects(ClearObjectsMode mode)
1075 infostream << "ServerEnvironment::clearObjects(): "
1076 << "Removing all active objects" << std::endl;
1077 auto cb_removal = [this] (ServerActiveObject *obj, u16 id) {
1078 if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER)
1081 // Delete static object if block is loaded
1082 deleteStaticFromBlock(obj, id, MOD_REASON_CLEAR_ALL_OBJECTS, true);
1084 // If known by some client, don't delete immediately
1085 if (obj->m_known_by_count > 0) {
1086 obj->m_pending_removal = true;
1090 // Tell the object about removal
1091 obj->removingFromEnvironment();
1092 // Deregister in scripting api
1093 m_script->removeObjectReference(obj);
1095 // Delete active object
1096 if (obj->environmentDeletes())
1102 m_ao_manager.clear(cb_removal);
1104 // Get list of loaded blocks
1105 std::vector<v3s16> loaded_blocks;
1106 infostream << "ServerEnvironment::clearObjects(): "
1107 << "Listing all loaded blocks" << std::endl;
1108 m_map->listAllLoadedBlocks(loaded_blocks);
1109 infostream << "ServerEnvironment::clearObjects(): "
1110 << "Done listing all loaded blocks: "
1111 << loaded_blocks.size()<<std::endl;
1113 // Get list of loadable blocks
1114 std::vector<v3s16> loadable_blocks;
1115 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1116 infostream << "ServerEnvironment::clearObjects(): "
1117 << "Listing all loadable blocks" << std::endl;
1118 m_map->listAllLoadableBlocks(loadable_blocks);
1119 infostream << "ServerEnvironment::clearObjects(): "
1120 << "Done listing all loadable blocks: "
1121 << loadable_blocks.size() << std::endl;
1123 loadable_blocks = loaded_blocks;
1126 actionstream << "ServerEnvironment::clearObjects(): "
1127 << "Now clearing objects in " << loadable_blocks.size()
1128 << " blocks" << std::endl;
1130 // Grab a reference on each loaded block to avoid unloading it
1131 for (v3s16 p : loaded_blocks) {
1132 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1133 assert(block != NULL);
1137 // Remove objects in all loadable blocks
1138 u32 unload_interval = U32_MAX;
1139 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1140 unload_interval = g_settings->getS32("max_clearobjects_extra_loaded_blocks");
1141 unload_interval = MYMAX(unload_interval, 1);
1143 u32 report_interval = loadable_blocks.size() / 10;
1144 u32 num_blocks_checked = 0;
1145 u32 num_blocks_cleared = 0;
1146 u32 num_objs_cleared = 0;
1147 for (auto i = loadable_blocks.begin();
1148 i != loadable_blocks.end(); ++i) {
1150 MapBlock *block = m_map->emergeBlock(p, false);
1152 errorstream << "ServerEnvironment::clearObjects(): "
1153 << "Failed to emerge block " << PP(p) << std::endl;
1156 u32 num_stored = block->m_static_objects.m_stored.size();
1157 u32 num_active = block->m_static_objects.m_active.size();
1158 if (num_stored != 0 || num_active != 0) {
1159 block->m_static_objects.m_stored.clear();
1160 block->m_static_objects.m_active.clear();
1161 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1162 MOD_REASON_CLEAR_ALL_OBJECTS);
1163 num_objs_cleared += num_stored + num_active;
1164 num_blocks_cleared++;
1166 num_blocks_checked++;
1168 if (report_interval != 0 &&
1169 num_blocks_checked % report_interval == 0) {
1170 float percent = 100.0 * (float)num_blocks_checked /
1171 loadable_blocks.size();
1172 actionstream << "ServerEnvironment::clearObjects(): "
1173 << "Cleared " << num_objs_cleared << " objects"
1174 << " in " << num_blocks_cleared << " blocks ("
1175 << percent << "%)" << std::endl;
1177 if (num_blocks_checked % unload_interval == 0) {
1178 m_map->unloadUnreferencedBlocks();
1181 m_map->unloadUnreferencedBlocks();
1183 // Drop references that were added above
1184 for (v3s16 p : loaded_blocks) {
1185 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1190 m_last_clear_objects_time = m_game_time;
1192 actionstream << "ServerEnvironment::clearObjects(): "
1193 << "Finished: Cleared " << num_objs_cleared << " objects"
1194 << " in " << num_blocks_cleared << " blocks" << std::endl;
1197 void ServerEnvironment::step(float dtime)
1199 /* Step time of day */
1200 stepTimeOfDay(dtime);
1203 // NOTE: This is kind of funny on a singleplayer game, but doesn't
1204 // really matter that much.
1205 static thread_local const float server_step =
1206 g_settings->getFloat("dedicated_server_step");
1207 m_recommended_send_interval = server_step;
1213 m_game_time_fraction_counter += dtime;
1214 u32 inc_i = (u32)m_game_time_fraction_counter;
1215 m_game_time += inc_i;
1216 m_game_time_fraction_counter -= (float)inc_i;
1223 ScopeProfiler sp(g_profiler, "SEnv: handle players avg", SPT_AVG);
1224 for (RemotePlayer *player : m_players) {
1225 // Ignore disconnected players
1226 if (player->getPeerId() == PEER_ID_INEXISTENT)
1230 player->move(dtime, this, 100 * BS);
1235 Manage active block list
1237 if (m_active_blocks_management_interval.step(dtime, m_cache_active_block_mgmt_interval)) {
1238 ScopeProfiler sp(g_profiler, "SEnv: manage act. block list avg per interval", SPT_AVG);
1240 Get player block positions
1242 std::vector<PlayerSAO*> players;
1243 for (RemotePlayer *player: m_players) {
1244 // Ignore disconnected players
1245 if (player->getPeerId() == PEER_ID_INEXISTENT)
1248 PlayerSAO *playersao = player->getPlayerSAO();
1251 players.push_back(playersao);
1255 Update list of active blocks, collecting changes
1257 // use active_object_send_range_blocks since that is max distance
1258 // for active objects sent the client anyway
1259 static thread_local const s16 active_object_range =
1260 g_settings->getS16("active_object_send_range_blocks");
1261 static thread_local const s16 active_block_range =
1262 g_settings->getS16("active_block_range");
1263 std::set<v3s16> blocks_removed;
1264 std::set<v3s16> blocks_added;
1265 m_active_blocks.update(players, active_block_range, active_object_range,
1266 blocks_removed, blocks_added);
1269 Handle removed blocks
1272 // Convert active objects that are no more in active blocks to static
1273 deactivateFarObjects(false);
1275 for (const v3s16 &p: blocks_removed) {
1276 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1280 // Set current time as timestamp (and let it set ChangedFlag)
1281 block->setTimestamp(m_game_time);
1288 for (const v3s16 &p: blocks_added) {
1289 MapBlock *block = m_map->getBlockOrEmerge(p);
1291 m_active_blocks.m_list.erase(p);
1292 m_active_blocks.m_abm_list.erase(p);
1296 activateBlock(block);
1301 Mess around in active blocks
1303 if (m_active_blocks_nodemetadata_interval.step(dtime, m_cache_nodetimer_interval)) {
1304 ScopeProfiler sp(g_profiler, "SEnv: mess in act. blocks avg per interval", SPT_AVG);
1306 float dtime = m_cache_nodetimer_interval;
1308 for (const v3s16 &p: m_active_blocks.m_list) {
1309 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1313 // Reset block usage timer
1314 block->resetUsageTimer();
1316 // Set current time as timestamp
1317 block->setTimestampNoChangedFlag(m_game_time);
1318 // If time has changed much from the one on disk,
1319 // set block to be saved when it is unloaded
1320 if(block->getTimestamp() > block->getDiskTimestamp() + 60)
1321 block->raiseModified(MOD_STATE_WRITE_AT_UNLOAD,
1322 MOD_REASON_BLOCK_EXPIRED);
1325 std::vector<NodeTimer> elapsed_timers = block->m_node_timers.step(dtime);
1326 if (!elapsed_timers.empty()) {
1329 for (const NodeTimer &elapsed_timer: elapsed_timers) {
1330 n = block->getNodeNoEx(elapsed_timer.position);
1331 p2 = elapsed_timer.position + block->getPosRelative();
1332 if (m_script->node_on_timer(p2, n, elapsed_timer.elapsed)) {
1333 block->setNodeTimer(NodeTimer(
1334 elapsed_timer.timeout, 0, elapsed_timer.position));
1341 if (m_active_block_modifier_interval.step(dtime, m_cache_abm_interval))
1343 if (m_active_block_interval_overload_skip > 0) {
1344 ScopeProfiler sp(g_profiler, "SEnv: ABM overload skips");
1345 m_active_block_interval_overload_skip--;
1348 ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg per interval", SPT_AVG);
1349 TimeTaker timer("modify in active blocks per interval");
1351 // Initialize handling of ActiveBlockModifiers
1352 ABMHandler abmhandler(m_abms, m_cache_abm_interval, this, true);
1354 int blocks_scanned = 0;
1356 int blocks_cached = 0;
1357 for (const v3s16 &p : m_active_blocks.m_abm_list) {
1358 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1362 // Set current time as timestamp
1363 block->setTimestampNoChangedFlag(m_game_time);
1365 /* Handle ActiveBlockModifiers */
1366 abmhandler.apply(block, blocks_scanned, abms_run, blocks_cached);
1368 g_profiler->avg("SEnv: active blocks", m_active_blocks.m_abm_list.size());
1369 g_profiler->avg("SEnv: active blocks cached", blocks_cached);
1370 g_profiler->avg("SEnv: active blocks scanned for ABMs", blocks_scanned);
1371 g_profiler->avg("SEnv: ABMs run", abms_run);
1373 u32 time_ms = timer.stop(true);
1374 u32 max_time_ms = 200;
1375 if (time_ms > max_time_ms) {
1376 warningstream<<"active block modifiers took "
1377 <<time_ms<<"ms (longer than "
1378 <<max_time_ms<<"ms)"<<std::endl;
1379 m_active_block_interval_overload_skip = (time_ms / max_time_ms) + 1;
1384 Step script environment (run global on_step())
1386 m_script->environment_Step(dtime);
1392 ScopeProfiler sp(g_profiler, "SEnv: step act. objs avg", SPT_AVG);
1394 // This helps the objects to send data at the same time
1395 bool send_recommended = false;
1396 m_send_recommended_timer += dtime;
1397 if (m_send_recommended_timer > getSendRecommendedInterval()) {
1398 m_send_recommended_timer -= getSendRecommendedInterval();
1399 send_recommended = true;
1402 auto cb_state = [this, dtime, send_recommended] (ServerActiveObject *obj) {
1407 obj->step(dtime, send_recommended);
1408 // Read messages from object
1409 while (!obj->m_messages_out.empty()) {
1410 this->m_active_object_messages.push(obj->m_messages_out.front());
1411 obj->m_messages_out.pop();
1414 m_ao_manager.step(dtime, cb_state);
1418 Manage active objects
1420 if (m_object_management_interval.step(dtime, 0.5)) {
1421 ScopeProfiler sp(g_profiler, "SEnv: remove removed objs avg /.5s", SPT_AVG);
1422 removeRemovedObjects();
1426 Manage particle spawner expiration
1428 if (m_particle_management_interval.step(dtime, 1.0)) {
1429 for (std::unordered_map<u32, float>::iterator i = m_particle_spawners.begin();
1430 i != m_particle_spawners.end(); ) {
1431 //non expiring spawners
1432 if (i->second == PARTICLE_SPAWNER_NO_EXPIRY) {
1438 if (i->second <= 0.f)
1439 m_particle_spawners.erase(i++);
1446 u32 ServerEnvironment::addParticleSpawner(float exptime)
1448 // Timers with lifetime 0 do not expire
1449 float time = exptime > 0.f ? exptime : PARTICLE_SPAWNER_NO_EXPIRY;
1452 for (;;) { // look for unused particlespawner id
1454 std::unordered_map<u32, float>::iterator f = m_particle_spawners.find(id);
1455 if (f == m_particle_spawners.end()) {
1456 m_particle_spawners[id] = time;
1463 u32 ServerEnvironment::addParticleSpawner(float exptime, u16 attached_id)
1465 u32 id = addParticleSpawner(exptime);
1466 m_particle_spawner_attachments[id] = attached_id;
1467 if (ServerActiveObject *obj = getActiveObject(attached_id)) {
1468 obj->attachParticleSpawner(id);
1473 void ServerEnvironment::deleteParticleSpawner(u32 id, bool remove_from_object)
1475 m_particle_spawners.erase(id);
1476 const auto &it = m_particle_spawner_attachments.find(id);
1477 if (it != m_particle_spawner_attachments.end()) {
1478 u16 obj_id = it->second;
1479 ServerActiveObject *sao = getActiveObject(obj_id);
1480 if (sao != NULL && remove_from_object) {
1481 sao->detachParticleSpawner(id);
1483 m_particle_spawner_attachments.erase(id);
1487 u16 ServerEnvironment::addActiveObject(ServerActiveObject *object)
1489 assert(object); // Pre-condition
1491 u16 id = addActiveObjectRaw(object, true, 0);
1496 Finds out what new objects have been added to
1497 inside a radius around a position
1499 void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius,
1501 std::set<u16> ¤t_objects,
1502 std::queue<u16> &added_objects)
1504 f32 radius_f = radius * BS;
1505 f32 player_radius_f = player_radius * BS;
1507 if (player_radius_f < 0.0f)
1508 player_radius_f = 0.0f;
1510 m_ao_manager.getAddedActiveObjectsAroundPos(playersao->getBasePosition(), radius_f,
1511 player_radius_f, current_objects, added_objects);
1515 Finds out what objects have been removed from
1516 inside a radius around a position
1518 void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius,
1520 std::set<u16> ¤t_objects,
1521 std::queue<u16> &removed_objects)
1523 f32 radius_f = radius * BS;
1524 f32 player_radius_f = player_radius * BS;
1526 if (player_radius_f < 0)
1527 player_radius_f = 0;
1529 Go through current_objects; object is removed if:
1530 - object is not found in m_active_objects (this is actually an
1531 error condition; objects should be removed only after all clients
1532 have been informed about removal), or
1533 - object is to be removed or deactivated, or
1534 - object is too far away
1536 for (u16 id : current_objects) {
1537 ServerActiveObject *object = getActiveObject(id);
1539 if (object == NULL) {
1540 infostream << "ServerEnvironment::getRemovedActiveObjects():"
1541 << " object in current_objects is NULL" << std::endl;
1542 removed_objects.push(id);
1546 if (object->isGone()) {
1547 removed_objects.push(id);
1551 f32 distance_f = object->getBasePosition().getDistanceFrom(playersao->getBasePosition());
1552 if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1553 if (distance_f <= player_radius_f || player_radius_f == 0)
1555 } else if (distance_f <= radius_f)
1558 // Object is no longer visible
1559 removed_objects.push(id);
1563 void ServerEnvironment::setStaticForActiveObjectsInBlock(
1564 v3s16 blockpos, bool static_exists, v3s16 static_block)
1566 MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos);
1570 for (auto &so_it : block->m_static_objects.m_active) {
1571 // Get the ServerActiveObject counterpart to this StaticObject
1572 ServerActiveObject *sao = m_ao_manager.getActiveObject(so_it.first);
1574 // If this ever happens, there must be some kind of nasty bug.
1575 errorstream << "ServerEnvironment::setStaticForObjectsInBlock(): "
1576 "Object from MapBlock::m_static_objects::m_active not found "
1577 "in m_active_objects";
1581 sao->m_static_exists = static_exists;
1582 sao->m_static_block = static_block;
1586 ActiveObjectMessage ServerEnvironment::getActiveObjectMessage()
1588 if(m_active_object_messages.empty())
1589 return ActiveObjectMessage(0);
1591 ActiveObjectMessage message = m_active_object_messages.front();
1592 m_active_object_messages.pop();
1596 void ServerEnvironment::getSelectedActiveObjects(
1597 const core::line3d<f32> &shootline_on_map,
1598 std::vector<PointedThing> &objects)
1600 std::vector<u16> objectIds;
1601 getObjectsInsideRadius(objectIds, shootline_on_map.start,
1602 shootline_on_map.getLength() + 10.0f);
1603 const v3f line_vector = shootline_on_map.getVector();
1605 for (u16 objectId : objectIds) {
1606 ServerActiveObject* obj = getActiveObject(objectId);
1608 aabb3f selection_box;
1609 if (!obj->getSelectionBox(&selection_box))
1612 v3f pos = obj->getBasePosition();
1614 aabb3f offsetted_box(selection_box.MinEdge + pos,
1615 selection_box.MaxEdge + pos);
1617 v3f current_intersection;
1618 v3s16 current_normal;
1619 if (boxLineCollision(offsetted_box, shootline_on_map.start, line_vector,
1620 ¤t_intersection, ¤t_normal)) {
1621 objects.emplace_back(
1622 (s16) objectId, current_intersection, current_normal,
1623 (current_intersection - shootline_on_map.start).getLengthSQ());
1629 ************ Private methods *************
1632 u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object,
1633 bool set_changed, u32 dtime_s)
1635 if (!m_ao_manager.registerObject(object)) {
1639 // Register reference in scripting api (must be done before post-init)
1640 m_script->addObjectReference(object);
1641 // Post-initialize object
1642 object->addedToEnvironment(dtime_s);
1644 // Add static data to block
1645 if (object->isStaticAllowed()) {
1646 // Add static object to active static list of the block
1647 v3f objectpos = object->getBasePosition();
1648 StaticObject s_obj(object, objectpos);
1649 // Add to the block where the object is located in
1650 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
1651 MapBlock *block = m_map->emergeBlock(blockpos);
1653 block->m_static_objects.m_active[object->getId()] = s_obj;
1654 object->m_static_exists = true;
1655 object->m_static_block = blockpos;
1658 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1659 MOD_REASON_ADD_ACTIVE_OBJECT_RAW);
1661 v3s16 p = floatToInt(objectpos, BS);
1662 errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
1663 <<"could not emerge block for storing id="<<object->getId()
1664 <<" statically (pos="<<PP(p)<<")"<<std::endl;
1668 return object->getId();
1672 Remove objects that satisfy (isGone() && m_known_by_count==0)
1674 void ServerEnvironment::removeRemovedObjects()
1676 auto clear_cb = [this] (ServerActiveObject *obj, u16 id) {
1677 // This shouldn't happen but check it
1679 errorstream << "ServerEnvironment::removeRemovedObjects(): "
1680 << "NULL object found. id=" << id << std::endl;
1685 We will handle objects marked for removal or deactivation
1691 Delete static data from block if removed
1693 if (obj->m_pending_removal)
1694 deleteStaticFromBlock(obj, id, MOD_REASON_REMOVE_OBJECTS_REMOVE, false);
1696 // If still known by clients, don't actually remove. On some future
1697 // invocation this will be 0, which is when removal will continue.
1698 if(obj->m_known_by_count > 0)
1702 Move static data from active to stored if deactivated
1704 if (!obj->m_pending_removal && obj->m_static_exists) {
1705 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
1707 const auto i = block->m_static_objects.m_active.find(id);
1708 if (i != block->m_static_objects.m_active.end()) {
1709 block->m_static_objects.m_stored.push_back(i->second);
1710 block->m_static_objects.m_active.erase(id);
1711 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1712 MOD_REASON_REMOVE_OBJECTS_DEACTIVATE);
1714 warningstream << "ServerEnvironment::removeRemovedObjects(): "
1715 << "id=" << id << " m_static_exists=true but "
1716 << "static data doesn't actually exist in "
1717 << PP(obj->m_static_block) << std::endl;
1720 infostream << "Failed to emerge block from which an object to "
1721 << "be deactivated was loaded from. id=" << id << std::endl;
1725 // Tell the object about removal
1726 obj->removingFromEnvironment();
1727 // Deregister in scripting api
1728 m_script->removeObjectReference(obj);
1731 if (obj->environmentDeletes())
1737 m_ao_manager.clear(clear_cb);
1740 static void print_hexdump(std::ostream &o, const std::string &data)
1742 const int linelength = 16;
1743 for(int l=0; ; l++){
1744 int i0 = linelength * l;
1745 bool at_end = false;
1746 int thislinelength = linelength;
1747 if(i0 + thislinelength > (int)data.size()){
1748 thislinelength = data.size() - i0;
1751 for(int di=0; di<linelength; di++){
1754 if(di<thislinelength)
1755 porting::mt_snprintf(buf, sizeof(buf), "%.2x ", data[i]);
1757 porting::mt_snprintf(buf, sizeof(buf), " ");
1761 for(int di=0; di<thislinelength; di++){
1775 Convert stored objects from blocks near the players to active.
1777 void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s)
1782 // Ignore if no stored objects (to not set changed flag)
1783 if(block->m_static_objects.m_stored.empty())
1786 verbosestream<<"ServerEnvironment::activateObjects(): "
1787 <<"activating objects of block "<<PP(block->getPos())
1788 <<" ("<<block->m_static_objects.m_stored.size()
1789 <<" objects)"<<std::endl;
1790 bool large_amount = (block->m_static_objects.m_stored.size() > g_settings->getU16("max_objects_per_block"));
1792 errorstream<<"suspiciously large amount of objects detected: "
1793 <<block->m_static_objects.m_stored.size()<<" in "
1794 <<PP(block->getPos())
1795 <<"; removing all of them."<<std::endl;
1796 // Clear stored list
1797 block->m_static_objects.m_stored.clear();
1798 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1799 MOD_REASON_TOO_MANY_OBJECTS);
1803 // Activate stored objects
1804 std::vector<StaticObject> new_stored;
1805 for (const StaticObject &s_obj : block->m_static_objects.m_stored) {
1806 // Create an active object from the data
1807 ServerActiveObject *obj = ServerActiveObject::create
1808 ((ActiveObjectType) s_obj.type, this, 0, s_obj.pos, s_obj.data);
1809 // If couldn't create object, store static data back.
1811 errorstream<<"ServerEnvironment::activateObjects(): "
1812 <<"failed to create active object from static object "
1813 <<"in block "<<PP(s_obj.pos/BS)
1814 <<" type="<<(int)s_obj.type<<" data:"<<std::endl;
1815 print_hexdump(verbosestream, s_obj.data);
1817 new_stored.push_back(s_obj);
1820 verbosestream<<"ServerEnvironment::activateObjects(): "
1821 <<"activated static object pos="<<PP(s_obj.pos/BS)
1822 <<" type="<<(int)s_obj.type<<std::endl;
1823 // This will also add the object to the active static list
1824 addActiveObjectRaw(obj, false, dtime_s);
1827 // Clear stored list
1828 block->m_static_objects.m_stored.clear();
1829 // Add leftover failed stuff to stored list
1830 for (const StaticObject &s_obj : new_stored) {
1831 block->m_static_objects.m_stored.push_back(s_obj);
1835 Note: Block hasn't really been modified here.
1836 The objects have just been activated and moved from the stored
1837 static list to the active static list.
1838 As such, the block is essentially the same.
1839 Thus, do not call block->raiseModified(MOD_STATE_WRITE_NEEDED).
1840 Otherwise there would be a huge amount of unnecessary I/O.
1845 Convert objects that are not standing inside active blocks to static.
1847 If m_known_by_count != 0, active object is not deleted, but static
1848 data is still updated.
1850 If force_delete is set, active object is deleted nevertheless. It
1851 shall only be set so in the destructor of the environment.
1853 If block wasn't generated (not in memory or on disk),
1855 void ServerEnvironment::deactivateFarObjects(bool _force_delete)
1857 auto cb_deactivate = [this, _force_delete] (ServerActiveObject *obj, u16 id) {
1858 // force_delete might be overriden per object
1859 bool force_delete = _force_delete;
1861 // Do not deactivate if static data creation not allowed
1862 if (!force_delete && !obj->isStaticAllowed())
1865 // removeRemovedObjects() is responsible for these
1866 if (!force_delete && obj->isGone())
1869 const v3f &objectpos = obj->getBasePosition();
1871 // The block in which the object resides in
1872 v3s16 blockpos_o = getNodeBlockPos(floatToInt(objectpos, BS));
1874 // If object's static data is stored in a deactivated block and object
1875 // is actually located in an active block, re-save to the block in
1876 // which the object is actually located in.
1877 if (!force_delete && obj->m_static_exists &&
1878 !m_active_blocks.contains(obj->m_static_block) &&
1879 m_active_blocks.contains(blockpos_o)) {
1880 // Delete from block where object was located
1881 deleteStaticFromBlock(obj, id, MOD_REASON_STATIC_DATA_REMOVED, false);
1883 StaticObject s_obj(obj, objectpos);
1884 // Save to block where object is located
1885 saveStaticToBlock(blockpos_o, id, obj, s_obj, MOD_REASON_STATIC_DATA_ADDED);
1890 // If block is still active, don't remove
1891 if (!force_delete && m_active_blocks.contains(blockpos_o))
1894 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1895 << "deactivating object id=" << id << " on inactive block "
1896 << PP(blockpos_o) << std::endl;
1898 // If known by some client, don't immediately delete.
1899 bool pending_delete = (obj->m_known_by_count > 0 && !force_delete);
1902 Update the static data
1904 if (obj->isStaticAllowed()) {
1905 // Create new static object
1906 StaticObject s_obj(obj, objectpos);
1908 bool stays_in_same_block = false;
1909 bool data_changed = true;
1911 // Check if static data has changed considerably
1912 if (obj->m_static_exists) {
1913 if (obj->m_static_block == blockpos_o)
1914 stays_in_same_block = true;
1916 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
1919 const auto n = block->m_static_objects.m_active.find(id);
1920 if (n != block->m_static_objects.m_active.end()) {
1921 StaticObject static_old = n->second;
1923 float save_movem = obj->getMinimumSavedMovement();
1925 if (static_old.data == s_obj.data &&
1926 (static_old.pos - objectpos).getLength() < save_movem)
1927 data_changed = false;
1929 warningstream << "ServerEnvironment::deactivateFarObjects(): "
1930 << "id=" << id << " m_static_exists=true but "
1931 << "static data doesn't actually exist in "
1932 << PP(obj->m_static_block) << std::endl;
1938 While changes are always saved, blocks are only marked as modified
1939 if the object has moved or different staticdata. (see above)
1941 bool shall_be_written = (!stays_in_same_block || data_changed);
1942 u32 reason = shall_be_written ? MOD_REASON_STATIC_DATA_CHANGED : MOD_REASON_UNKNOWN;
1944 // Delete old static object
1945 deleteStaticFromBlock(obj, id, reason, false);
1947 // Add to the block where the object is located in
1948 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
1949 u16 store_id = pending_delete ? id : 0;
1950 if (!saveStaticToBlock(blockpos, store_id, obj, s_obj, reason))
1951 force_delete = true;
1955 If known by some client, set pending deactivation.
1956 Otherwise delete it immediately.
1958 if (pending_delete && !force_delete) {
1959 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1960 << "object id=" << id << " is known by clients"
1961 << "; not deleting yet" << std::endl;
1963 obj->m_pending_deactivation = true;
1967 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1968 << "object id=" << id << " is not known by clients"
1969 << "; deleting" << std::endl;
1971 // Tell the object about removal
1972 obj->removingFromEnvironment();
1973 // Deregister in scripting api
1974 m_script->removeObjectReference(obj);
1976 // Delete active object
1977 if (obj->environmentDeletes())
1983 m_ao_manager.clear(cb_deactivate);
1986 void ServerEnvironment::deleteStaticFromBlock(
1987 ServerActiveObject *obj, u16 id, u32 mod_reason, bool no_emerge)
1989 if (!obj->m_static_exists)
1994 block = m_map->getBlockNoCreateNoEx(obj->m_static_block);
1996 block = m_map->emergeBlock(obj->m_static_block, false);
1999 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2000 << " when deleting static data of object from it. id=" << id << std::endl;
2004 block->m_static_objects.remove(id);
2005 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2006 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2008 obj->m_static_exists = false;
2011 bool ServerEnvironment::saveStaticToBlock(
2012 v3s16 blockpos, u16 store_id,
2013 ServerActiveObject *obj, const StaticObject &s_obj,
2016 MapBlock *block = nullptr;
2018 block = m_map->emergeBlock(blockpos);
2019 } catch (InvalidPositionException &e) {
2020 // Handled via NULL pointer
2021 // NOTE: emergeBlock's failure is usually determined by it
2022 // actually returning NULL
2026 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2027 << " when saving static data of object to it. id=" << store_id << std::endl;
2030 if (block->m_static_objects.m_stored.size() >= g_settings->getU16("max_objects_per_block")) {
2031 warningstream << "ServerEnv: Trying to store id = " << store_id
2032 << " statically but block " << PP(blockpos)
2033 << " already contains "
2034 << block->m_static_objects.m_stored.size()
2035 << " objects." << std::endl;
2039 block->m_static_objects.insert(store_id, s_obj);
2040 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2041 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2043 obj->m_static_exists = true;
2044 obj->m_static_block = blockpos;
2049 PlayerDatabase *ServerEnvironment::openPlayerDatabase(const std::string &name,
2050 const std::string &savedir, const Settings &conf)
2053 if (name == "sqlite3")
2054 return new PlayerDatabaseSQLite3(savedir);
2056 if (name == "dummy")
2057 return new Database_Dummy();
2059 if (name == "postgresql") {
2060 std::string connect_string;
2061 conf.getNoEx("pgsql_player_connection", connect_string);
2062 return new PlayerDatabasePostgreSQL(connect_string);
2065 if (name == "files")
2066 return new PlayerDatabaseFiles(savedir + DIR_DELIM + "players");
2068 throw BaseException(std::string("Database backend ") + name + " not supported.");
2071 bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params,
2072 const Settings &cmd_args)
2074 std::string migrate_to = cmd_args.get("migrate-players");
2076 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2077 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2078 errorstream << "Cannot read world.mt!" << std::endl;
2082 if (!world_mt.exists("player_backend")) {
2083 errorstream << "Please specify your current backend in world.mt:"
2085 << " player_backend = {files|sqlite3|postgresql}"
2090 std::string backend = world_mt.get("player_backend");
2091 if (backend == migrate_to) {
2092 errorstream << "Cannot migrate: new backend is same"
2093 << " as the old one" << std::endl;
2097 const std::string players_backup_path = game_params.world_path + DIR_DELIM
2100 if (backend == "files") {
2101 // Create backup directory
2102 fs::CreateDir(players_backup_path);
2106 PlayerDatabase *srcdb = ServerEnvironment::openPlayerDatabase(backend,
2107 game_params.world_path, world_mt);
2108 PlayerDatabase *dstdb = ServerEnvironment::openPlayerDatabase(migrate_to,
2109 game_params.world_path, world_mt);
2111 std::vector<std::string> player_list;
2112 srcdb->listPlayers(player_list);
2113 for (std::vector<std::string>::const_iterator it = player_list.begin();
2114 it != player_list.end(); ++it) {
2115 actionstream << "Migrating player " << it->c_str() << std::endl;
2116 RemotePlayer player(it->c_str(), NULL);
2117 PlayerSAO playerSAO(NULL, &player, 15000, false);
2119 srcdb->loadPlayer(&player, &playerSAO);
2121 playerSAO.finalize(&player, std::set<std::string>());
2122 player.setPlayerSAO(&playerSAO);
2124 dstdb->savePlayer(&player);
2126 // For files source, move player files to backup dir
2127 if (backend == "files") {
2129 game_params.world_path + DIR_DELIM + "players" + DIR_DELIM + (*it),
2130 players_backup_path + DIR_DELIM + (*it));
2134 actionstream << "Successfully migrated " << player_list.size() << " players"
2136 world_mt.set("player_backend", migrate_to);
2137 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2138 errorstream << "Failed to update world.mt!" << std::endl;
2140 actionstream << "world.mt updated" << std::endl;
2142 // When migration is finished from file backend, remove players directory if empty
2143 if (backend == "files") {
2144 fs::DeleteSingleFileOrEmptyDirectory(game_params.world_path + DIR_DELIM
2151 } catch (BaseException &e) {
2152 errorstream << "An error occurred during migration: " << e.what() << std::endl;
2158 AuthDatabase *ServerEnvironment::openAuthDatabase(
2159 const std::string &name, const std::string &savedir, const Settings &conf)
2161 if (name == "sqlite3")
2162 return new AuthDatabaseSQLite3(savedir);
2164 if (name == "files")
2165 return new AuthDatabaseFiles(savedir);
2167 throw BaseException(std::string("Database backend ") + name + " not supported.");
2170 bool ServerEnvironment::migrateAuthDatabase(
2171 const GameParams &game_params, const Settings &cmd_args)
2173 std::string migrate_to = cmd_args.get("migrate-auth");
2175 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2176 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2177 errorstream << "Cannot read world.mt!" << std::endl;
2181 std::string backend = "files";
2182 if (world_mt.exists("auth_backend"))
2183 backend = world_mt.get("auth_backend");
2185 warningstream << "No auth_backend found in world.mt, "
2186 "assuming \"files\"." << std::endl;
2188 if (backend == migrate_to) {
2189 errorstream << "Cannot migrate: new backend is same"
2190 << " as the old one" << std::endl;
2195 const std::unique_ptr<AuthDatabase> srcdb(ServerEnvironment::openAuthDatabase(
2196 backend, game_params.world_path, world_mt));
2197 const std::unique_ptr<AuthDatabase> dstdb(ServerEnvironment::openAuthDatabase(
2198 migrate_to, game_params.world_path, world_mt));
2200 std::vector<std::string> names_list;
2201 srcdb->listNames(names_list);
2202 for (const std::string &name : names_list) {
2203 actionstream << "Migrating auth entry for " << name << std::endl;
2205 AuthEntry authEntry;
2206 success = srcdb->getAuth(name, authEntry);
2207 success = success && dstdb->createAuth(authEntry);
2209 errorstream << "Failed to migrate " << name << std::endl;
2212 actionstream << "Successfully migrated " << names_list.size()
2213 << " auth entries" << std::endl;
2214 world_mt.set("auth_backend", migrate_to);
2215 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2216 errorstream << "Failed to update world.mt!" << std::endl;
2218 actionstream << "world.mt updated" << std::endl;
2220 if (backend == "files") {
2221 // special-case files migration:
2222 // move auth.txt to auth.txt.bak if possible
2223 std::string auth_txt_path =
2224 game_params.world_path + DIR_DELIM + "auth.txt";
2225 std::string auth_bak_path = auth_txt_path + ".bak";
2226 if (!fs::PathExists(auth_bak_path))
2227 if (fs::Rename(auth_txt_path, auth_bak_path))
2228 actionstream << "Renamed auth.txt to auth.txt.bak"
2231 errorstream << "Could not rename auth.txt to "
2232 "auth.txt.bak" << std::endl;
2234 warningstream << "auth.txt.bak already exists, auth.txt "
2235 "not renamed" << std::endl;
2238 } catch (BaseException &e) {
2239 errorstream << "An error occurred during migration: " << e.what()