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 // Random device to seed pseudo random generators.
388 static std::random_device seed;
390 ServerEnvironment::ServerEnvironment(ServerMap *map,
391 ServerScripting *scriptIface, Server *server,
392 const std::string &path_world):
395 m_script(scriptIface),
397 m_path_world(path_world),
400 // Determine which database backend to use
401 std::string conf_path = path_world + DIR_DELIM + "world.mt";
404 std::string player_backend_name = "sqlite3";
405 std::string auth_backend_name = "sqlite3";
407 bool succeeded = conf.readConfigFile(conf_path.c_str());
409 // If we open world.mt read the backend configurations.
411 // Read those values before setting defaults
412 bool player_backend_exists = conf.exists("player_backend");
413 bool auth_backend_exists = conf.exists("auth_backend");
415 // player backend is not set, assume it's legacy file backend.
416 if (!player_backend_exists) {
417 // fall back to files
418 conf.set("player_backend", "files");
419 player_backend_name = "files";
421 if (!conf.updateConfigFile(conf_path.c_str())) {
422 errorstream << "ServerEnvironment::ServerEnvironment(): "
423 << "Failed to update world.mt!" << std::endl;
426 conf.getNoEx("player_backend", player_backend_name);
429 // auth backend is not set, assume it's legacy file backend.
430 if (!auth_backend_exists) {
431 conf.set("auth_backend", "files");
432 auth_backend_name = "files";
434 if (!conf.updateConfigFile(conf_path.c_str())) {
435 errorstream << "ServerEnvironment::ServerEnvironment(): "
436 << "Failed to update world.mt!" << std::endl;
439 conf.getNoEx("auth_backend", auth_backend_name);
443 if (player_backend_name == "files") {
444 warningstream << "/!\\ You are using old player file backend. "
445 << "This backend is deprecated and will be removed in a future release /!\\"
446 << std::endl << "Switching to SQLite3 or PostgreSQL is advised, "
447 << "please read http://wiki.minetest.net/Database_backends." << std::endl;
450 if (auth_backend_name == "files") {
451 warningstream << "/!\\ You are using old auth file backend. "
452 << "This backend is deprecated and will be removed in a future release /!\\"
453 << std::endl << "Switching to SQLite3 is advised, "
454 << "please read http://wiki.minetest.net/Database_backends." << std::endl;
457 m_player_database = openPlayerDatabase(player_backend_name, path_world, conf);
458 m_auth_database = openAuthDatabase(auth_backend_name, path_world, conf);
461 ServerEnvironment::~ServerEnvironment()
463 // Clear active block list.
464 // This makes the next one delete all active objects.
465 m_active_blocks.clear();
467 // Convert all objects to static and delete the active objects
468 deactivateFarObjects(true);
473 // Delete ActiveBlockModifiers
474 for (ABMWithState &m_abm : m_abms) {
478 // Deallocate players
479 for (RemotePlayer *m_player : m_players) {
483 delete m_player_database;
484 delete m_auth_database;
487 Map & ServerEnvironment::getMap()
492 ServerMap & ServerEnvironment::getServerMap()
497 RemotePlayer *ServerEnvironment::getPlayer(const session_t peer_id)
499 for (RemotePlayer *player : m_players) {
500 if (player->getPeerId() == peer_id)
506 RemotePlayer *ServerEnvironment::getPlayer(const char* name)
508 for (RemotePlayer *player : m_players) {
509 if (strcmp(player->getName(), name) == 0)
515 void ServerEnvironment::addPlayer(RemotePlayer *player)
518 Check that peer_ids are unique.
519 Also check that names are unique.
520 Exception: there can be multiple players with peer_id=0
522 // If peer id is non-zero, it has to be unique.
523 if (player->getPeerId() != PEER_ID_INEXISTENT)
524 FATAL_ERROR_IF(getPlayer(player->getPeerId()) != NULL, "Peer id not unique");
525 // Name has to be unique.
526 FATAL_ERROR_IF(getPlayer(player->getName()) != NULL, "Player name not unique");
528 m_players.push_back(player);
531 void ServerEnvironment::removePlayer(RemotePlayer *player)
533 for (std::vector<RemotePlayer *>::iterator it = m_players.begin();
534 it != m_players.end(); ++it) {
535 if ((*it) == player) {
543 bool ServerEnvironment::removePlayerFromDatabase(const std::string &name)
545 return m_player_database->removePlayer(name);
548 bool ServerEnvironment::line_of_sight(v3f pos1, v3f pos2, v3s16 *p)
550 // Iterate trough nodes on the line
551 voxalgo::VoxelLineIterator iterator(pos1 / BS, (pos2 - pos1) / BS);
553 MapNode n = getMap().getNode(iterator.m_current_node_pos);
556 if (n.param0 != CONTENT_AIR) {
558 *p = iterator.m_current_node_pos;
562 } while (iterator.m_current_index <= iterator.m_last_index);
566 void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason,
567 const std::string &str_reason, bool reconnect)
569 for (RemotePlayer *player : m_players) {
570 m_server->DenyAccessVerCompliant(player->getPeerId(),
571 player->protocol_version, reason, str_reason, reconnect);
575 void ServerEnvironment::saveLoadedPlayers(bool force)
577 for (RemotePlayer *player : m_players) {
578 if (force || player->checkModified() || (player->getPlayerSAO() &&
579 player->getPlayerSAO()->getMeta().isModified())) {
581 m_player_database->savePlayer(player);
582 } catch (DatabaseException &e) {
583 errorstream << "Failed to save player " << player->getName() << " exception: "
584 << e.what() << std::endl;
591 void ServerEnvironment::savePlayer(RemotePlayer *player)
594 m_player_database->savePlayer(player);
595 } catch (DatabaseException &e) {
596 errorstream << "Failed to save player " << player->getName() << " exception: "
597 << e.what() << std::endl;
602 PlayerSAO *ServerEnvironment::loadPlayer(RemotePlayer *player, bool *new_player,
603 session_t peer_id, bool is_singleplayer)
605 PlayerSAO *playersao = new PlayerSAO(this, player, peer_id, is_singleplayer);
606 // Create player if it doesn't exist
607 if (!m_player_database->loadPlayer(player, playersao)) {
609 // Set player position
610 infostream << "Server: Finding spawn place for player \""
611 << player->getName() << "\"" << std::endl;
612 playersao->setBasePosition(m_server->findSpawnPos());
614 // Make sure the player is saved
615 player->setModified(true);
617 // If the player exists, ensure that they respawn inside legal bounds
618 // This fixes an assert crash when the player can't be added
619 // to the environment
620 if (objectpos_over_limit(playersao->getBasePosition())) {
621 actionstream << "Respawn position for player \""
622 << player->getName() << "\" outside limits, resetting" << std::endl;
623 playersao->setBasePosition(m_server->findSpawnPos());
627 // Add player to environment
630 /* Clean up old HUD elements from previous sessions */
633 /* Add object to environment */
634 addActiveObject(playersao);
639 void ServerEnvironment::saveMeta()
641 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
643 // Open file and serialize
644 std::ostringstream ss(std::ios_base::binary);
647 args.setU64("game_time", m_game_time);
648 args.setU64("time_of_day", getTimeOfDay());
649 args.setU64("last_clear_objects_time", m_last_clear_objects_time);
650 args.setU64("lbm_introduction_times_version", 1);
651 args.set("lbm_introduction_times",
652 m_lbm_mgr.createIntroductionTimesString());
653 args.setU64("day_count", m_day_count);
657 if(!fs::safeWriteToFile(path, ss.str()))
659 infostream<<"ServerEnvironment::saveMeta(): Failed to write "
661 throw SerializationError("Couldn't save env meta");
665 void ServerEnvironment::loadMeta()
667 // If file doesn't exist, load default environment metadata
668 if (!fs::PathExists(m_path_world + DIR_DELIM "env_meta.txt")) {
669 infostream << "ServerEnvironment: Loading default environment metadata"
675 infostream << "ServerEnvironment: Loading environment metadata" << std::endl;
677 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
679 // Open file and deserialize
680 std::ifstream is(path.c_str(), std::ios_base::binary);
682 infostream << "ServerEnvironment::loadMeta(): Failed to open "
683 << path << std::endl;
684 throw SerializationError("Couldn't load env meta");
689 if (!args.parseConfigLines(is, "EnvArgsEnd")) {
690 throw SerializationError("ServerEnvironment::loadMeta(): "
691 "EnvArgsEnd not found!");
695 m_game_time = args.getU64("game_time");
696 } catch (SettingNotFoundException &e) {
697 // Getting this is crucial, otherwise timestamps are useless
698 throw SerializationError("Couldn't load env meta game_time");
701 setTimeOfDay(args.exists("time_of_day") ?
702 // set day to early morning by default
703 args.getU64("time_of_day") : 5250);
705 m_last_clear_objects_time = args.exists("last_clear_objects_time") ?
706 // If missing, do as if clearObjects was never called
707 args.getU64("last_clear_objects_time") : 0;
709 std::string lbm_introduction_times;
711 u64 ver = args.getU64("lbm_introduction_times_version");
713 lbm_introduction_times = args.get("lbm_introduction_times");
715 infostream << "ServerEnvironment::loadMeta(): Non-supported"
716 << " introduction time version " << ver << std::endl;
718 } catch (SettingNotFoundException &e) {
719 // No problem, this is expected. Just continue with an empty string
721 m_lbm_mgr.loadIntroductionTimes(lbm_introduction_times, m_server, m_game_time);
723 m_day_count = args.exists("day_count") ?
724 args.getU64("day_count") : 0;
728 * called if env_meta.txt doesn't exist (e.g. new world)
730 void ServerEnvironment::loadDefaultMeta()
732 m_lbm_mgr.loadIntroductionTimes("", m_server, m_game_time);
737 ActiveBlockModifier *abm;
739 std::vector<content_t> required_neighbors;
740 bool check_required_neighbors; // false if required_neighbors is known to be empty
746 ServerEnvironment *m_env;
747 std::vector<std::vector<ActiveABM> *> m_aabms;
749 ABMHandler(std::vector<ABMWithState> &abms,
750 float dtime_s, ServerEnvironment *env,
756 const NodeDefManager *ndef = env->getGameDef()->ndef();
757 for (ABMWithState &abmws : abms) {
758 ActiveBlockModifier *abm = abmws.abm;
759 float trigger_interval = abm->getTriggerInterval();
760 if(trigger_interval < 0.001)
761 trigger_interval = 0.001;
762 float actual_interval = dtime_s;
764 abmws.timer += dtime_s;
765 if(abmws.timer < trigger_interval)
767 abmws.timer -= trigger_interval;
768 actual_interval = trigger_interval;
770 float chance = abm->getTriggerChance();
775 if (abm->getSimpleCatchUp()) {
776 float intervals = actual_interval / trigger_interval;
779 aabm.chance = chance / intervals;
783 aabm.chance = chance;
787 const std::vector<std::string> &required_neighbors_s =
788 abm->getRequiredNeighbors();
789 for (const std::string &required_neighbor_s : required_neighbors_s) {
790 ndef->getIds(required_neighbor_s, aabm.required_neighbors);
792 aabm.check_required_neighbors = !required_neighbors_s.empty();
795 const std::vector<std::string> &contents_s = abm->getTriggerContents();
796 for (const std::string &content_s : contents_s) {
797 std::vector<content_t> ids;
798 ndef->getIds(content_s, ids);
799 for (content_t c : ids) {
800 if (c >= m_aabms.size())
801 m_aabms.resize(c + 256, NULL);
803 m_aabms[c] = new std::vector<ActiveABM>;
804 m_aabms[c]->push_back(aabm);
812 for (auto &aabms : m_aabms)
816 // Find out how many objects the given block and its neighbours contain.
817 // Returns the number of objects in the block, and also in 'wider' the
818 // number of objects in the block and all its neighbours. The latter
819 // may an estimate if any neighbours are unloaded.
820 u32 countObjects(MapBlock *block, ServerMap * map, u32 &wider)
823 u32 wider_unknown_count = 0;
824 for(s16 x=-1; x<=1; x++)
825 for(s16 y=-1; y<=1; y++)
826 for(s16 z=-1; z<=1; z++)
828 MapBlock *block2 = map->getBlockNoCreateNoEx(
829 block->getPos() + v3s16(x,y,z));
831 wider_unknown_count++;
834 wider += block2->m_static_objects.m_active.size()
835 + block2->m_static_objects.m_stored.size();
838 u32 active_object_count = block->m_static_objects.m_active.size();
839 u32 wider_known_count = 3*3*3 - wider_unknown_count;
840 wider += wider_unknown_count * wider / wider_known_count;
841 return active_object_count;
844 void apply(MapBlock *block, int &blocks_scanned, int &abms_run, int &blocks_cached)
846 if(m_aabms.empty() || block->isDummy())
849 // Check the content type cache first
850 // to see whether there are any ABMs
851 // to be run at all for this block.
852 if (block->contents_cached) {
854 bool run_abms = false;
855 for (content_t c : block->contents) {
856 if (c < m_aabms.size() && m_aabms[c]) {
865 block->contents.clear();
869 ServerMap *map = &m_env->getServerMap();
871 u32 active_object_count_wider;
872 u32 active_object_count = this->countObjects(block, map, active_object_count_wider);
873 m_env->m_added_objects = 0;
876 for(p0.X=0; p0.X<MAP_BLOCKSIZE; p0.X++)
877 for(p0.Y=0; p0.Y<MAP_BLOCKSIZE; p0.Y++)
878 for(p0.Z=0; p0.Z<MAP_BLOCKSIZE; p0.Z++)
880 const MapNode &n = block->getNodeUnsafe(p0);
881 content_t c = n.getContent();
882 // Cache content types as we go
883 if (!block->contents_cached && !block->do_not_cache_contents) {
884 block->contents.insert(c);
885 if (block->contents.size() > 64) {
886 // Too many different nodes... don't try to cache
887 block->do_not_cache_contents = true;
888 block->contents.clear();
892 if (c >= m_aabms.size() || !m_aabms[c])
895 v3s16 p = p0 + block->getPosRelative();
896 for (ActiveABM &aabm : *m_aabms[c]) {
897 if (myrand() % aabm.chance != 0)
901 if (aabm.check_required_neighbors) {
903 for(p1.X = p0.X-1; p1.X <= p0.X+1; p1.X++)
904 for(p1.Y = p0.Y-1; p1.Y <= p0.Y+1; p1.Y++)
905 for(p1.Z = p0.Z-1; p1.Z <= p0.Z+1; p1.Z++)
910 if (block->isValidPosition(p1)) {
911 // if the neighbor is found on the same map block
912 // get it straight from there
913 const MapNode &n = block->getNodeUnsafe(p1);
916 // otherwise consult the map
917 MapNode n = map->getNode(p1 + block->getPosRelative());
920 if (CONTAINS(aabm.required_neighbors, c))
923 // No required neighbor found
929 // Call all the trigger variations
930 aabm.abm->trigger(m_env, p, n);
931 aabm.abm->trigger(m_env, p, n,
932 active_object_count, active_object_count_wider);
934 // Count surrounding objects again if the abms added any
935 if(m_env->m_added_objects > 0) {
936 active_object_count = countObjects(block, map, active_object_count_wider);
937 m_env->m_added_objects = 0;
941 block->contents_cached = !block->do_not_cache_contents;
945 void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime)
947 // Reset usage timer immediately, otherwise a block that becomes active
948 // again at around the same time as it would normally be unloaded will
949 // get unloaded incorrectly. (I think this still leaves a small possibility
950 // of a race condition between this and server::AsyncRunStep, which only
951 // some kind of synchronisation will fix, but it at least reduces the window
952 // of opportunity for it to break from seconds to nanoseconds)
953 block->resetUsageTimer();
955 // Get time difference
957 u32 stamp = block->getTimestamp();
958 if (m_game_time > stamp && stamp != BLOCK_TIMESTAMP_UNDEFINED)
959 dtime_s = m_game_time - stamp;
960 dtime_s += additional_dtime;
962 /*infostream<<"ServerEnvironment::activateBlock(): block timestamp: "
963 <<stamp<<", game time: "<<m_game_time<<std::endl;*/
965 // Remove stored static objects if clearObjects was called since block's timestamp
966 if (stamp == BLOCK_TIMESTAMP_UNDEFINED || stamp < m_last_clear_objects_time) {
967 block->m_static_objects.m_stored.clear();
968 // do not set changed flag to avoid unnecessary mapblock writes
971 // Set current time as timestamp
972 block->setTimestampNoChangedFlag(m_game_time);
974 /*infostream<<"ServerEnvironment::activateBlock(): block is "
975 <<dtime_s<<" seconds old."<<std::endl;*/
977 // Activate stored objects
978 activateObjects(block, dtime_s);
980 /* Handle LoadingBlockModifiers */
981 m_lbm_mgr.applyLBMs(this, block, stamp);
984 std::vector<NodeTimer> elapsed_timers =
985 block->m_node_timers.step((float)dtime_s);
986 if (!elapsed_timers.empty()) {
988 for (const NodeTimer &elapsed_timer : elapsed_timers) {
989 n = block->getNodeNoEx(elapsed_timer.position);
990 v3s16 p = elapsed_timer.position + block->getPosRelative();
991 if (m_script->node_on_timer(p, n, elapsed_timer.elapsed))
992 block->setNodeTimer(NodeTimer(elapsed_timer.timeout, 0,
993 elapsed_timer.position));
998 void ServerEnvironment::addActiveBlockModifier(ActiveBlockModifier *abm)
1000 m_abms.emplace_back(abm);
1003 void ServerEnvironment::addLoadingBlockModifierDef(LoadingBlockModifierDef *lbm)
1005 m_lbm_mgr.addLBMDef(lbm);
1008 bool ServerEnvironment::setNode(v3s16 p, const MapNode &n)
1010 const NodeDefManager *ndef = m_server->ndef();
1011 MapNode n_old = m_map->getNode(p);
1013 const ContentFeatures &cf_old = ndef->get(n_old);
1016 if (cf_old.has_on_destruct)
1017 m_script->node_on_destruct(p, n_old);
1020 if (!m_map->addNodeWithEvent(p, n))
1023 // Update active VoxelManipulator if a mapgen thread
1024 m_map->updateVManip(p);
1026 // Call post-destructor
1027 if (cf_old.has_after_destruct)
1028 m_script->node_after_destruct(p, n_old);
1030 // Retrieve node content features
1031 // if new node is same as old, reuse old definition to prevent a lookup
1032 const ContentFeatures &cf_new = n_old == n ? cf_old : ndef->get(n);
1035 if (cf_new.has_on_construct)
1036 m_script->node_on_construct(p, n);
1041 bool ServerEnvironment::removeNode(v3s16 p)
1043 const NodeDefManager *ndef = m_server->ndef();
1044 MapNode n_old = m_map->getNode(p);
1047 if (ndef->get(n_old).has_on_destruct)
1048 m_script->node_on_destruct(p, n_old);
1051 // This is slightly optimized compared to addNodeWithEvent(air)
1052 if (!m_map->removeNodeWithEvent(p))
1055 // Update active VoxelManipulator if a mapgen thread
1056 m_map->updateVManip(p);
1058 // Call post-destructor
1059 if (ndef->get(n_old).has_after_destruct)
1060 m_script->node_after_destruct(p, n_old);
1062 // Air doesn't require constructor
1066 bool ServerEnvironment::swapNode(v3s16 p, const MapNode &n)
1068 if (!m_map->addNodeWithEvent(p, n, false))
1071 // Update active VoxelManipulator if a mapgen thread
1072 m_map->updateVManip(p);
1077 void ServerEnvironment::clearObjects(ClearObjectsMode mode)
1079 infostream << "ServerEnvironment::clearObjects(): "
1080 << "Removing all active objects" << std::endl;
1081 auto cb_removal = [this] (ServerActiveObject *obj, u16 id) {
1082 if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER)
1085 // Delete static object if block is loaded
1086 deleteStaticFromBlock(obj, id, MOD_REASON_CLEAR_ALL_OBJECTS, true);
1088 // If known by some client, don't delete immediately
1089 if (obj->m_known_by_count > 0) {
1090 obj->m_pending_removal = true;
1094 // Tell the object about removal
1095 obj->removingFromEnvironment();
1096 // Deregister in scripting api
1097 m_script->removeObjectReference(obj);
1099 // Delete active object
1100 if (obj->environmentDeletes())
1106 m_ao_manager.clear(cb_removal);
1108 // Get list of loaded blocks
1109 std::vector<v3s16> loaded_blocks;
1110 infostream << "ServerEnvironment::clearObjects(): "
1111 << "Listing all loaded blocks" << std::endl;
1112 m_map->listAllLoadedBlocks(loaded_blocks);
1113 infostream << "ServerEnvironment::clearObjects(): "
1114 << "Done listing all loaded blocks: "
1115 << loaded_blocks.size()<<std::endl;
1117 // Get list of loadable blocks
1118 std::vector<v3s16> loadable_blocks;
1119 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1120 infostream << "ServerEnvironment::clearObjects(): "
1121 << "Listing all loadable blocks" << std::endl;
1122 m_map->listAllLoadableBlocks(loadable_blocks);
1123 infostream << "ServerEnvironment::clearObjects(): "
1124 << "Done listing all loadable blocks: "
1125 << loadable_blocks.size() << std::endl;
1127 loadable_blocks = loaded_blocks;
1130 actionstream << "ServerEnvironment::clearObjects(): "
1131 << "Now clearing objects in " << loadable_blocks.size()
1132 << " blocks" << std::endl;
1134 // Grab a reference on each loaded block to avoid unloading it
1135 for (v3s16 p : loaded_blocks) {
1136 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1137 assert(block != NULL);
1141 // Remove objects in all loadable blocks
1142 u32 unload_interval = U32_MAX;
1143 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1144 unload_interval = g_settings->getS32("max_clearobjects_extra_loaded_blocks");
1145 unload_interval = MYMAX(unload_interval, 1);
1147 u32 report_interval = loadable_blocks.size() / 10;
1148 u32 num_blocks_checked = 0;
1149 u32 num_blocks_cleared = 0;
1150 u32 num_objs_cleared = 0;
1151 for (auto i = loadable_blocks.begin();
1152 i != loadable_blocks.end(); ++i) {
1154 MapBlock *block = m_map->emergeBlock(p, false);
1156 errorstream << "ServerEnvironment::clearObjects(): "
1157 << "Failed to emerge block " << PP(p) << std::endl;
1160 u32 num_stored = block->m_static_objects.m_stored.size();
1161 u32 num_active = block->m_static_objects.m_active.size();
1162 if (num_stored != 0 || num_active != 0) {
1163 block->m_static_objects.m_stored.clear();
1164 block->m_static_objects.m_active.clear();
1165 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1166 MOD_REASON_CLEAR_ALL_OBJECTS);
1167 num_objs_cleared += num_stored + num_active;
1168 num_blocks_cleared++;
1170 num_blocks_checked++;
1172 if (report_interval != 0 &&
1173 num_blocks_checked % report_interval == 0) {
1174 float percent = 100.0 * (float)num_blocks_checked /
1175 loadable_blocks.size();
1176 actionstream << "ServerEnvironment::clearObjects(): "
1177 << "Cleared " << num_objs_cleared << " objects"
1178 << " in " << num_blocks_cleared << " blocks ("
1179 << percent << "%)" << std::endl;
1181 if (num_blocks_checked % unload_interval == 0) {
1182 m_map->unloadUnreferencedBlocks();
1185 m_map->unloadUnreferencedBlocks();
1187 // Drop references that were added above
1188 for (v3s16 p : loaded_blocks) {
1189 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1194 m_last_clear_objects_time = m_game_time;
1196 actionstream << "ServerEnvironment::clearObjects(): "
1197 << "Finished: Cleared " << num_objs_cleared << " objects"
1198 << " in " << num_blocks_cleared << " blocks" << std::endl;
1201 void ServerEnvironment::step(float dtime)
1203 ScopeProfiler sp2(g_profiler, "ServerEnv::step()", SPT_AVG);
1204 /* Step time of day */
1205 stepTimeOfDay(dtime);
1208 // NOTE: This is kind of funny on a singleplayer game, but doesn't
1209 // really matter that much.
1210 static thread_local const float server_step =
1211 g_settings->getFloat("dedicated_server_step");
1212 m_recommended_send_interval = server_step;
1218 m_game_time_fraction_counter += dtime;
1219 u32 inc_i = (u32)m_game_time_fraction_counter;
1220 m_game_time += inc_i;
1221 m_game_time_fraction_counter -= (float)inc_i;
1228 ScopeProfiler sp(g_profiler, "ServerEnv: move players", SPT_AVG);
1229 for (RemotePlayer *player : m_players) {
1230 // Ignore disconnected players
1231 if (player->getPeerId() == PEER_ID_INEXISTENT)
1235 player->move(dtime, this, 100 * BS);
1240 Manage active block list
1242 if (m_active_blocks_management_interval.step(dtime, m_cache_active_block_mgmt_interval)) {
1243 ScopeProfiler sp(g_profiler, "ServerEnv: update active blocks", SPT_AVG);
1245 Get player block positions
1247 std::vector<PlayerSAO*> players;
1248 for (RemotePlayer *player: m_players) {
1249 // Ignore disconnected players
1250 if (player->getPeerId() == PEER_ID_INEXISTENT)
1253 PlayerSAO *playersao = player->getPlayerSAO();
1256 players.push_back(playersao);
1260 Update list of active blocks, collecting changes
1262 // use active_object_send_range_blocks since that is max distance
1263 // for active objects sent the client anyway
1264 static thread_local const s16 active_object_range =
1265 g_settings->getS16("active_object_send_range_blocks");
1266 static thread_local const s16 active_block_range =
1267 g_settings->getS16("active_block_range");
1268 std::set<v3s16> blocks_removed;
1269 std::set<v3s16> blocks_added;
1270 m_active_blocks.update(players, active_block_range, active_object_range,
1271 blocks_removed, blocks_added);
1274 Handle removed blocks
1277 // Convert active objects that are no more in active blocks to static
1278 deactivateFarObjects(false);
1280 for (const v3s16 &p: blocks_removed) {
1281 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1285 // Set current time as timestamp (and let it set ChangedFlag)
1286 block->setTimestamp(m_game_time);
1293 for (const v3s16 &p: blocks_added) {
1294 MapBlock *block = m_map->getBlockOrEmerge(p);
1296 m_active_blocks.m_list.erase(p);
1297 m_active_blocks.m_abm_list.erase(p);
1301 activateBlock(block);
1306 Mess around in active blocks
1308 if (m_active_blocks_nodemetadata_interval.step(dtime, m_cache_nodetimer_interval)) {
1309 ScopeProfiler sp(g_profiler, "ServerEnv: Run node timers", SPT_AVG);
1311 float dtime = m_cache_nodetimer_interval;
1313 for (const v3s16 &p: m_active_blocks.m_list) {
1314 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1318 // Reset block usage timer
1319 block->resetUsageTimer();
1321 // Set current time as timestamp
1322 block->setTimestampNoChangedFlag(m_game_time);
1323 // If time has changed much from the one on disk,
1324 // set block to be saved when it is unloaded
1325 if(block->getTimestamp() > block->getDiskTimestamp() + 60)
1326 block->raiseModified(MOD_STATE_WRITE_AT_UNLOAD,
1327 MOD_REASON_BLOCK_EXPIRED);
1330 std::vector<NodeTimer> elapsed_timers = block->m_node_timers.step(dtime);
1331 if (!elapsed_timers.empty()) {
1334 for (const NodeTimer &elapsed_timer: elapsed_timers) {
1335 n = block->getNodeNoEx(elapsed_timer.position);
1336 p2 = elapsed_timer.position + block->getPosRelative();
1337 if (m_script->node_on_timer(p2, n, elapsed_timer.elapsed)) {
1338 block->setNodeTimer(NodeTimer(
1339 elapsed_timer.timeout, 0, elapsed_timer.position));
1346 if (m_active_block_modifier_interval.step(dtime, m_cache_abm_interval)) {
1347 ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg per interval", SPT_AVG);
1348 TimeTaker timer("modify in active blocks per interval");
1350 // Initialize handling of ActiveBlockModifiers
1351 ABMHandler abmhandler(m_abms, m_cache_abm_interval, this, true);
1353 int blocks_scanned = 0;
1355 int blocks_cached = 0;
1357 std::vector<v3s16> output(m_active_blocks.m_abm_list.size());
1359 // Shuffle the active blocks so that each block gets an equal chance
1360 // of having its ABMs run.
1361 std::copy(m_active_blocks.m_abm_list.begin(), m_active_blocks.m_abm_list.end(), output.begin());
1362 std::shuffle(output.begin(), output.end(), m_rgen);
1365 // The time budget for ABMs is 20%.
1366 u32 max_time_ms = m_cache_abm_interval * 1000 / 5;
1367 for (const v3s16 &p : output) {
1368 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1374 // Set current time as timestamp
1375 block->setTimestampNoChangedFlag(m_game_time);
1377 /* Handle ActiveBlockModifiers */
1378 abmhandler.apply(block, blocks_scanned, abms_run, blocks_cached);
1380 u32 time_ms = timer.getTimerTime();
1382 if (time_ms > max_time_ms) {
1383 warningstream << "active block modifiers took "
1384 << time_ms << "ms (processed " << i << " of "
1385 << output.size() << " active blocks)" << std::endl;
1389 g_profiler->avg("ServerEnv: active blocks", m_active_blocks.m_abm_list.size());
1390 g_profiler->avg("ServerEnv: active blocks cached", blocks_cached);
1391 g_profiler->avg("ServerEnv: active blocks scanned for ABMs", blocks_scanned);
1392 g_profiler->avg("ServerEnv: ABMs run", abms_run);
1398 Step script environment (run global on_step())
1400 m_script->environment_Step(dtime);
1406 ScopeProfiler sp(g_profiler, "ServerEnv: Run SAO::step()", SPT_AVG);
1408 // This helps the objects to send data at the same time
1409 bool send_recommended = false;
1410 m_send_recommended_timer += dtime;
1411 if (m_send_recommended_timer > getSendRecommendedInterval()) {
1412 m_send_recommended_timer -= getSendRecommendedInterval();
1413 send_recommended = true;
1416 auto cb_state = [this, dtime, send_recommended] (ServerActiveObject *obj) {
1421 obj->step(dtime, send_recommended);
1422 // Read messages from object
1423 while (!obj->m_messages_out.empty()) {
1424 this->m_active_object_messages.push(obj->m_messages_out.front());
1425 obj->m_messages_out.pop();
1428 m_ao_manager.step(dtime, cb_state);
1432 Manage active objects
1434 if (m_object_management_interval.step(dtime, 0.5)) {
1435 removeRemovedObjects();
1439 Manage particle spawner expiration
1441 if (m_particle_management_interval.step(dtime, 1.0)) {
1442 for (std::unordered_map<u32, float>::iterator i = m_particle_spawners.begin();
1443 i != m_particle_spawners.end(); ) {
1444 //non expiring spawners
1445 if (i->second == PARTICLE_SPAWNER_NO_EXPIRY) {
1451 if (i->second <= 0.f)
1452 m_particle_spawners.erase(i++);
1458 // Send outdated player inventories
1459 for (RemotePlayer *player : m_players) {
1460 if (player->getPeerId() == PEER_ID_INEXISTENT)
1463 PlayerSAO *sao = player->getPlayerSAO();
1464 if (sao && player->inventory.checkModified())
1465 m_server->SendInventory(sao, true);
1468 // Send outdated detached inventories
1469 m_server->sendDetachedInventories(PEER_ID_INEXISTENT, true);
1472 u32 ServerEnvironment::addParticleSpawner(float exptime)
1474 // Timers with lifetime 0 do not expire
1475 float time = exptime > 0.f ? exptime : PARTICLE_SPAWNER_NO_EXPIRY;
1478 for (;;) { // look for unused particlespawner id
1480 std::unordered_map<u32, float>::iterator f = m_particle_spawners.find(id);
1481 if (f == m_particle_spawners.end()) {
1482 m_particle_spawners[id] = time;
1489 u32 ServerEnvironment::addParticleSpawner(float exptime, u16 attached_id)
1491 u32 id = addParticleSpawner(exptime);
1492 m_particle_spawner_attachments[id] = attached_id;
1493 if (ServerActiveObject *obj = getActiveObject(attached_id)) {
1494 obj->attachParticleSpawner(id);
1499 void ServerEnvironment::deleteParticleSpawner(u32 id, bool remove_from_object)
1501 m_particle_spawners.erase(id);
1502 const auto &it = m_particle_spawner_attachments.find(id);
1503 if (it != m_particle_spawner_attachments.end()) {
1504 u16 obj_id = it->second;
1505 ServerActiveObject *sao = getActiveObject(obj_id);
1506 if (sao != NULL && remove_from_object) {
1507 sao->detachParticleSpawner(id);
1509 m_particle_spawner_attachments.erase(id);
1513 u16 ServerEnvironment::addActiveObject(ServerActiveObject *object)
1515 assert(object); // Pre-condition
1517 u16 id = addActiveObjectRaw(object, true, 0);
1522 Finds out what new objects have been added to
1523 inside a radius around a position
1525 void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius,
1527 std::set<u16> ¤t_objects,
1528 std::queue<u16> &added_objects)
1530 f32 radius_f = radius * BS;
1531 f32 player_radius_f = player_radius * BS;
1533 if (player_radius_f < 0.0f)
1534 player_radius_f = 0.0f;
1536 m_ao_manager.getAddedActiveObjectsAroundPos(playersao->getBasePosition(), radius_f,
1537 player_radius_f, current_objects, added_objects);
1541 Finds out what objects have been removed from
1542 inside a radius around a position
1544 void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius,
1546 std::set<u16> ¤t_objects,
1547 std::queue<u16> &removed_objects)
1549 f32 radius_f = radius * BS;
1550 f32 player_radius_f = player_radius * BS;
1552 if (player_radius_f < 0)
1553 player_radius_f = 0;
1555 Go through current_objects; object is removed if:
1556 - object is not found in m_active_objects (this is actually an
1557 error condition; objects should be removed only after all clients
1558 have been informed about removal), or
1559 - object is to be removed or deactivated, or
1560 - object is too far away
1562 for (u16 id : current_objects) {
1563 ServerActiveObject *object = getActiveObject(id);
1565 if (object == NULL) {
1566 infostream << "ServerEnvironment::getRemovedActiveObjects():"
1567 << " object in current_objects is NULL" << std::endl;
1568 removed_objects.push(id);
1572 if (object->isGone()) {
1573 removed_objects.push(id);
1577 f32 distance_f = object->getBasePosition().getDistanceFrom(playersao->getBasePosition());
1578 if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1579 if (distance_f <= player_radius_f || player_radius_f == 0)
1581 } else if (distance_f <= radius_f)
1584 // Object is no longer visible
1585 removed_objects.push(id);
1589 void ServerEnvironment::setStaticForActiveObjectsInBlock(
1590 v3s16 blockpos, bool static_exists, v3s16 static_block)
1592 MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos);
1596 for (auto &so_it : block->m_static_objects.m_active) {
1597 // Get the ServerActiveObject counterpart to this StaticObject
1598 ServerActiveObject *sao = m_ao_manager.getActiveObject(so_it.first);
1600 // If this ever happens, there must be some kind of nasty bug.
1601 errorstream << "ServerEnvironment::setStaticForObjectsInBlock(): "
1602 "Object from MapBlock::m_static_objects::m_active not found "
1603 "in m_active_objects";
1607 sao->m_static_exists = static_exists;
1608 sao->m_static_block = static_block;
1612 ActiveObjectMessage ServerEnvironment::getActiveObjectMessage()
1614 if(m_active_object_messages.empty())
1615 return ActiveObjectMessage(0);
1617 ActiveObjectMessage message = m_active_object_messages.front();
1618 m_active_object_messages.pop();
1622 void ServerEnvironment::getSelectedActiveObjects(
1623 const core::line3d<f32> &shootline_on_map,
1624 std::vector<PointedThing> &objects)
1626 std::vector<u16> objectIds;
1627 getObjectsInsideRadius(objectIds, shootline_on_map.start,
1628 shootline_on_map.getLength() + 10.0f);
1629 const v3f line_vector = shootline_on_map.getVector();
1631 for (u16 objectId : objectIds) {
1632 ServerActiveObject* obj = getActiveObject(objectId);
1634 aabb3f selection_box;
1635 if (!obj->getSelectionBox(&selection_box))
1638 v3f pos = obj->getBasePosition();
1640 aabb3f offsetted_box(selection_box.MinEdge + pos,
1641 selection_box.MaxEdge + pos);
1643 v3f current_intersection;
1644 v3s16 current_normal;
1645 if (boxLineCollision(offsetted_box, shootline_on_map.start, line_vector,
1646 ¤t_intersection, ¤t_normal)) {
1647 objects.emplace_back(
1648 (s16) objectId, current_intersection, current_normal,
1649 (current_intersection - shootline_on_map.start).getLengthSQ());
1655 ************ Private methods *************
1658 u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object,
1659 bool set_changed, u32 dtime_s)
1661 if (!m_ao_manager.registerObject(object)) {
1665 // Register reference in scripting api (must be done before post-init)
1666 m_script->addObjectReference(object);
1667 // Post-initialize object
1668 object->addedToEnvironment(dtime_s);
1670 // Add static data to block
1671 if (object->isStaticAllowed()) {
1672 // Add static object to active static list of the block
1673 v3f objectpos = object->getBasePosition();
1674 StaticObject s_obj(object, objectpos);
1675 // Add to the block where the object is located in
1676 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
1677 MapBlock *block = m_map->emergeBlock(blockpos);
1679 block->m_static_objects.m_active[object->getId()] = s_obj;
1680 object->m_static_exists = true;
1681 object->m_static_block = blockpos;
1684 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1685 MOD_REASON_ADD_ACTIVE_OBJECT_RAW);
1687 v3s16 p = floatToInt(objectpos, BS);
1688 errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
1689 <<"could not emerge block for storing id="<<object->getId()
1690 <<" statically (pos="<<PP(p)<<")"<<std::endl;
1694 return object->getId();
1698 Remove objects that satisfy (isGone() && m_known_by_count==0)
1700 void ServerEnvironment::removeRemovedObjects()
1702 ScopeProfiler sp(g_profiler, "ServerEnvironment::removeRemovedObjects()", SPT_AVG);
1704 auto clear_cb = [this] (ServerActiveObject *obj, u16 id) {
1705 // This shouldn't happen but check it
1707 errorstream << "ServerEnvironment::removeRemovedObjects(): "
1708 << "NULL object found. id=" << id << std::endl;
1713 We will handle objects marked for removal or deactivation
1719 Delete static data from block if removed
1721 if (obj->m_pending_removal)
1722 deleteStaticFromBlock(obj, id, MOD_REASON_REMOVE_OBJECTS_REMOVE, false);
1724 // If still known by clients, don't actually remove. On some future
1725 // invocation this will be 0, which is when removal will continue.
1726 if(obj->m_known_by_count > 0)
1730 Move static data from active to stored if deactivated
1732 if (!obj->m_pending_removal && obj->m_static_exists) {
1733 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
1735 const auto i = block->m_static_objects.m_active.find(id);
1736 if (i != block->m_static_objects.m_active.end()) {
1737 block->m_static_objects.m_stored.push_back(i->second);
1738 block->m_static_objects.m_active.erase(id);
1739 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1740 MOD_REASON_REMOVE_OBJECTS_DEACTIVATE);
1742 warningstream << "ServerEnvironment::removeRemovedObjects(): "
1743 << "id=" << id << " m_static_exists=true but "
1744 << "static data doesn't actually exist in "
1745 << PP(obj->m_static_block) << std::endl;
1748 infostream << "Failed to emerge block from which an object to "
1749 << "be deactivated was loaded from. id=" << id << std::endl;
1753 // Tell the object about removal
1754 obj->removingFromEnvironment();
1755 // Deregister in scripting api
1756 m_script->removeObjectReference(obj);
1759 if (obj->environmentDeletes())
1765 m_ao_manager.clear(clear_cb);
1768 static void print_hexdump(std::ostream &o, const std::string &data)
1770 const int linelength = 16;
1771 for(int l=0; ; l++){
1772 int i0 = linelength * l;
1773 bool at_end = false;
1774 int thislinelength = linelength;
1775 if(i0 + thislinelength > (int)data.size()){
1776 thislinelength = data.size() - i0;
1779 for(int di=0; di<linelength; di++){
1782 if(di<thislinelength)
1783 porting::mt_snprintf(buf, sizeof(buf), "%.2x ", data[i]);
1785 porting::mt_snprintf(buf, sizeof(buf), " ");
1789 for(int di=0; di<thislinelength; di++){
1803 Convert stored objects from blocks near the players to active.
1805 void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s)
1810 // Ignore if no stored objects (to not set changed flag)
1811 if(block->m_static_objects.m_stored.empty())
1814 verbosestream<<"ServerEnvironment::activateObjects(): "
1815 <<"activating objects of block "<<PP(block->getPos())
1816 <<" ("<<block->m_static_objects.m_stored.size()
1817 <<" objects)"<<std::endl;
1818 bool large_amount = (block->m_static_objects.m_stored.size() > g_settings->getU16("max_objects_per_block"));
1820 errorstream<<"suspiciously large amount of objects detected: "
1821 <<block->m_static_objects.m_stored.size()<<" in "
1822 <<PP(block->getPos())
1823 <<"; removing all of them."<<std::endl;
1824 // Clear stored list
1825 block->m_static_objects.m_stored.clear();
1826 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1827 MOD_REASON_TOO_MANY_OBJECTS);
1831 // Activate stored objects
1832 std::vector<StaticObject> new_stored;
1833 for (const StaticObject &s_obj : block->m_static_objects.m_stored) {
1834 // Create an active object from the data
1835 ServerActiveObject *obj = ServerActiveObject::create
1836 ((ActiveObjectType) s_obj.type, this, 0, s_obj.pos, s_obj.data);
1837 // If couldn't create object, store static data back.
1839 errorstream<<"ServerEnvironment::activateObjects(): "
1840 <<"failed to create active object from static object "
1841 <<"in block "<<PP(s_obj.pos/BS)
1842 <<" type="<<(int)s_obj.type<<" data:"<<std::endl;
1843 print_hexdump(verbosestream, s_obj.data);
1845 new_stored.push_back(s_obj);
1848 verbosestream<<"ServerEnvironment::activateObjects(): "
1849 <<"activated static object pos="<<PP(s_obj.pos/BS)
1850 <<" type="<<(int)s_obj.type<<std::endl;
1851 // This will also add the object to the active static list
1852 addActiveObjectRaw(obj, false, dtime_s);
1855 // Clear stored list
1856 block->m_static_objects.m_stored.clear();
1857 // Add leftover failed stuff to stored list
1858 for (const StaticObject &s_obj : new_stored) {
1859 block->m_static_objects.m_stored.push_back(s_obj);
1863 Note: Block hasn't really been modified here.
1864 The objects have just been activated and moved from the stored
1865 static list to the active static list.
1866 As such, the block is essentially the same.
1867 Thus, do not call block->raiseModified(MOD_STATE_WRITE_NEEDED).
1868 Otherwise there would be a huge amount of unnecessary I/O.
1873 Convert objects that are not standing inside active blocks to static.
1875 If m_known_by_count != 0, active object is not deleted, but static
1876 data is still updated.
1878 If force_delete is set, active object is deleted nevertheless. It
1879 shall only be set so in the destructor of the environment.
1881 If block wasn't generated (not in memory or on disk),
1883 void ServerEnvironment::deactivateFarObjects(bool _force_delete)
1885 auto cb_deactivate = [this, _force_delete] (ServerActiveObject *obj, u16 id) {
1886 // force_delete might be overriden per object
1887 bool force_delete = _force_delete;
1889 // Do not deactivate if static data creation not allowed
1890 if (!force_delete && !obj->isStaticAllowed())
1893 // removeRemovedObjects() is responsible for these
1894 if (!force_delete && obj->isGone())
1897 const v3f &objectpos = obj->getBasePosition();
1899 // The block in which the object resides in
1900 v3s16 blockpos_o = getNodeBlockPos(floatToInt(objectpos, BS));
1902 // If object's static data is stored in a deactivated block and object
1903 // is actually located in an active block, re-save to the block in
1904 // which the object is actually located in.
1905 if (!force_delete && obj->m_static_exists &&
1906 !m_active_blocks.contains(obj->m_static_block) &&
1907 m_active_blocks.contains(blockpos_o)) {
1908 // Delete from block where object was located
1909 deleteStaticFromBlock(obj, id, MOD_REASON_STATIC_DATA_REMOVED, false);
1911 StaticObject s_obj(obj, objectpos);
1912 // Save to block where object is located
1913 saveStaticToBlock(blockpos_o, id, obj, s_obj, MOD_REASON_STATIC_DATA_ADDED);
1918 // If block is still active, don't remove
1919 if (!force_delete && m_active_blocks.contains(blockpos_o))
1922 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1923 << "deactivating object id=" << id << " on inactive block "
1924 << PP(blockpos_o) << std::endl;
1926 // If known by some client, don't immediately delete.
1927 bool pending_delete = (obj->m_known_by_count > 0 && !force_delete);
1930 Update the static data
1932 if (obj->isStaticAllowed()) {
1933 // Create new static object
1934 StaticObject s_obj(obj, objectpos);
1936 bool stays_in_same_block = false;
1937 bool data_changed = true;
1939 // Check if static data has changed considerably
1940 if (obj->m_static_exists) {
1941 if (obj->m_static_block == blockpos_o)
1942 stays_in_same_block = true;
1944 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
1947 const auto n = block->m_static_objects.m_active.find(id);
1948 if (n != block->m_static_objects.m_active.end()) {
1949 StaticObject static_old = n->second;
1951 float save_movem = obj->getMinimumSavedMovement();
1953 if (static_old.data == s_obj.data &&
1954 (static_old.pos - objectpos).getLength() < save_movem)
1955 data_changed = false;
1957 warningstream << "ServerEnvironment::deactivateFarObjects(): "
1958 << "id=" << id << " m_static_exists=true but "
1959 << "static data doesn't actually exist in "
1960 << PP(obj->m_static_block) << std::endl;
1966 While changes are always saved, blocks are only marked as modified
1967 if the object has moved or different staticdata. (see above)
1969 bool shall_be_written = (!stays_in_same_block || data_changed);
1970 u32 reason = shall_be_written ? MOD_REASON_STATIC_DATA_CHANGED : MOD_REASON_UNKNOWN;
1972 // Delete old static object
1973 deleteStaticFromBlock(obj, id, reason, false);
1975 // Add to the block where the object is located in
1976 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
1977 u16 store_id = pending_delete ? id : 0;
1978 if (!saveStaticToBlock(blockpos, store_id, obj, s_obj, reason))
1979 force_delete = true;
1983 If known by some client, set pending deactivation.
1984 Otherwise delete it immediately.
1986 if (pending_delete && !force_delete) {
1987 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1988 << "object id=" << id << " is known by clients"
1989 << "; not deleting yet" << std::endl;
1991 obj->m_pending_deactivation = true;
1995 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1996 << "object id=" << id << " is not known by clients"
1997 << "; deleting" << std::endl;
1999 // Tell the object about removal
2000 obj->removingFromEnvironment();
2001 // Deregister in scripting api
2002 m_script->removeObjectReference(obj);
2004 // Delete active object
2005 if (obj->environmentDeletes())
2011 m_ao_manager.clear(cb_deactivate);
2014 void ServerEnvironment::deleteStaticFromBlock(
2015 ServerActiveObject *obj, u16 id, u32 mod_reason, bool no_emerge)
2017 if (!obj->m_static_exists)
2022 block = m_map->getBlockNoCreateNoEx(obj->m_static_block);
2024 block = m_map->emergeBlock(obj->m_static_block, false);
2027 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2028 << " when deleting static data of object from it. id=" << id << std::endl;
2032 block->m_static_objects.remove(id);
2033 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2034 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2036 obj->m_static_exists = false;
2039 bool ServerEnvironment::saveStaticToBlock(
2040 v3s16 blockpos, u16 store_id,
2041 ServerActiveObject *obj, const StaticObject &s_obj,
2044 MapBlock *block = nullptr;
2046 block = m_map->emergeBlock(blockpos);
2047 } catch (InvalidPositionException &e) {
2048 // Handled via NULL pointer
2049 // NOTE: emergeBlock's failure is usually determined by it
2050 // actually returning NULL
2054 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2055 << " when saving static data of object to it. id=" << store_id << std::endl;
2058 if (block->m_static_objects.m_stored.size() >= g_settings->getU16("max_objects_per_block")) {
2059 warningstream << "ServerEnv: Trying to store id = " << store_id
2060 << " statically but block " << PP(blockpos)
2061 << " already contains "
2062 << block->m_static_objects.m_stored.size()
2063 << " objects." << std::endl;
2067 block->m_static_objects.insert(store_id, s_obj);
2068 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2069 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2071 obj->m_static_exists = true;
2072 obj->m_static_block = blockpos;
2077 PlayerDatabase *ServerEnvironment::openPlayerDatabase(const std::string &name,
2078 const std::string &savedir, const Settings &conf)
2081 if (name == "sqlite3")
2082 return new PlayerDatabaseSQLite3(savedir);
2084 if (name == "dummy")
2085 return new Database_Dummy();
2087 if (name == "postgresql") {
2088 std::string connect_string;
2089 conf.getNoEx("pgsql_player_connection", connect_string);
2090 return new PlayerDatabasePostgreSQL(connect_string);
2093 if (name == "files")
2094 return new PlayerDatabaseFiles(savedir + DIR_DELIM + "players");
2096 throw BaseException(std::string("Database backend ") + name + " not supported.");
2099 bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params,
2100 const Settings &cmd_args)
2102 std::string migrate_to = cmd_args.get("migrate-players");
2104 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2105 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2106 errorstream << "Cannot read world.mt!" << std::endl;
2110 if (!world_mt.exists("player_backend")) {
2111 errorstream << "Please specify your current backend in world.mt:"
2113 << " player_backend = {files|sqlite3|postgresql}"
2118 std::string backend = world_mt.get("player_backend");
2119 if (backend == migrate_to) {
2120 errorstream << "Cannot migrate: new backend is same"
2121 << " as the old one" << std::endl;
2125 const std::string players_backup_path = game_params.world_path + DIR_DELIM
2128 if (backend == "files") {
2129 // Create backup directory
2130 fs::CreateDir(players_backup_path);
2134 PlayerDatabase *srcdb = ServerEnvironment::openPlayerDatabase(backend,
2135 game_params.world_path, world_mt);
2136 PlayerDatabase *dstdb = ServerEnvironment::openPlayerDatabase(migrate_to,
2137 game_params.world_path, world_mt);
2139 std::vector<std::string> player_list;
2140 srcdb->listPlayers(player_list);
2141 for (std::vector<std::string>::const_iterator it = player_list.begin();
2142 it != player_list.end(); ++it) {
2143 actionstream << "Migrating player " << it->c_str() << std::endl;
2144 RemotePlayer player(it->c_str(), NULL);
2145 PlayerSAO playerSAO(NULL, &player, 15000, false);
2147 srcdb->loadPlayer(&player, &playerSAO);
2149 playerSAO.finalize(&player, std::set<std::string>());
2150 player.setPlayerSAO(&playerSAO);
2152 dstdb->savePlayer(&player);
2154 // For files source, move player files to backup dir
2155 if (backend == "files") {
2157 game_params.world_path + DIR_DELIM + "players" + DIR_DELIM + (*it),
2158 players_backup_path + DIR_DELIM + (*it));
2162 actionstream << "Successfully migrated " << player_list.size() << " players"
2164 world_mt.set("player_backend", migrate_to);
2165 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2166 errorstream << "Failed to update world.mt!" << std::endl;
2168 actionstream << "world.mt updated" << std::endl;
2170 // When migration is finished from file backend, remove players directory if empty
2171 if (backend == "files") {
2172 fs::DeleteSingleFileOrEmptyDirectory(game_params.world_path + DIR_DELIM
2179 } catch (BaseException &e) {
2180 errorstream << "An error occurred during migration: " << e.what() << std::endl;
2186 AuthDatabase *ServerEnvironment::openAuthDatabase(
2187 const std::string &name, const std::string &savedir, const Settings &conf)
2189 if (name == "sqlite3")
2190 return new AuthDatabaseSQLite3(savedir);
2192 if (name == "files")
2193 return new AuthDatabaseFiles(savedir);
2195 throw BaseException(std::string("Database backend ") + name + " not supported.");
2198 bool ServerEnvironment::migrateAuthDatabase(
2199 const GameParams &game_params, const Settings &cmd_args)
2201 std::string migrate_to = cmd_args.get("migrate-auth");
2203 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2204 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2205 errorstream << "Cannot read world.mt!" << std::endl;
2209 std::string backend = "files";
2210 if (world_mt.exists("auth_backend"))
2211 backend = world_mt.get("auth_backend");
2213 warningstream << "No auth_backend found in world.mt, "
2214 "assuming \"files\"." << std::endl;
2216 if (backend == migrate_to) {
2217 errorstream << "Cannot migrate: new backend is same"
2218 << " as the old one" << std::endl;
2223 const std::unique_ptr<AuthDatabase> srcdb(ServerEnvironment::openAuthDatabase(
2224 backend, game_params.world_path, world_mt));
2225 const std::unique_ptr<AuthDatabase> dstdb(ServerEnvironment::openAuthDatabase(
2226 migrate_to, game_params.world_path, world_mt));
2228 std::vector<std::string> names_list;
2229 srcdb->listNames(names_list);
2230 for (const std::string &name : names_list) {
2231 actionstream << "Migrating auth entry for " << name << std::endl;
2233 AuthEntry authEntry;
2234 success = srcdb->getAuth(name, authEntry);
2235 success = success && dstdb->createAuth(authEntry);
2237 errorstream << "Failed to migrate " << name << std::endl;
2240 actionstream << "Successfully migrated " << names_list.size()
2241 << " auth entries" << std::endl;
2242 world_mt.set("auth_backend", migrate_to);
2243 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2244 errorstream << "Failed to update world.mt!" << std::endl;
2246 actionstream << "world.mt updated" << std::endl;
2248 if (backend == "files") {
2249 // special-case files migration:
2250 // move auth.txt to auth.txt.bak if possible
2251 std::string auth_txt_path =
2252 game_params.world_path + DIR_DELIM + "auth.txt";
2253 std::string auth_bak_path = auth_txt_path + ".bak";
2254 if (!fs::PathExists(auth_bak_path))
2255 if (fs::Rename(auth_txt_path, auth_bak_path))
2256 actionstream << "Renamed auth.txt to auth.txt.bak"
2259 errorstream << "Could not rename auth.txt to "
2260 "auth.txt.bak" << std::endl;
2262 warningstream << "auth.txt.bak already exists, auth.txt "
2263 "not renamed" << std::endl;
2266 } catch (BaseException &e) {
2267 errorstream << "An error occurred during migration: " << e.what()