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";
399 bool succeeded = conf.readConfigFile(conf_path.c_str());
400 if (!succeeded || !conf.exists("player_backend")) {
401 // fall back to files
402 conf.set("player_backend", "files");
403 warningstream << "/!\\ You are using old player file backend. "
404 << "This backend is deprecated and will be removed in next release /!\\"
405 << std::endl << "Switching to SQLite3 or PostgreSQL is advised, "
406 << "please read http://wiki.minetest.net/Database_backends." << std::endl;
408 if (!conf.updateConfigFile(conf_path.c_str())) {
409 errorstream << "ServerEnvironment::ServerEnvironment(): "
410 << "Failed to update world.mt!" << std::endl;
415 conf.getNoEx("player_backend", name);
416 m_player_database = openPlayerDatabase(name, path_world, conf);
418 std::string auth_name = "files";
419 if (conf.exists("auth_backend")) {
420 conf.getNoEx("auth_backend", auth_name);
422 conf.set("auth_backend", "files");
423 if (!conf.updateConfigFile(conf_path.c_str())) {
424 errorstream << "ServerEnvironment::ServerEnvironment(): "
425 << "Failed to update world.mt!" << std::endl;
428 m_auth_database = openAuthDatabase(auth_name, path_world, conf);
431 ServerEnvironment::~ServerEnvironment()
433 // Clear active block list.
434 // This makes the next one delete all active objects.
435 m_active_blocks.clear();
437 // Convert all objects to static and delete the active objects
438 deactivateFarObjects(true);
443 // Delete ActiveBlockModifiers
444 for (ABMWithState &m_abm : m_abms) {
448 // Deallocate players
449 for (RemotePlayer *m_player : m_players) {
453 delete m_player_database;
454 delete m_auth_database;
457 Map & ServerEnvironment::getMap()
462 ServerMap & ServerEnvironment::getServerMap()
467 RemotePlayer *ServerEnvironment::getPlayer(const session_t peer_id)
469 for (RemotePlayer *player : m_players) {
470 if (player->getPeerId() == peer_id)
476 RemotePlayer *ServerEnvironment::getPlayer(const char* name)
478 for (RemotePlayer *player : m_players) {
479 if (strcmp(player->getName(), name) == 0)
485 void ServerEnvironment::addPlayer(RemotePlayer *player)
488 Check that peer_ids are unique.
489 Also check that names are unique.
490 Exception: there can be multiple players with peer_id=0
492 // If peer id is non-zero, it has to be unique.
493 if (player->getPeerId() != PEER_ID_INEXISTENT)
494 FATAL_ERROR_IF(getPlayer(player->getPeerId()) != NULL, "Peer id not unique");
495 // Name has to be unique.
496 FATAL_ERROR_IF(getPlayer(player->getName()) != NULL, "Player name not unique");
498 m_players.push_back(player);
501 void ServerEnvironment::removePlayer(RemotePlayer *player)
503 for (std::vector<RemotePlayer *>::iterator it = m_players.begin();
504 it != m_players.end(); ++it) {
505 if ((*it) == player) {
513 bool ServerEnvironment::removePlayerFromDatabase(const std::string &name)
515 return m_player_database->removePlayer(name);
518 bool ServerEnvironment::line_of_sight(v3f pos1, v3f pos2, v3s16 *p)
520 // Iterate trough nodes on the line
521 voxalgo::VoxelLineIterator iterator(pos1 / BS, (pos2 - pos1) / BS);
523 MapNode n = getMap().getNodeNoEx(iterator.m_current_node_pos);
526 if (n.param0 != CONTENT_AIR) {
528 *p = iterator.m_current_node_pos;
532 } while (iterator.m_current_index <= iterator.m_last_index);
536 void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason,
537 const std::string &str_reason, bool reconnect)
539 for (RemotePlayer *player : m_players) {
540 m_server->DenyAccessVerCompliant(player->getPeerId(),
541 player->protocol_version, reason, str_reason, reconnect);
545 void ServerEnvironment::saveLoadedPlayers()
547 for (RemotePlayer *player : m_players) {
548 if (player->checkModified() || (player->getPlayerSAO() &&
549 player->getPlayerSAO()->getMeta().isModified())) {
551 m_player_database->savePlayer(player);
552 } catch (DatabaseException &e) {
553 errorstream << "Failed to save player " << player->getName() << " exception: "
554 << e.what() << std::endl;
561 void ServerEnvironment::savePlayer(RemotePlayer *player)
564 m_player_database->savePlayer(player);
565 } catch (DatabaseException &e) {
566 errorstream << "Failed to save player " << player->getName() << " exception: "
567 << e.what() << std::endl;
572 PlayerSAO *ServerEnvironment::loadPlayer(RemotePlayer *player, bool *new_player,
573 session_t peer_id, bool is_singleplayer)
575 PlayerSAO *playersao = new PlayerSAO(this, player, peer_id, is_singleplayer);
576 // Create player if it doesn't exist
577 if (!m_player_database->loadPlayer(player, playersao)) {
579 // Set player position
580 infostream << "Server: Finding spawn place for player \""
581 << player->getName() << "\"" << std::endl;
582 playersao->setBasePosition(m_server->findSpawnPos());
584 // Make sure the player is saved
585 player->setModified(true);
587 // If the player exists, ensure that they respawn inside legal bounds
588 // This fixes an assert crash when the player can't be added
589 // to the environment
590 if (objectpos_over_limit(playersao->getBasePosition())) {
591 actionstream << "Respawn position for player \""
592 << player->getName() << "\" outside limits, resetting" << std::endl;
593 playersao->setBasePosition(m_server->findSpawnPos());
597 // Add player to environment
600 /* Clean up old HUD elements from previous sessions */
603 /* Add object to environment */
604 addActiveObject(playersao);
609 void ServerEnvironment::saveMeta()
611 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
613 // Open file and serialize
614 std::ostringstream ss(std::ios_base::binary);
617 args.setU64("game_time", m_game_time);
618 args.setU64("time_of_day", getTimeOfDay());
619 args.setU64("last_clear_objects_time", m_last_clear_objects_time);
620 args.setU64("lbm_introduction_times_version", 1);
621 args.set("lbm_introduction_times",
622 m_lbm_mgr.createIntroductionTimesString());
623 args.setU64("day_count", m_day_count);
627 if(!fs::safeWriteToFile(path, ss.str()))
629 infostream<<"ServerEnvironment::saveMeta(): Failed to write "
631 throw SerializationError("Couldn't save env meta");
635 void ServerEnvironment::loadMeta()
637 // If file doesn't exist, load default environment metadata
638 if (!fs::PathExists(m_path_world + DIR_DELIM "env_meta.txt")) {
639 infostream << "ServerEnvironment: Loading default environment metadata"
645 infostream << "ServerEnvironment: Loading environment metadata" << std::endl;
647 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
649 // Open file and deserialize
650 std::ifstream is(path.c_str(), std::ios_base::binary);
652 infostream << "ServerEnvironment::loadMeta(): Failed to open "
653 << path << std::endl;
654 throw SerializationError("Couldn't load env meta");
659 if (!args.parseConfigLines(is, "EnvArgsEnd")) {
660 throw SerializationError("ServerEnvironment::loadMeta(): "
661 "EnvArgsEnd not found!");
665 m_game_time = args.getU64("game_time");
666 } catch (SettingNotFoundException &e) {
667 // Getting this is crucial, otherwise timestamps are useless
668 throw SerializationError("Couldn't load env meta game_time");
671 setTimeOfDay(args.exists("time_of_day") ?
672 // set day to early morning by default
673 args.getU64("time_of_day") : 5250);
675 m_last_clear_objects_time = args.exists("last_clear_objects_time") ?
676 // If missing, do as if clearObjects was never called
677 args.getU64("last_clear_objects_time") : 0;
679 std::string lbm_introduction_times;
681 u64 ver = args.getU64("lbm_introduction_times_version");
683 lbm_introduction_times = args.get("lbm_introduction_times");
685 infostream << "ServerEnvironment::loadMeta(): Non-supported"
686 << " introduction time version " << ver << std::endl;
688 } catch (SettingNotFoundException &e) {
689 // No problem, this is expected. Just continue with an empty string
691 m_lbm_mgr.loadIntroductionTimes(lbm_introduction_times, m_server, m_game_time);
693 m_day_count = args.exists("day_count") ?
694 args.getU64("day_count") : 0;
698 * called if env_meta.txt doesn't exist (e.g. new world)
700 void ServerEnvironment::loadDefaultMeta()
702 m_lbm_mgr.loadIntroductionTimes("", m_server, m_game_time);
707 ActiveBlockModifier *abm;
709 std::vector<content_t> required_neighbors;
710 bool check_required_neighbors; // false if required_neighbors is known to be empty
716 ServerEnvironment *m_env;
717 std::vector<std::vector<ActiveABM> *> m_aabms;
719 ABMHandler(std::vector<ABMWithState> &abms,
720 float dtime_s, ServerEnvironment *env,
726 const NodeDefManager *ndef = env->getGameDef()->ndef();
727 for (ABMWithState &abmws : abms) {
728 ActiveBlockModifier *abm = abmws.abm;
729 float trigger_interval = abm->getTriggerInterval();
730 if(trigger_interval < 0.001)
731 trigger_interval = 0.001;
732 float actual_interval = dtime_s;
734 abmws.timer += dtime_s;
735 if(abmws.timer < trigger_interval)
737 abmws.timer -= trigger_interval;
738 actual_interval = trigger_interval;
740 float chance = abm->getTriggerChance();
745 if (abm->getSimpleCatchUp()) {
746 float intervals = actual_interval / trigger_interval;
749 aabm.chance = chance / intervals;
753 aabm.chance = chance;
757 const std::vector<std::string> &required_neighbors_s =
758 abm->getRequiredNeighbors();
759 for (const std::string &required_neighbor_s : required_neighbors_s) {
760 ndef->getIds(required_neighbor_s, aabm.required_neighbors);
762 aabm.check_required_neighbors = !required_neighbors_s.empty();
765 const std::vector<std::string> &contents_s = abm->getTriggerContents();
766 for (const std::string &content_s : contents_s) {
767 std::vector<content_t> ids;
768 ndef->getIds(content_s, ids);
769 for (content_t c : ids) {
770 if (c >= m_aabms.size())
771 m_aabms.resize(c + 256, NULL);
773 m_aabms[c] = new std::vector<ActiveABM>;
774 m_aabms[c]->push_back(aabm);
782 for (auto &aabms : m_aabms)
786 // Find out how many objects the given block and its neighbours contain.
787 // Returns the number of objects in the block, and also in 'wider' the
788 // number of objects in the block and all its neighbours. The latter
789 // may an estimate if any neighbours are unloaded.
790 u32 countObjects(MapBlock *block, ServerMap * map, u32 &wider)
793 u32 wider_unknown_count = 0;
794 for(s16 x=-1; x<=1; x++)
795 for(s16 y=-1; y<=1; y++)
796 for(s16 z=-1; z<=1; z++)
798 MapBlock *block2 = map->getBlockNoCreateNoEx(
799 block->getPos() + v3s16(x,y,z));
801 wider_unknown_count++;
804 wider += block2->m_static_objects.m_active.size()
805 + block2->m_static_objects.m_stored.size();
808 u32 active_object_count = block->m_static_objects.m_active.size();
809 u32 wider_known_count = 3*3*3 - wider_unknown_count;
810 wider += wider_unknown_count * wider / wider_known_count;
811 return active_object_count;
814 void apply(MapBlock *block, int &blocks_scanned, int &abms_run, int &blocks_cached)
816 if(m_aabms.empty() || block->isDummy())
819 // Check the content type cache first
820 // to see whether there are any ABMs
821 // to be run at all for this block.
822 if (block->contents_cached) {
824 bool run_abms = false;
825 for (content_t c : block->contents) {
826 if (c < m_aabms.size() && m_aabms[c]) {
835 block->contents.clear();
839 ServerMap *map = &m_env->getServerMap();
841 u32 active_object_count_wider;
842 u32 active_object_count = this->countObjects(block, map, active_object_count_wider);
843 m_env->m_added_objects = 0;
846 for(p0.X=0; p0.X<MAP_BLOCKSIZE; p0.X++)
847 for(p0.Y=0; p0.Y<MAP_BLOCKSIZE; p0.Y++)
848 for(p0.Z=0; p0.Z<MAP_BLOCKSIZE; p0.Z++)
850 const MapNode &n = block->getNodeUnsafe(p0);
851 content_t c = n.getContent();
852 // Cache content types as we go
853 if (!block->contents_cached && !block->do_not_cache_contents) {
854 block->contents.insert(c);
855 if (block->contents.size() > 64) {
856 // Too many different nodes... don't try to cache
857 block->do_not_cache_contents = true;
858 block->contents.clear();
862 if (c >= m_aabms.size() || !m_aabms[c])
865 v3s16 p = p0 + block->getPosRelative();
866 for (ActiveABM &aabm : *m_aabms[c]) {
867 if (myrand() % aabm.chance != 0)
871 if (aabm.check_required_neighbors) {
873 for(p1.X = p0.X-1; p1.X <= p0.X+1; p1.X++)
874 for(p1.Y = p0.Y-1; p1.Y <= p0.Y+1; p1.Y++)
875 for(p1.Z = p0.Z-1; p1.Z <= p0.Z+1; p1.Z++)
880 if (block->isValidPosition(p1)) {
881 // if the neighbor is found on the same map block
882 // get it straight from there
883 const MapNode &n = block->getNodeUnsafe(p1);
886 // otherwise consult the map
887 MapNode n = map->getNodeNoEx(p1 + block->getPosRelative());
890 if (CONTAINS(aabm.required_neighbors, c))
893 // No required neighbor found
899 // Call all the trigger variations
900 aabm.abm->trigger(m_env, p, n);
901 aabm.abm->trigger(m_env, p, n,
902 active_object_count, active_object_count_wider);
904 // Count surrounding objects again if the abms added any
905 if(m_env->m_added_objects > 0) {
906 active_object_count = countObjects(block, map, active_object_count_wider);
907 m_env->m_added_objects = 0;
911 block->contents_cached = !block->do_not_cache_contents;
915 void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime)
917 // Reset usage timer immediately, otherwise a block that becomes active
918 // again at around the same time as it would normally be unloaded will
919 // get unloaded incorrectly. (I think this still leaves a small possibility
920 // of a race condition between this and server::AsyncRunStep, which only
921 // some kind of synchronisation will fix, but it at least reduces the window
922 // of opportunity for it to break from seconds to nanoseconds)
923 block->resetUsageTimer();
925 // Get time difference
927 u32 stamp = block->getTimestamp();
928 if (m_game_time > stamp && stamp != BLOCK_TIMESTAMP_UNDEFINED)
929 dtime_s = m_game_time - stamp;
930 dtime_s += additional_dtime;
932 /*infostream<<"ServerEnvironment::activateBlock(): block timestamp: "
933 <<stamp<<", game time: "<<m_game_time<<std::endl;*/
935 // Remove stored static objects if clearObjects was called since block's timestamp
936 if (stamp == BLOCK_TIMESTAMP_UNDEFINED || stamp < m_last_clear_objects_time) {
937 block->m_static_objects.m_stored.clear();
938 // do not set changed flag to avoid unnecessary mapblock writes
941 // Set current time as timestamp
942 block->setTimestampNoChangedFlag(m_game_time);
944 /*infostream<<"ServerEnvironment::activateBlock(): block is "
945 <<dtime_s<<" seconds old."<<std::endl;*/
947 // Activate stored objects
948 activateObjects(block, dtime_s);
950 /* Handle LoadingBlockModifiers */
951 m_lbm_mgr.applyLBMs(this, block, stamp);
954 std::vector<NodeTimer> elapsed_timers =
955 block->m_node_timers.step((float)dtime_s);
956 if (!elapsed_timers.empty()) {
958 for (const NodeTimer &elapsed_timer : elapsed_timers) {
959 n = block->getNodeNoEx(elapsed_timer.position);
960 v3s16 p = elapsed_timer.position + block->getPosRelative();
961 if (m_script->node_on_timer(p, n, elapsed_timer.elapsed))
962 block->setNodeTimer(NodeTimer(elapsed_timer.timeout, 0,
963 elapsed_timer.position));
968 void ServerEnvironment::addActiveBlockModifier(ActiveBlockModifier *abm)
970 m_abms.emplace_back(abm);
973 void ServerEnvironment::addLoadingBlockModifierDef(LoadingBlockModifierDef *lbm)
975 m_lbm_mgr.addLBMDef(lbm);
978 bool ServerEnvironment::setNode(v3s16 p, const MapNode &n)
980 const NodeDefManager *ndef = m_server->ndef();
981 MapNode n_old = m_map->getNodeNoEx(p);
983 const ContentFeatures &cf_old = ndef->get(n_old);
986 if (cf_old.has_on_destruct)
987 m_script->node_on_destruct(p, n_old);
990 if (!m_map->addNodeWithEvent(p, n))
993 // Update active VoxelManipulator if a mapgen thread
994 m_map->updateVManip(p);
996 // Call post-destructor
997 if (cf_old.has_after_destruct)
998 m_script->node_after_destruct(p, n_old);
1000 // Retrieve node content features
1001 // if new node is same as old, reuse old definition to prevent a lookup
1002 const ContentFeatures &cf_new = n_old == n ? cf_old : ndef->get(n);
1005 if (cf_new.has_on_construct)
1006 m_script->node_on_construct(p, n);
1011 bool ServerEnvironment::removeNode(v3s16 p)
1013 const NodeDefManager *ndef = m_server->ndef();
1014 MapNode n_old = m_map->getNodeNoEx(p);
1017 if (ndef->get(n_old).has_on_destruct)
1018 m_script->node_on_destruct(p, n_old);
1021 // This is slightly optimized compared to addNodeWithEvent(air)
1022 if (!m_map->removeNodeWithEvent(p))
1025 // Update active VoxelManipulator if a mapgen thread
1026 m_map->updateVManip(p);
1028 // Call post-destructor
1029 if (ndef->get(n_old).has_after_destruct)
1030 m_script->node_after_destruct(p, n_old);
1032 // Air doesn't require constructor
1036 bool ServerEnvironment::swapNode(v3s16 p, const MapNode &n)
1038 if (!m_map->addNodeWithEvent(p, n, false))
1041 // Update active VoxelManipulator if a mapgen thread
1042 m_map->updateVManip(p);
1047 void ServerEnvironment::clearObjects(ClearObjectsMode mode)
1049 infostream << "ServerEnvironment::clearObjects(): "
1050 << "Removing all active objects" << std::endl;
1051 auto cb_removal = [this] (ServerActiveObject *obj, u16 id) {
1052 if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER)
1055 // Delete static object if block is loaded
1056 deleteStaticFromBlock(obj, id, MOD_REASON_CLEAR_ALL_OBJECTS, true);
1058 // If known by some client, don't delete immediately
1059 if (obj->m_known_by_count > 0) {
1060 obj->m_pending_removal = true;
1064 // Tell the object about removal
1065 obj->removingFromEnvironment();
1066 // Deregister in scripting api
1067 m_script->removeObjectReference(obj);
1069 // Delete active object
1070 if (obj->environmentDeletes())
1076 m_ao_manager.clear(cb_removal);
1078 // Get list of loaded blocks
1079 std::vector<v3s16> loaded_blocks;
1080 infostream << "ServerEnvironment::clearObjects(): "
1081 << "Listing all loaded blocks" << std::endl;
1082 m_map->listAllLoadedBlocks(loaded_blocks);
1083 infostream << "ServerEnvironment::clearObjects(): "
1084 << "Done listing all loaded blocks: "
1085 << loaded_blocks.size()<<std::endl;
1087 // Get list of loadable blocks
1088 std::vector<v3s16> loadable_blocks;
1089 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1090 infostream << "ServerEnvironment::clearObjects(): "
1091 << "Listing all loadable blocks" << std::endl;
1092 m_map->listAllLoadableBlocks(loadable_blocks);
1093 infostream << "ServerEnvironment::clearObjects(): "
1094 << "Done listing all loadable blocks: "
1095 << loadable_blocks.size() << std::endl;
1097 loadable_blocks = loaded_blocks;
1100 actionstream << "ServerEnvironment::clearObjects(): "
1101 << "Now clearing objects in " << loadable_blocks.size()
1102 << " blocks" << std::endl;
1104 // Grab a reference on each loaded block to avoid unloading it
1105 for (v3s16 p : loaded_blocks) {
1106 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1107 assert(block != NULL);
1111 // Remove objects in all loadable blocks
1112 u32 unload_interval = U32_MAX;
1113 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1114 unload_interval = g_settings->getS32("max_clearobjects_extra_loaded_blocks");
1115 unload_interval = MYMAX(unload_interval, 1);
1117 u32 report_interval = loadable_blocks.size() / 10;
1118 u32 num_blocks_checked = 0;
1119 u32 num_blocks_cleared = 0;
1120 u32 num_objs_cleared = 0;
1121 for (auto i = loadable_blocks.begin();
1122 i != loadable_blocks.end(); ++i) {
1124 MapBlock *block = m_map->emergeBlock(p, false);
1126 errorstream << "ServerEnvironment::clearObjects(): "
1127 << "Failed to emerge block " << PP(p) << std::endl;
1130 u32 num_stored = block->m_static_objects.m_stored.size();
1131 u32 num_active = block->m_static_objects.m_active.size();
1132 if (num_stored != 0 || num_active != 0) {
1133 block->m_static_objects.m_stored.clear();
1134 block->m_static_objects.m_active.clear();
1135 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1136 MOD_REASON_CLEAR_ALL_OBJECTS);
1137 num_objs_cleared += num_stored + num_active;
1138 num_blocks_cleared++;
1140 num_blocks_checked++;
1142 if (report_interval != 0 &&
1143 num_blocks_checked % report_interval == 0) {
1144 float percent = 100.0 * (float)num_blocks_checked /
1145 loadable_blocks.size();
1146 actionstream << "ServerEnvironment::clearObjects(): "
1147 << "Cleared " << num_objs_cleared << " objects"
1148 << " in " << num_blocks_cleared << " blocks ("
1149 << percent << "%)" << std::endl;
1151 if (num_blocks_checked % unload_interval == 0) {
1152 m_map->unloadUnreferencedBlocks();
1155 m_map->unloadUnreferencedBlocks();
1157 // Drop references that were added above
1158 for (v3s16 p : loaded_blocks) {
1159 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1164 m_last_clear_objects_time = m_game_time;
1166 actionstream << "ServerEnvironment::clearObjects(): "
1167 << "Finished: Cleared " << num_objs_cleared << " objects"
1168 << " in " << num_blocks_cleared << " blocks" << std::endl;
1171 void ServerEnvironment::step(float dtime)
1173 /* Step time of day */
1174 stepTimeOfDay(dtime);
1177 // NOTE: This is kind of funny on a singleplayer game, but doesn't
1178 // really matter that much.
1179 static thread_local const float server_step =
1180 g_settings->getFloat("dedicated_server_step");
1181 m_recommended_send_interval = server_step;
1187 m_game_time_fraction_counter += dtime;
1188 u32 inc_i = (u32)m_game_time_fraction_counter;
1189 m_game_time += inc_i;
1190 m_game_time_fraction_counter -= (float)inc_i;
1197 ScopeProfiler sp(g_profiler, "SEnv: handle players avg", SPT_AVG);
1198 for (RemotePlayer *player : m_players) {
1199 // Ignore disconnected players
1200 if (player->getPeerId() == PEER_ID_INEXISTENT)
1204 player->move(dtime, this, 100 * BS);
1209 Manage active block list
1211 if (m_active_blocks_management_interval.step(dtime, m_cache_active_block_mgmt_interval)) {
1212 ScopeProfiler sp(g_profiler, "SEnv: manage act. block list avg per interval", SPT_AVG);
1214 Get player block positions
1216 std::vector<PlayerSAO*> players;
1217 for (RemotePlayer *player: m_players) {
1218 // Ignore disconnected players
1219 if (player->getPeerId() == PEER_ID_INEXISTENT)
1222 PlayerSAO *playersao = player->getPlayerSAO();
1225 players.push_back(playersao);
1229 Update list of active blocks, collecting changes
1231 // use active_object_send_range_blocks since that is max distance
1232 // for active objects sent the client anyway
1233 static thread_local const s16 active_object_range =
1234 g_settings->getS16("active_object_send_range_blocks");
1235 static thread_local const s16 active_block_range =
1236 g_settings->getS16("active_block_range");
1237 std::set<v3s16> blocks_removed;
1238 std::set<v3s16> blocks_added;
1239 m_active_blocks.update(players, active_block_range, active_object_range,
1240 blocks_removed, blocks_added);
1243 Handle removed blocks
1246 // Convert active objects that are no more in active blocks to static
1247 deactivateFarObjects(false);
1249 for (const v3s16 &p: blocks_removed) {
1250 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1254 // Set current time as timestamp (and let it set ChangedFlag)
1255 block->setTimestamp(m_game_time);
1262 for (const v3s16 &p: blocks_added) {
1263 MapBlock *block = m_map->getBlockOrEmerge(p);
1265 m_active_blocks.m_list.erase(p);
1266 m_active_blocks.m_abm_list.erase(p);
1270 activateBlock(block);
1275 Mess around in active blocks
1277 if (m_active_blocks_nodemetadata_interval.step(dtime, m_cache_nodetimer_interval)) {
1278 ScopeProfiler sp(g_profiler, "SEnv: mess in act. blocks avg per interval", SPT_AVG);
1280 float dtime = m_cache_nodetimer_interval;
1282 for (const v3s16 &p: m_active_blocks.m_list) {
1283 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1287 // Reset block usage timer
1288 block->resetUsageTimer();
1290 // Set current time as timestamp
1291 block->setTimestampNoChangedFlag(m_game_time);
1292 // If time has changed much from the one on disk,
1293 // set block to be saved when it is unloaded
1294 if(block->getTimestamp() > block->getDiskTimestamp() + 60)
1295 block->raiseModified(MOD_STATE_WRITE_AT_UNLOAD,
1296 MOD_REASON_BLOCK_EXPIRED);
1299 std::vector<NodeTimer> elapsed_timers = block->m_node_timers.step(dtime);
1300 if (!elapsed_timers.empty()) {
1303 for (const NodeTimer &elapsed_timer: elapsed_timers) {
1304 n = block->getNodeNoEx(elapsed_timer.position);
1305 p2 = elapsed_timer.position + block->getPosRelative();
1306 if (m_script->node_on_timer(p2, n, elapsed_timer.elapsed)) {
1307 block->setNodeTimer(NodeTimer(
1308 elapsed_timer.timeout, 0, elapsed_timer.position));
1315 if (m_active_block_modifier_interval.step(dtime, m_cache_abm_interval))
1317 if (m_active_block_interval_overload_skip > 0) {
1318 ScopeProfiler sp(g_profiler, "SEnv: ABM overload skips");
1319 m_active_block_interval_overload_skip--;
1322 ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg per interval", SPT_AVG);
1323 TimeTaker timer("modify in active blocks per interval");
1325 // Initialize handling of ActiveBlockModifiers
1326 ABMHandler abmhandler(m_abms, m_cache_abm_interval, this, true);
1328 int blocks_scanned = 0;
1330 int blocks_cached = 0;
1331 for (const v3s16 &p : m_active_blocks.m_abm_list) {
1332 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1336 // Set current time as timestamp
1337 block->setTimestampNoChangedFlag(m_game_time);
1339 /* Handle ActiveBlockModifiers */
1340 abmhandler.apply(block, blocks_scanned, abms_run, blocks_cached);
1342 g_profiler->avg("SEnv: active blocks", m_active_blocks.m_abm_list.size());
1343 g_profiler->avg("SEnv: active blocks cached", blocks_cached);
1344 g_profiler->avg("SEnv: active blocks scanned for ABMs", blocks_scanned);
1345 g_profiler->avg("SEnv: ABMs run", abms_run);
1347 u32 time_ms = timer.stop(true);
1348 u32 max_time_ms = 200;
1349 if (time_ms > max_time_ms) {
1350 warningstream<<"active block modifiers took "
1351 <<time_ms<<"ms (longer than "
1352 <<max_time_ms<<"ms)"<<std::endl;
1353 m_active_block_interval_overload_skip = (time_ms / max_time_ms) + 1;
1358 Step script environment (run global on_step())
1360 m_script->environment_Step(dtime);
1366 ScopeProfiler sp(g_profiler, "SEnv: step act. objs avg", SPT_AVG);
1368 // This helps the objects to send data at the same time
1369 bool send_recommended = false;
1370 m_send_recommended_timer += dtime;
1371 if (m_send_recommended_timer > getSendRecommendedInterval()) {
1372 m_send_recommended_timer -= getSendRecommendedInterval();
1373 send_recommended = true;
1376 auto cb_state = [this, dtime, send_recommended] (ServerActiveObject *obj) {
1381 obj->step(dtime, send_recommended);
1382 // Read messages from object
1383 while (!obj->m_messages_out.empty()) {
1384 this->m_active_object_messages.push(obj->m_messages_out.front());
1385 obj->m_messages_out.pop();
1388 m_ao_manager.step(dtime, cb_state);
1392 Manage active objects
1394 if (m_object_management_interval.step(dtime, 0.5)) {
1395 ScopeProfiler sp(g_profiler, "SEnv: remove removed objs avg /.5s", SPT_AVG);
1396 removeRemovedObjects();
1400 Manage particle spawner expiration
1402 if (m_particle_management_interval.step(dtime, 1.0)) {
1403 for (std::unordered_map<u32, float>::iterator i = m_particle_spawners.begin();
1404 i != m_particle_spawners.end(); ) {
1405 //non expiring spawners
1406 if (i->second == PARTICLE_SPAWNER_NO_EXPIRY) {
1412 if (i->second <= 0.f)
1413 m_particle_spawners.erase(i++);
1420 u32 ServerEnvironment::addParticleSpawner(float exptime)
1422 // Timers with lifetime 0 do not expire
1423 float time = exptime > 0.f ? exptime : PARTICLE_SPAWNER_NO_EXPIRY;
1426 for (;;) { // look for unused particlespawner id
1428 std::unordered_map<u32, float>::iterator f = m_particle_spawners.find(id);
1429 if (f == m_particle_spawners.end()) {
1430 m_particle_spawners[id] = time;
1437 u32 ServerEnvironment::addParticleSpawner(float exptime, u16 attached_id)
1439 u32 id = addParticleSpawner(exptime);
1440 m_particle_spawner_attachments[id] = attached_id;
1441 if (ServerActiveObject *obj = getActiveObject(attached_id)) {
1442 obj->attachParticleSpawner(id);
1447 void ServerEnvironment::deleteParticleSpawner(u32 id, bool remove_from_object)
1449 m_particle_spawners.erase(id);
1450 const auto &it = m_particle_spawner_attachments.find(id);
1451 if (it != m_particle_spawner_attachments.end()) {
1452 u16 obj_id = it->second;
1453 ServerActiveObject *sao = getActiveObject(obj_id);
1454 if (sao != NULL && remove_from_object) {
1455 sao->detachParticleSpawner(id);
1457 m_particle_spawner_attachments.erase(id);
1461 u16 ServerEnvironment::addActiveObject(ServerActiveObject *object)
1463 assert(object); // Pre-condition
1465 u16 id = addActiveObjectRaw(object, true, 0);
1470 Finds out what new objects have been added to
1471 inside a radius around a position
1473 void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius,
1475 std::set<u16> ¤t_objects,
1476 std::queue<u16> &added_objects)
1478 f32 radius_f = radius * BS;
1479 f32 player_radius_f = player_radius * BS;
1481 if (player_radius_f < 0.0f)
1482 player_radius_f = 0.0f;
1484 m_ao_manager.getAddedActiveObjectsAroundPos(playersao->getBasePosition(), radius_f,
1485 player_radius_f, current_objects, added_objects);
1489 Finds out what objects have been removed from
1490 inside a radius around a position
1492 void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius,
1494 std::set<u16> ¤t_objects,
1495 std::queue<u16> &removed_objects)
1497 f32 radius_f = radius * BS;
1498 f32 player_radius_f = player_radius * BS;
1500 if (player_radius_f < 0)
1501 player_radius_f = 0;
1503 Go through current_objects; object is removed if:
1504 - object is not found in m_active_objects (this is actually an
1505 error condition; objects should be removed only after all clients
1506 have been informed about removal), or
1507 - object is to be removed or deactivated, or
1508 - object is too far away
1510 for (u16 id : current_objects) {
1511 ServerActiveObject *object = getActiveObject(id);
1513 if (object == NULL) {
1514 infostream << "ServerEnvironment::getRemovedActiveObjects():"
1515 << " object in current_objects is NULL" << std::endl;
1516 removed_objects.push(id);
1520 if (object->isGone()) {
1521 removed_objects.push(id);
1525 f32 distance_f = object->getBasePosition().getDistanceFrom(playersao->getBasePosition());
1526 if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1527 if (distance_f <= player_radius_f || player_radius_f == 0)
1529 } else if (distance_f <= radius_f)
1532 // Object is no longer visible
1533 removed_objects.push(id);
1537 void ServerEnvironment::setStaticForActiveObjectsInBlock(
1538 v3s16 blockpos, bool static_exists, v3s16 static_block)
1540 MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos);
1544 for (auto &so_it : block->m_static_objects.m_active) {
1545 // Get the ServerActiveObject counterpart to this StaticObject
1546 ServerActiveObject *sao = m_ao_manager.getActiveObject(so_it.first);
1548 // If this ever happens, there must be some kind of nasty bug.
1549 errorstream << "ServerEnvironment::setStaticForObjectsInBlock(): "
1550 "Object from MapBlock::m_static_objects::m_active not found "
1551 "in m_active_objects";
1555 sao->m_static_exists = static_exists;
1556 sao->m_static_block = static_block;
1560 ActiveObjectMessage ServerEnvironment::getActiveObjectMessage()
1562 if(m_active_object_messages.empty())
1563 return ActiveObjectMessage(0);
1565 ActiveObjectMessage message = m_active_object_messages.front();
1566 m_active_object_messages.pop();
1570 void ServerEnvironment::getSelectedActiveObjects(
1571 const core::line3d<f32> &shootline_on_map,
1572 std::vector<PointedThing> &objects)
1574 std::vector<u16> objectIds;
1575 getObjectsInsideRadius(objectIds, shootline_on_map.start,
1576 shootline_on_map.getLength() + 10.0f);
1577 const v3f line_vector = shootline_on_map.getVector();
1579 for (u16 objectId : objectIds) {
1580 ServerActiveObject* obj = getActiveObject(objectId);
1582 aabb3f selection_box;
1583 if (!obj->getSelectionBox(&selection_box))
1586 v3f pos = obj->getBasePosition();
1588 aabb3f offsetted_box(selection_box.MinEdge + pos,
1589 selection_box.MaxEdge + pos);
1591 v3f current_intersection;
1592 v3s16 current_normal;
1593 if (boxLineCollision(offsetted_box, shootline_on_map.start, line_vector,
1594 ¤t_intersection, ¤t_normal)) {
1595 objects.emplace_back(
1596 (s16) objectId, current_intersection, current_normal,
1597 (current_intersection - shootline_on_map.start).getLengthSQ());
1603 ************ Private methods *************
1606 u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object,
1607 bool set_changed, u32 dtime_s)
1609 if (!m_ao_manager.registerObject(object)) {
1613 // Register reference in scripting api (must be done before post-init)
1614 m_script->addObjectReference(object);
1615 // Post-initialize object
1616 object->addedToEnvironment(dtime_s);
1618 // Add static data to block
1619 if (object->isStaticAllowed()) {
1620 // Add static object to active static list of the block
1621 v3f objectpos = object->getBasePosition();
1622 StaticObject s_obj(object, objectpos);
1623 // Add to the block where the object is located in
1624 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
1625 MapBlock *block = m_map->emergeBlock(blockpos);
1627 block->m_static_objects.m_active[object->getId()] = s_obj;
1628 object->m_static_exists = true;
1629 object->m_static_block = blockpos;
1632 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1633 MOD_REASON_ADD_ACTIVE_OBJECT_RAW);
1635 v3s16 p = floatToInt(objectpos, BS);
1636 errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
1637 <<"could not emerge block for storing id="<<object->getId()
1638 <<" statically (pos="<<PP(p)<<")"<<std::endl;
1642 return object->getId();
1646 Remove objects that satisfy (isGone() && m_known_by_count==0)
1648 void ServerEnvironment::removeRemovedObjects()
1650 auto clear_cb = [this] (ServerActiveObject *obj, u16 id) {
1651 // This shouldn't happen but check it
1653 errorstream << "ServerEnvironment::removeRemovedObjects(): "
1654 << "NULL object found. id=" << id << std::endl;
1659 We will handle objects marked for removal or deactivation
1665 Delete static data from block if removed
1667 if (obj->m_pending_removal)
1668 deleteStaticFromBlock(obj, id, MOD_REASON_REMOVE_OBJECTS_REMOVE, false);
1670 // If still known by clients, don't actually remove. On some future
1671 // invocation this will be 0, which is when removal will continue.
1672 if(obj->m_known_by_count > 0)
1676 Move static data from active to stored if deactivated
1678 if (!obj->m_pending_removal && obj->m_static_exists) {
1679 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
1681 const auto i = block->m_static_objects.m_active.find(id);
1682 if (i != block->m_static_objects.m_active.end()) {
1683 block->m_static_objects.m_stored.push_back(i->second);
1684 block->m_static_objects.m_active.erase(id);
1685 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1686 MOD_REASON_REMOVE_OBJECTS_DEACTIVATE);
1688 warningstream << "ServerEnvironment::removeRemovedObjects(): "
1689 << "id=" << id << " m_static_exists=true but "
1690 << "static data doesn't actually exist in "
1691 << PP(obj->m_static_block) << std::endl;
1694 infostream << "Failed to emerge block from which an object to "
1695 << "be deactivated was loaded from. id=" << id << std::endl;
1699 // Tell the object about removal
1700 obj->removingFromEnvironment();
1701 // Deregister in scripting api
1702 m_script->removeObjectReference(obj);
1705 if (obj->environmentDeletes())
1711 m_ao_manager.clear(clear_cb);
1714 static void print_hexdump(std::ostream &o, const std::string &data)
1716 const int linelength = 16;
1717 for(int l=0; ; l++){
1718 int i0 = linelength * l;
1719 bool at_end = false;
1720 int thislinelength = linelength;
1721 if(i0 + thislinelength > (int)data.size()){
1722 thislinelength = data.size() - i0;
1725 for(int di=0; di<linelength; di++){
1728 if(di<thislinelength)
1729 porting::mt_snprintf(buf, sizeof(buf), "%.2x ", data[i]);
1731 porting::mt_snprintf(buf, sizeof(buf), " ");
1735 for(int di=0; di<thislinelength; di++){
1749 Convert stored objects from blocks near the players to active.
1751 void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s)
1756 // Ignore if no stored objects (to not set changed flag)
1757 if(block->m_static_objects.m_stored.empty())
1760 verbosestream<<"ServerEnvironment::activateObjects(): "
1761 <<"activating objects of block "<<PP(block->getPos())
1762 <<" ("<<block->m_static_objects.m_stored.size()
1763 <<" objects)"<<std::endl;
1764 bool large_amount = (block->m_static_objects.m_stored.size() > g_settings->getU16("max_objects_per_block"));
1766 errorstream<<"suspiciously large amount of objects detected: "
1767 <<block->m_static_objects.m_stored.size()<<" in "
1768 <<PP(block->getPos())
1769 <<"; removing all of them."<<std::endl;
1770 // Clear stored list
1771 block->m_static_objects.m_stored.clear();
1772 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1773 MOD_REASON_TOO_MANY_OBJECTS);
1777 // Activate stored objects
1778 std::vector<StaticObject> new_stored;
1779 for (const StaticObject &s_obj : block->m_static_objects.m_stored) {
1780 // Create an active object from the data
1781 ServerActiveObject *obj = ServerActiveObject::create
1782 ((ActiveObjectType) s_obj.type, this, 0, s_obj.pos, s_obj.data);
1783 // If couldn't create object, store static data back.
1785 errorstream<<"ServerEnvironment::activateObjects(): "
1786 <<"failed to create active object from static object "
1787 <<"in block "<<PP(s_obj.pos/BS)
1788 <<" type="<<(int)s_obj.type<<" data:"<<std::endl;
1789 print_hexdump(verbosestream, s_obj.data);
1791 new_stored.push_back(s_obj);
1794 verbosestream<<"ServerEnvironment::activateObjects(): "
1795 <<"activated static object pos="<<PP(s_obj.pos/BS)
1796 <<" type="<<(int)s_obj.type<<std::endl;
1797 // This will also add the object to the active static list
1798 addActiveObjectRaw(obj, false, dtime_s);
1801 // Clear stored list
1802 block->m_static_objects.m_stored.clear();
1803 // Add leftover failed stuff to stored list
1804 for (const StaticObject &s_obj : new_stored) {
1805 block->m_static_objects.m_stored.push_back(s_obj);
1809 Note: Block hasn't really been modified here.
1810 The objects have just been activated and moved from the stored
1811 static list to the active static list.
1812 As such, the block is essentially the same.
1813 Thus, do not call block->raiseModified(MOD_STATE_WRITE_NEEDED).
1814 Otherwise there would be a huge amount of unnecessary I/O.
1819 Convert objects that are not standing inside active blocks to static.
1821 If m_known_by_count != 0, active object is not deleted, but static
1822 data is still updated.
1824 If force_delete is set, active object is deleted nevertheless. It
1825 shall only be set so in the destructor of the environment.
1827 If block wasn't generated (not in memory or on disk),
1829 void ServerEnvironment::deactivateFarObjects(bool _force_delete)
1831 auto cb_deactivate = [this, _force_delete] (ServerActiveObject *obj, u16 id) {
1832 // force_delete might be overriden per object
1833 bool force_delete = _force_delete;
1835 // Do not deactivate if static data creation not allowed
1836 if (!force_delete && !obj->isStaticAllowed())
1839 // removeRemovedObjects() is responsible for these
1840 if (!force_delete && obj->isGone())
1843 const v3f &objectpos = obj->getBasePosition();
1845 // The block in which the object resides in
1846 v3s16 blockpos_o = getNodeBlockPos(floatToInt(objectpos, BS));
1848 // If object's static data is stored in a deactivated block and object
1849 // is actually located in an active block, re-save to the block in
1850 // which the object is actually located in.
1851 if (!force_delete && obj->m_static_exists &&
1852 !m_active_blocks.contains(obj->m_static_block) &&
1853 m_active_blocks.contains(blockpos_o)) {
1854 // Delete from block where object was located
1855 deleteStaticFromBlock(obj, id, MOD_REASON_STATIC_DATA_REMOVED, false);
1857 StaticObject s_obj(obj, objectpos);
1858 // Save to block where object is located
1859 saveStaticToBlock(blockpos_o, id, obj, s_obj, MOD_REASON_STATIC_DATA_ADDED);
1864 // If block is still active, don't remove
1865 if (!force_delete && m_active_blocks.contains(blockpos_o))
1868 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1869 << "deactivating object id=" << id << " on inactive block "
1870 << PP(blockpos_o) << std::endl;
1872 // If known by some client, don't immediately delete.
1873 bool pending_delete = (obj->m_known_by_count > 0 && !force_delete);
1876 Update the static data
1878 if (obj->isStaticAllowed()) {
1879 // Create new static object
1880 StaticObject s_obj(obj, objectpos);
1882 bool stays_in_same_block = false;
1883 bool data_changed = true;
1885 // Check if static data has changed considerably
1886 if (obj->m_static_exists) {
1887 if (obj->m_static_block == blockpos_o)
1888 stays_in_same_block = true;
1890 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
1893 const auto n = block->m_static_objects.m_active.find(id);
1894 if (n != block->m_static_objects.m_active.end()) {
1895 StaticObject static_old = n->second;
1897 float save_movem = obj->getMinimumSavedMovement();
1899 if (static_old.data == s_obj.data &&
1900 (static_old.pos - objectpos).getLength() < save_movem)
1901 data_changed = false;
1903 warningstream << "ServerEnvironment::deactivateFarObjects(): "
1904 << "id=" << id << " m_static_exists=true but "
1905 << "static data doesn't actually exist in "
1906 << PP(obj->m_static_block) << std::endl;
1912 While changes are always saved, blocks are only marked as modified
1913 if the object has moved or different staticdata. (see above)
1915 bool shall_be_written = (!stays_in_same_block || data_changed);
1916 u32 reason = shall_be_written ? MOD_REASON_STATIC_DATA_CHANGED : MOD_REASON_UNKNOWN;
1918 // Delete old static object
1919 deleteStaticFromBlock(obj, id, reason, false);
1921 // Add to the block where the object is located in
1922 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
1923 u16 store_id = pending_delete ? id : 0;
1924 if (!saveStaticToBlock(blockpos, store_id, obj, s_obj, reason))
1925 force_delete = true;
1929 If known by some client, set pending deactivation.
1930 Otherwise delete it immediately.
1932 if (pending_delete && !force_delete) {
1933 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1934 << "object id=" << id << " is known by clients"
1935 << "; not deleting yet" << std::endl;
1937 obj->m_pending_deactivation = true;
1941 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1942 << "object id=" << id << " is not known by clients"
1943 << "; deleting" << std::endl;
1945 // Tell the object about removal
1946 obj->removingFromEnvironment();
1947 // Deregister in scripting api
1948 m_script->removeObjectReference(obj);
1950 // Delete active object
1951 if (obj->environmentDeletes())
1957 m_ao_manager.clear(cb_deactivate);
1960 void ServerEnvironment::deleteStaticFromBlock(
1961 ServerActiveObject *obj, u16 id, u32 mod_reason, bool no_emerge)
1963 if (!obj->m_static_exists)
1968 block = m_map->getBlockNoCreateNoEx(obj->m_static_block);
1970 block = m_map->emergeBlock(obj->m_static_block, false);
1973 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
1974 << " when deleting static data of object from it. id=" << id << std::endl;
1978 block->m_static_objects.remove(id);
1979 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
1980 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
1982 obj->m_static_exists = false;
1985 bool ServerEnvironment::saveStaticToBlock(
1986 v3s16 blockpos, u16 store_id,
1987 ServerActiveObject *obj, const StaticObject &s_obj,
1990 MapBlock *block = nullptr;
1992 block = m_map->emergeBlock(blockpos);
1993 } catch (InvalidPositionException &e) {
1994 // Handled via NULL pointer
1995 // NOTE: emergeBlock's failure is usually determined by it
1996 // actually returning NULL
2000 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2001 << " when saving static data of object to it. id=" << store_id << std::endl;
2004 if (block->m_static_objects.m_stored.size() >= g_settings->getU16("max_objects_per_block")) {
2005 warningstream << "ServerEnv: Trying to store id = " << store_id
2006 << " statically but block " << PP(blockpos)
2007 << " already contains "
2008 << block->m_static_objects.m_stored.size()
2009 << " objects." << std::endl;
2013 block->m_static_objects.insert(store_id, s_obj);
2014 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2015 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2017 obj->m_static_exists = true;
2018 obj->m_static_block = blockpos;
2023 PlayerDatabase *ServerEnvironment::openPlayerDatabase(const std::string &name,
2024 const std::string &savedir, const Settings &conf)
2027 if (name == "sqlite3")
2028 return new PlayerDatabaseSQLite3(savedir);
2030 if (name == "dummy")
2031 return new Database_Dummy();
2033 if (name == "postgresql") {
2034 std::string connect_string;
2035 conf.getNoEx("pgsql_player_connection", connect_string);
2036 return new PlayerDatabasePostgreSQL(connect_string);
2039 if (name == "files")
2040 return new PlayerDatabaseFiles(savedir + DIR_DELIM + "players");
2042 throw BaseException(std::string("Database backend ") + name + " not supported.");
2045 bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params,
2046 const Settings &cmd_args)
2048 std::string migrate_to = cmd_args.get("migrate-players");
2050 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2051 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2052 errorstream << "Cannot read world.mt!" << std::endl;
2056 if (!world_mt.exists("player_backend")) {
2057 errorstream << "Please specify your current backend in world.mt:"
2059 << " player_backend = {files|sqlite3|postgresql}"
2064 std::string backend = world_mt.get("player_backend");
2065 if (backend == migrate_to) {
2066 errorstream << "Cannot migrate: new backend is same"
2067 << " as the old one" << std::endl;
2071 const std::string players_backup_path = game_params.world_path + DIR_DELIM
2074 if (backend == "files") {
2075 // Create backup directory
2076 fs::CreateDir(players_backup_path);
2080 PlayerDatabase *srcdb = ServerEnvironment::openPlayerDatabase(backend,
2081 game_params.world_path, world_mt);
2082 PlayerDatabase *dstdb = ServerEnvironment::openPlayerDatabase(migrate_to,
2083 game_params.world_path, world_mt);
2085 std::vector<std::string> player_list;
2086 srcdb->listPlayers(player_list);
2087 for (std::vector<std::string>::const_iterator it = player_list.begin();
2088 it != player_list.end(); ++it) {
2089 actionstream << "Migrating player " << it->c_str() << std::endl;
2090 RemotePlayer player(it->c_str(), NULL);
2091 PlayerSAO playerSAO(NULL, &player, 15000, false);
2093 srcdb->loadPlayer(&player, &playerSAO);
2095 playerSAO.finalize(&player, std::set<std::string>());
2096 player.setPlayerSAO(&playerSAO);
2098 dstdb->savePlayer(&player);
2100 // For files source, move player files to backup dir
2101 if (backend == "files") {
2103 game_params.world_path + DIR_DELIM + "players" + DIR_DELIM + (*it),
2104 players_backup_path + DIR_DELIM + (*it));
2108 actionstream << "Successfully migrated " << player_list.size() << " players"
2110 world_mt.set("player_backend", migrate_to);
2111 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2112 errorstream << "Failed to update world.mt!" << std::endl;
2114 actionstream << "world.mt updated" << std::endl;
2116 // When migration is finished from file backend, remove players directory if empty
2117 if (backend == "files") {
2118 fs::DeleteSingleFileOrEmptyDirectory(game_params.world_path + DIR_DELIM
2125 } catch (BaseException &e) {
2126 errorstream << "An error occured during migration: " << e.what() << std::endl;
2132 AuthDatabase *ServerEnvironment::openAuthDatabase(
2133 const std::string &name, const std::string &savedir, const Settings &conf)
2135 if (name == "sqlite3")
2136 return new AuthDatabaseSQLite3(savedir);
2138 if (name == "files")
2139 return new AuthDatabaseFiles(savedir);
2141 throw BaseException(std::string("Database backend ") + name + " not supported.");
2144 bool ServerEnvironment::migrateAuthDatabase(
2145 const GameParams &game_params, const Settings &cmd_args)
2147 std::string migrate_to = cmd_args.get("migrate-auth");
2149 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2150 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2151 errorstream << "Cannot read world.mt!" << std::endl;
2155 std::string backend = "files";
2156 if (world_mt.exists("auth_backend"))
2157 backend = world_mt.get("auth_backend");
2159 warningstream << "No auth_backend found in world.mt, "
2160 "assuming \"files\"." << std::endl;
2162 if (backend == migrate_to) {
2163 errorstream << "Cannot migrate: new backend is same"
2164 << " as the old one" << std::endl;
2169 const std::unique_ptr<AuthDatabase> srcdb(ServerEnvironment::openAuthDatabase(
2170 backend, game_params.world_path, world_mt));
2171 const std::unique_ptr<AuthDatabase> dstdb(ServerEnvironment::openAuthDatabase(
2172 migrate_to, game_params.world_path, world_mt));
2174 std::vector<std::string> names_list;
2175 srcdb->listNames(names_list);
2176 for (const std::string &name : names_list) {
2177 actionstream << "Migrating auth entry for " << name << std::endl;
2179 AuthEntry authEntry;
2180 success = srcdb->getAuth(name, authEntry);
2181 success = success && dstdb->createAuth(authEntry);
2183 errorstream << "Failed to migrate " << name << std::endl;
2186 actionstream << "Successfully migrated " << names_list.size()
2187 << " auth entries" << std::endl;
2188 world_mt.set("auth_backend", migrate_to);
2189 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2190 errorstream << "Failed to update world.mt!" << std::endl;
2192 actionstream << "world.mt updated" << std::endl;
2194 if (backend == "files") {
2195 // special-case files migration:
2196 // move auth.txt to auth.txt.bak if possible
2197 std::string auth_txt_path =
2198 game_params.world_path + DIR_DELIM + "auth.txt";
2199 std::string auth_bak_path = auth_txt_path + ".bak";
2200 if (!fs::PathExists(auth_bak_path))
2201 if (fs::Rename(auth_txt_path, auth_bak_path))
2202 actionstream << "Renamed auth.txt to auth.txt.bak"
2205 errorstream << "Could not rename auth.txt to "
2206 "auth.txt.bak" << std::endl;
2208 warningstream << "auth.txt.bak already exists, auth.txt "
2209 "not renamed" << std::endl;
2212 } catch (BaseException &e) {
2213 errorstream << "An error occured during migration: " << e.what()