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"
31 #include "remoteplayer.h"
32 #include "scripting_server.h"
34 #include "util/serialize.h"
35 #include "util/basic_macros.h"
36 #include "util/pointedthing.h"
37 #include "threading/mutex_auto_lock.h"
39 #include "gameparams.h"
40 #include "database/database-dummy.h"
41 #include "database/database-files.h"
42 #include "database/database-sqlite3.h"
44 #include "database/database-postgresql.h"
48 #define LBM_NAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_:"
50 // A number that is much smaller than the timeout for particle spawners should/could ever be
51 #define PARTICLE_SPAWNER_NO_EXPIRY -1024.f
57 ABMWithState::ABMWithState(ActiveBlockModifier *abm_):
60 // Initialize timer to random value to spread processing
61 float itv = abm->getTriggerInterval();
62 itv = MYMAX(0.001, itv); // No less than 1ms
63 int minval = MYMAX(-0.51*itv, -60); // Clamp to
64 int maxval = MYMIN(0.51*itv, 60); // +-60 seconds
65 timer = myrand_range(minval, maxval);
72 void LBMContentMapping::deleteContents()
74 for (auto &it : lbm_list) {
79 void LBMContentMapping::addLBM(LoadingBlockModifierDef *lbm_def, IGameDef *gamedef)
81 // Add the lbm_def to the LBMContentMapping.
82 // Unknown names get added to the global NameIdMapping.
83 const NodeDefManager *nodedef = gamedef->ndef();
85 lbm_list.push_back(lbm_def);
87 for (const std::string &nodeTrigger: lbm_def->trigger_contents) {
88 std::vector<content_t> c_ids;
89 bool found = nodedef->getIds(nodeTrigger, c_ids);
91 content_t c_id = gamedef->allocateUnknownNodeId(nodeTrigger);
92 if (c_id == CONTENT_IGNORE) {
93 // Seems it can't be allocated.
94 warningstream << "Could not internalize node name \"" << nodeTrigger
95 << "\" while loading LBM \"" << lbm_def->name << "\"." << std::endl;
98 c_ids.push_back(c_id);
101 for (content_t c_id : c_ids) {
102 map[c_id].push_back(lbm_def);
107 const std::vector<LoadingBlockModifierDef *> *
108 LBMContentMapping::lookup(content_t c) const
110 lbm_map::const_iterator it = map.find(c);
113 // This first dereferences the iterator, returning
114 // a std::vector<LoadingBlockModifierDef *>
115 // reference, then we convert it to a pointer.
116 return &(it->second);
119 LBMManager::~LBMManager()
121 for (auto &m_lbm_def : m_lbm_defs) {
122 delete m_lbm_def.second;
125 for (auto &it : m_lbm_lookup) {
126 (it.second).deleteContents();
130 void LBMManager::addLBMDef(LoadingBlockModifierDef *lbm_def)
132 // Precondition, in query mode the map isn't used anymore
133 FATAL_ERROR_IF(m_query_mode,
134 "attempted to modify LBMManager in query mode");
136 if (!string_allowed(lbm_def->name, LBM_NAME_ALLOWED_CHARS)) {
137 throw ModError("Error adding LBM \"" + lbm_def->name +
138 "\": Does not follow naming conventions: "
139 "Only characters [a-z0-9_:] are allowed.");
142 m_lbm_defs[lbm_def->name] = lbm_def;
145 void LBMManager::loadIntroductionTimes(const std::string ×,
146 IGameDef *gamedef, u32 now)
151 // Storing it in a map first instead of
152 // handling the stuff directly in the loop
153 // removes all duplicate entries.
154 // TODO make this std::unordered_map
155 std::map<std::string, u32> introduction_times;
158 The introduction times string consists of name~time entries,
159 with each entry terminated by a semicolon. The time is decimal.
164 while ((idx_new = times.find(';', idx)) != std::string::npos) {
165 std::string entry = times.substr(idx, idx_new - idx);
166 std::vector<std::string> components = str_split(entry, '~');
167 if (components.size() != 2)
168 throw SerializationError("Introduction times entry \""
169 + entry + "\" requires exactly one '~'!");
170 const std::string &name = components[0];
171 u32 time = from_string<u32>(components[1]);
172 introduction_times[name] = time;
176 // Put stuff from introduction_times into m_lbm_lookup
177 for (std::map<std::string, u32>::const_iterator it = introduction_times.begin();
178 it != introduction_times.end(); ++it) {
179 const std::string &name = it->first;
180 u32 time = it->second;
182 std::map<std::string, LoadingBlockModifierDef *>::iterator def_it =
183 m_lbm_defs.find(name);
184 if (def_it == m_lbm_defs.end()) {
185 // This seems to be an LBM entry for
186 // an LBM we haven't loaded. Discard it.
189 LoadingBlockModifierDef *lbm_def = def_it->second;
190 if (lbm_def->run_at_every_load) {
191 // This seems to be an LBM entry for
192 // an LBM that runs at every load.
193 // Don't add it just yet.
197 m_lbm_lookup[time].addLBM(lbm_def, gamedef);
199 // Erase the entry so that we know later
200 // what elements didn't get put into m_lbm_lookup
201 m_lbm_defs.erase(name);
204 // Now also add the elements from m_lbm_defs to m_lbm_lookup
205 // that weren't added in the previous step.
206 // They are introduced first time to this world,
207 // or are run at every load (introducement time hardcoded to U32_MAX).
209 LBMContentMapping &lbms_we_introduce_now = m_lbm_lookup[now];
210 LBMContentMapping &lbms_running_always = m_lbm_lookup[U32_MAX];
212 for (auto &m_lbm_def : m_lbm_defs) {
213 if (m_lbm_def.second->run_at_every_load) {
214 lbms_running_always.addLBM(m_lbm_def.second, gamedef);
216 lbms_we_introduce_now.addLBM(m_lbm_def.second, gamedef);
220 // Clear the list, so that we don't delete remaining elements
221 // twice in the destructor
225 std::string LBMManager::createIntroductionTimesString()
227 // Precondition, we must be in query mode
228 FATAL_ERROR_IF(!m_query_mode,
229 "attempted to query on non fully set up LBMManager");
231 std::ostringstream oss;
232 for (const auto &it : m_lbm_lookup) {
234 const std::vector<LoadingBlockModifierDef *> &lbm_list = it.second.lbm_list;
235 for (const auto &lbm_def : lbm_list) {
236 // Don't add if the LBM runs at every load,
237 // then introducement time is hardcoded
238 // and doesn't need to be stored
239 if (lbm_def->run_at_every_load)
241 oss << lbm_def->name << "~" << time << ";";
247 void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, u32 stamp)
249 // Precondition, we need m_lbm_lookup to be initialized
250 FATAL_ERROR_IF(!m_query_mode,
251 "attempted to query on non fully set up LBMManager");
252 v3s16 pos_of_block = block->getPosRelative();
256 lbm_lookup_map::const_iterator it = getLBMsIntroducedAfter(stamp);
257 for (pos.X = 0; pos.X < MAP_BLOCKSIZE; pos.X++)
258 for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++)
259 for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++)
261 n = block->getNodeNoEx(pos);
263 for (LBMManager::lbm_lookup_map::const_iterator iit = it;
264 iit != m_lbm_lookup.end(); ++iit) {
265 const std::vector<LoadingBlockModifierDef *> *lbm_list =
266 iit->second.lookup(c);
269 for (auto lbmdef : *lbm_list) {
270 lbmdef->trigger(env, pos + pos_of_block, n);
280 void fillRadiusBlock(v3s16 p0, s16 r, std::set<v3s16> &list)
283 for(p.X=p0.X-r; p.X<=p0.X+r; p.X++)
284 for(p.Y=p0.Y-r; p.Y<=p0.Y+r; p.Y++)
285 for(p.Z=p0.Z-r; p.Z<=p0.Z+r; p.Z++)
288 if (p.getDistanceFrom(p0) <= r) {
295 void fillViewConeBlock(v3s16 p0,
297 const v3f camera_pos,
298 const v3f camera_dir,
299 const float camera_fov,
300 std::set<v3s16> &list)
303 const s16 r_nodes = r * BS * MAP_BLOCKSIZE;
304 for (p.X = p0.X - r; p.X <= p0.X+r; p.X++)
305 for (p.Y = p0.Y - r; p.Y <= p0.Y+r; p.Y++)
306 for (p.Z = p0.Z - r; p.Z <= p0.Z+r; p.Z++) {
307 if (isBlockInSight(p, camera_pos, camera_dir, camera_fov, r_nodes)) {
313 void ActiveBlockList::update(std::vector<PlayerSAO*> &active_players,
314 s16 active_block_range,
315 s16 active_object_range,
316 std::set<v3s16> &blocks_removed,
317 std::set<v3s16> &blocks_added)
322 std::set<v3s16> newlist = m_forceloaded_list;
323 m_abm_list = m_forceloaded_list;
324 for (const PlayerSAO *playersao : active_players) {
325 v3s16 pos = getNodeBlockPos(floatToInt(playersao->getBasePosition(), BS));
326 fillRadiusBlock(pos, active_block_range, m_abm_list);
327 fillRadiusBlock(pos, active_block_range, newlist);
329 s16 player_ao_range = std::min(active_object_range, playersao->getWantedRange());
330 // only do this if this would add blocks
331 if (player_ao_range > active_block_range) {
332 v3f camera_dir = v3f(0,0,1);
333 camera_dir.rotateYZBy(playersao->getPitch());
334 camera_dir.rotateXZBy(playersao->getYaw());
335 fillViewConeBlock(pos,
337 playersao->getEyePosition(),
345 Find out which blocks on the old list are not on the new list
347 // Go through old list
348 for (v3s16 p : m_list) {
349 // If not on new list, it's been removed
350 if (newlist.find(p) == newlist.end())
351 blocks_removed.insert(p);
355 Find out which blocks on the new list are not on the old list
357 // Go through new list
358 for (v3s16 p : newlist) {
359 // If not on old list, it's been added
360 if(m_list.find(p) == m_list.end())
361 blocks_added.insert(p);
368 for (v3s16 p : newlist) {
377 ServerEnvironment::ServerEnvironment(ServerMap *map,
378 ServerScripting *scriptIface, Server *server,
379 const std::string &path_world):
382 m_script(scriptIface),
384 m_path_world(path_world)
386 // Determine which database backend to use
387 std::string conf_path = path_world + DIR_DELIM + "world.mt";
389 bool succeeded = conf.readConfigFile(conf_path.c_str());
390 if (!succeeded || !conf.exists("player_backend")) {
391 // fall back to files
392 conf.set("player_backend", "files");
393 warningstream << "/!\\ You are using old player file backend. "
394 << "This backend is deprecated and will be removed in next release /!\\"
395 << std::endl << "Switching to SQLite3 or PostgreSQL is advised, "
396 << "please read http://wiki.minetest.net/Database_backends." << std::endl;
398 if (!conf.updateConfigFile(conf_path.c_str())) {
399 errorstream << "ServerEnvironment::ServerEnvironment(): "
400 << "Failed to update world.mt!" << std::endl;
405 conf.getNoEx("player_backend", name);
406 m_player_database = openPlayerDatabase(name, path_world, conf);
409 ServerEnvironment::~ServerEnvironment()
411 // Clear active block list.
412 // This makes the next one delete all active objects.
413 m_active_blocks.clear();
415 // Convert all objects to static and delete the active objects
416 deactivateFarObjects(true);
421 // Delete ActiveBlockModifiers
422 for (ABMWithState &m_abm : m_abms) {
426 // Deallocate players
427 for (RemotePlayer *m_player : m_players) {
431 delete m_player_database;
434 Map & ServerEnvironment::getMap()
439 ServerMap & ServerEnvironment::getServerMap()
444 RemotePlayer *ServerEnvironment::getPlayer(const session_t peer_id)
446 for (RemotePlayer *player : m_players) {
447 if (player->getPeerId() == peer_id)
453 RemotePlayer *ServerEnvironment::getPlayer(const char* name)
455 for (RemotePlayer *player : m_players) {
456 if (strcmp(player->getName(), name) == 0)
462 void ServerEnvironment::addPlayer(RemotePlayer *player)
465 Check that peer_ids are unique.
466 Also check that names are unique.
467 Exception: there can be multiple players with peer_id=0
469 // If peer id is non-zero, it has to be unique.
470 if (player->getPeerId() != PEER_ID_INEXISTENT)
471 FATAL_ERROR_IF(getPlayer(player->getPeerId()) != NULL, "Peer id not unique");
472 // Name has to be unique.
473 FATAL_ERROR_IF(getPlayer(player->getName()) != NULL, "Player name not unique");
475 m_players.push_back(player);
478 void ServerEnvironment::removePlayer(RemotePlayer *player)
480 for (std::vector<RemotePlayer *>::iterator it = m_players.begin();
481 it != m_players.end(); ++it) {
482 if ((*it) == player) {
490 bool ServerEnvironment::removePlayerFromDatabase(const std::string &name)
492 return m_player_database->removePlayer(name);
495 bool ServerEnvironment::line_of_sight(v3f pos1, v3f pos2, v3s16 *p)
497 // Iterate trough nodes on the line
498 voxalgo::VoxelLineIterator iterator(pos1 / BS, (pos2 - pos1) / BS);
500 MapNode n = getMap().getNodeNoEx(iterator.m_current_node_pos);
503 if (n.param0 != CONTENT_AIR) {
505 *p = iterator.m_current_node_pos;
509 } while (iterator.m_current_index <= iterator.m_last_index);
513 void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason,
514 const std::string &str_reason, bool reconnect)
516 for (RemotePlayer *player : m_players) {
517 m_server->DenyAccessVerCompliant(player->getPeerId(),
518 player->protocol_version, reason, str_reason, reconnect);
522 void ServerEnvironment::saveLoadedPlayers()
524 std::string players_path = m_path_world + DIR_DELIM + "players";
525 fs::CreateDir(players_path);
527 for (RemotePlayer *player : m_players) {
528 if (player->checkModified() || (player->getPlayerSAO() &&
529 player->getPlayerSAO()->extendedAttributesModified())) {
531 m_player_database->savePlayer(player);
532 } catch (DatabaseException &e) {
533 errorstream << "Failed to save player " << player->getName() << " exception: "
534 << e.what() << std::endl;
541 void ServerEnvironment::savePlayer(RemotePlayer *player)
544 m_player_database->savePlayer(player);
545 } catch (DatabaseException &e) {
546 errorstream << "Failed to save player " << player->getName() << " exception: "
547 << e.what() << std::endl;
552 PlayerSAO *ServerEnvironment::loadPlayer(RemotePlayer *player, bool *new_player,
553 session_t peer_id, bool is_singleplayer)
555 PlayerSAO *playersao = new PlayerSAO(this, player, peer_id, is_singleplayer);
556 // Create player if it doesn't exist
557 if (!m_player_database->loadPlayer(player, playersao)) {
559 // Set player position
560 infostream << "Server: Finding spawn place for player \""
561 << player->getName() << "\"" << std::endl;
562 playersao->setBasePosition(m_server->findSpawnPos());
564 // Make sure the player is saved
565 player->setModified(true);
567 // If the player exists, ensure that they respawn inside legal bounds
568 // This fixes an assert crash when the player can't be added
569 // to the environment
570 if (objectpos_over_limit(playersao->getBasePosition())) {
571 actionstream << "Respawn position for player \""
572 << player->getName() << "\" outside limits, resetting" << std::endl;
573 playersao->setBasePosition(m_server->findSpawnPos());
577 // Add player to environment
580 /* Clean up old HUD elements from previous sessions */
583 /* Add object to environment */
584 addActiveObject(playersao);
589 void ServerEnvironment::saveMeta()
591 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
593 // Open file and serialize
594 std::ostringstream ss(std::ios_base::binary);
597 args.setU64("game_time", m_game_time);
598 args.setU64("time_of_day", getTimeOfDay());
599 args.setU64("last_clear_objects_time", m_last_clear_objects_time);
600 args.setU64("lbm_introduction_times_version", 1);
601 args.set("lbm_introduction_times",
602 m_lbm_mgr.createIntroductionTimesString());
603 args.setU64("day_count", m_day_count);
607 if(!fs::safeWriteToFile(path, ss.str()))
609 infostream<<"ServerEnvironment::saveMeta(): Failed to write "
611 throw SerializationError("Couldn't save env meta");
615 void ServerEnvironment::loadMeta()
617 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
619 // Open file and deserialize
620 std::ifstream is(path.c_str(), std::ios_base::binary);
622 infostream << "ServerEnvironment::loadMeta(): Failed to open "
623 << path << std::endl;
624 throw SerializationError("Couldn't load env meta");
629 if (!args.parseConfigLines(is, "EnvArgsEnd")) {
630 throw SerializationError("ServerEnvironment::loadMeta(): "
631 "EnvArgsEnd not found!");
635 m_game_time = args.getU64("game_time");
636 } catch (SettingNotFoundException &e) {
637 // Getting this is crucial, otherwise timestamps are useless
638 throw SerializationError("Couldn't load env meta game_time");
641 setTimeOfDay(args.exists("time_of_day") ?
642 // set day to early morning by default
643 args.getU64("time_of_day") : 5250);
645 m_last_clear_objects_time = args.exists("last_clear_objects_time") ?
646 // If missing, do as if clearObjects was never called
647 args.getU64("last_clear_objects_time") : 0;
649 std::string lbm_introduction_times;
651 u64 ver = args.getU64("lbm_introduction_times_version");
653 lbm_introduction_times = args.get("lbm_introduction_times");
655 infostream << "ServerEnvironment::loadMeta(): Non-supported"
656 << " introduction time version " << ver << std::endl;
658 } catch (SettingNotFoundException &e) {
659 // No problem, this is expected. Just continue with an empty string
661 m_lbm_mgr.loadIntroductionTimes(lbm_introduction_times, m_server, m_game_time);
663 m_day_count = args.exists("day_count") ?
664 args.getU64("day_count") : 0;
667 void ServerEnvironment::loadDefaultMeta()
669 m_lbm_mgr.loadIntroductionTimes("", m_server, m_game_time);
674 ActiveBlockModifier *abm;
676 std::vector<content_t> required_neighbors;
677 bool check_required_neighbors; // false if required_neighbors is known to be empty
683 ServerEnvironment *m_env;
684 std::vector<std::vector<ActiveABM> *> m_aabms;
686 ABMHandler(std::vector<ABMWithState> &abms,
687 float dtime_s, ServerEnvironment *env,
693 const NodeDefManager *ndef = env->getGameDef()->ndef();
694 for (ABMWithState &abmws : abms) {
695 ActiveBlockModifier *abm = abmws.abm;
696 float trigger_interval = abm->getTriggerInterval();
697 if(trigger_interval < 0.001)
698 trigger_interval = 0.001;
699 float actual_interval = dtime_s;
701 abmws.timer += dtime_s;
702 if(abmws.timer < trigger_interval)
704 abmws.timer -= trigger_interval;
705 actual_interval = trigger_interval;
707 float chance = abm->getTriggerChance();
712 if (abm->getSimpleCatchUp()) {
713 float intervals = actual_interval / trigger_interval;
716 aabm.chance = chance / intervals;
720 aabm.chance = chance;
724 const std::vector<std::string> &required_neighbors_s =
725 abm->getRequiredNeighbors();
726 for (const std::string &required_neighbor_s : required_neighbors_s) {
727 ndef->getIds(required_neighbor_s, aabm.required_neighbors);
729 aabm.check_required_neighbors = !required_neighbors_s.empty();
732 const std::vector<std::string> &contents_s = abm->getTriggerContents();
733 for (const std::string &content_s : contents_s) {
734 std::vector<content_t> ids;
735 ndef->getIds(content_s, ids);
736 for (content_t c : ids) {
737 if (c >= m_aabms.size())
738 m_aabms.resize(c + 256, NULL);
740 m_aabms[c] = new std::vector<ActiveABM>;
741 m_aabms[c]->push_back(aabm);
749 for (auto &aabms : m_aabms)
753 // Find out how many objects the given block and its neighbours contain.
754 // Returns the number of objects in the block, and also in 'wider' the
755 // number of objects in the block and all its neighbours. The latter
756 // may an estimate if any neighbours are unloaded.
757 u32 countObjects(MapBlock *block, ServerMap * map, u32 &wider)
760 u32 wider_unknown_count = 0;
761 for(s16 x=-1; x<=1; x++)
762 for(s16 y=-1; y<=1; y++)
763 for(s16 z=-1; z<=1; z++)
765 MapBlock *block2 = map->getBlockNoCreateNoEx(
766 block->getPos() + v3s16(x,y,z));
768 wider_unknown_count++;
771 wider += block2->m_static_objects.m_active.size()
772 + block2->m_static_objects.m_stored.size();
775 u32 active_object_count = block->m_static_objects.m_active.size();
776 u32 wider_known_count = 3*3*3 - wider_unknown_count;
777 wider += wider_unknown_count * wider / wider_known_count;
778 return active_object_count;
781 void apply(MapBlock *block)
783 if(m_aabms.empty() || block->isDummy())
786 ServerMap *map = &m_env->getServerMap();
788 u32 active_object_count_wider;
789 u32 active_object_count = this->countObjects(block, map, active_object_count_wider);
790 m_env->m_added_objects = 0;
793 for(p0.X=0; p0.X<MAP_BLOCKSIZE; p0.X++)
794 for(p0.Y=0; p0.Y<MAP_BLOCKSIZE; p0.Y++)
795 for(p0.Z=0; p0.Z<MAP_BLOCKSIZE; p0.Z++)
797 const MapNode &n = block->getNodeUnsafe(p0);
798 content_t c = n.getContent();
800 if (c >= m_aabms.size() || !m_aabms[c])
803 v3s16 p = p0 + block->getPosRelative();
804 for (ActiveABM &aabm : *m_aabms[c]) {
805 if (myrand() % aabm.chance != 0)
809 if (aabm.check_required_neighbors) {
811 for(p1.X = p0.X-1; p1.X <= p0.X+1; p1.X++)
812 for(p1.Y = p0.Y-1; p1.Y <= p0.Y+1; p1.Y++)
813 for(p1.Z = p0.Z-1; p1.Z <= p0.Z+1; p1.Z++)
818 if (block->isValidPosition(p1)) {
819 // if the neighbor is found on the same map block
820 // get it straight from there
821 const MapNode &n = block->getNodeUnsafe(p1);
824 // otherwise consult the map
825 MapNode n = map->getNodeNoEx(p1 + block->getPosRelative());
828 if (CONTAINS(aabm.required_neighbors, c))
831 // No required neighbor found
836 // Call all the trigger variations
837 aabm.abm->trigger(m_env, p, n);
838 aabm.abm->trigger(m_env, p, n,
839 active_object_count, active_object_count_wider);
841 // Count surrounding objects again if the abms added any
842 if(m_env->m_added_objects > 0) {
843 active_object_count = countObjects(block, map, active_object_count_wider);
844 m_env->m_added_objects = 0;
851 void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime)
853 // Reset usage timer immediately, otherwise a block that becomes active
854 // again at around the same time as it would normally be unloaded will
855 // get unloaded incorrectly. (I think this still leaves a small possibility
856 // of a race condition between this and server::AsyncRunStep, which only
857 // some kind of synchronisation will fix, but it at least reduces the window
858 // of opportunity for it to break from seconds to nanoseconds)
859 block->resetUsageTimer();
861 // Get time difference
863 u32 stamp = block->getTimestamp();
864 if (m_game_time > stamp && stamp != BLOCK_TIMESTAMP_UNDEFINED)
865 dtime_s = m_game_time - stamp;
866 dtime_s += additional_dtime;
868 /*infostream<<"ServerEnvironment::activateBlock(): block timestamp: "
869 <<stamp<<", game time: "<<m_game_time<<std::endl;*/
871 // Remove stored static objects if clearObjects was called since block's timestamp
872 if (stamp == BLOCK_TIMESTAMP_UNDEFINED || stamp < m_last_clear_objects_time) {
873 block->m_static_objects.m_stored.clear();
874 // do not set changed flag to avoid unnecessary mapblock writes
877 // Set current time as timestamp
878 block->setTimestampNoChangedFlag(m_game_time);
880 /*infostream<<"ServerEnvironment::activateBlock(): block is "
881 <<dtime_s<<" seconds old."<<std::endl;*/
883 // Activate stored objects
884 activateObjects(block, dtime_s);
886 /* Handle LoadingBlockModifiers */
887 m_lbm_mgr.applyLBMs(this, block, stamp);
890 std::vector<NodeTimer> elapsed_timers =
891 block->m_node_timers.step((float)dtime_s);
892 if (!elapsed_timers.empty()) {
894 for (const NodeTimer &elapsed_timer : elapsed_timers) {
895 n = block->getNodeNoEx(elapsed_timer.position);
896 v3s16 p = elapsed_timer.position + block->getPosRelative();
897 if (m_script->node_on_timer(p, n, elapsed_timer.elapsed))
898 block->setNodeTimer(NodeTimer(elapsed_timer.timeout, 0,
899 elapsed_timer.position));
904 void ServerEnvironment::addActiveBlockModifier(ActiveBlockModifier *abm)
906 m_abms.emplace_back(abm);
909 void ServerEnvironment::addLoadingBlockModifierDef(LoadingBlockModifierDef *lbm)
911 m_lbm_mgr.addLBMDef(lbm);
914 bool ServerEnvironment::setNode(v3s16 p, const MapNode &n)
916 const NodeDefManager *ndef = m_server->ndef();
917 MapNode n_old = m_map->getNodeNoEx(p);
919 const ContentFeatures &cf_old = ndef->get(n_old);
922 if (cf_old.has_on_destruct)
923 m_script->node_on_destruct(p, n_old);
926 if (!m_map->addNodeWithEvent(p, n))
929 // Update active VoxelManipulator if a mapgen thread
930 m_map->updateVManip(p);
932 // Call post-destructor
933 if (cf_old.has_after_destruct)
934 m_script->node_after_destruct(p, n_old);
936 // Retrieve node content features
937 // if new node is same as old, reuse old definition to prevent a lookup
938 const ContentFeatures &cf_new = n_old == n ? cf_old : ndef->get(n);
941 if (cf_new.has_on_construct)
942 m_script->node_on_construct(p, n);
947 bool ServerEnvironment::removeNode(v3s16 p)
949 const NodeDefManager *ndef = m_server->ndef();
950 MapNode n_old = m_map->getNodeNoEx(p);
953 if (ndef->get(n_old).has_on_destruct)
954 m_script->node_on_destruct(p, n_old);
957 // This is slightly optimized compared to addNodeWithEvent(air)
958 if (!m_map->removeNodeWithEvent(p))
961 // Update active VoxelManipulator if a mapgen thread
962 m_map->updateVManip(p);
964 // Call post-destructor
965 if (ndef->get(n_old).has_after_destruct)
966 m_script->node_after_destruct(p, n_old);
968 // Air doesn't require constructor
972 bool ServerEnvironment::swapNode(v3s16 p, const MapNode &n)
974 if (!m_map->addNodeWithEvent(p, n, false))
977 // Update active VoxelManipulator if a mapgen thread
978 m_map->updateVManip(p);
983 void ServerEnvironment::getObjectsInsideRadius(std::vector<u16> &objects, v3f pos,
986 for (auto &activeObject : m_active_objects) {
987 ServerActiveObject* obj = activeObject.second;
988 u16 id = activeObject.first;
989 v3f objectpos = obj->getBasePosition();
990 if (objectpos.getDistanceFrom(pos) > radius)
992 objects.push_back(id);
996 void ServerEnvironment::clearObjects(ClearObjectsMode mode)
998 infostream << "ServerEnvironment::clearObjects(): "
999 << "Removing all active objects" << std::endl;
1000 std::vector<u16> objects_to_remove;
1001 for (auto &it : m_active_objects) {
1003 ServerActiveObject* obj = it.second;
1004 if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER)
1007 // Delete static object if block is loaded
1008 deleteStaticFromBlock(obj, id, MOD_REASON_CLEAR_ALL_OBJECTS, true);
1010 // If known by some client, don't delete immediately
1011 if (obj->m_known_by_count > 0) {
1012 obj->m_pending_removal = true;
1016 // Tell the object about removal
1017 obj->removingFromEnvironment();
1018 // Deregister in scripting api
1019 m_script->removeObjectReference(obj);
1021 // Delete active object
1022 if (obj->environmentDeletes())
1024 // Id to be removed from m_active_objects
1025 objects_to_remove.push_back(id);
1028 // Remove references from m_active_objects
1029 for (u16 i : objects_to_remove) {
1030 m_active_objects.erase(i);
1033 // Get list of loaded blocks
1034 std::vector<v3s16> loaded_blocks;
1035 infostream << "ServerEnvironment::clearObjects(): "
1036 << "Listing all loaded blocks" << std::endl;
1037 m_map->listAllLoadedBlocks(loaded_blocks);
1038 infostream << "ServerEnvironment::clearObjects(): "
1039 << "Done listing all loaded blocks: "
1040 << loaded_blocks.size()<<std::endl;
1042 // Get list of loadable blocks
1043 std::vector<v3s16> loadable_blocks;
1044 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1045 infostream << "ServerEnvironment::clearObjects(): "
1046 << "Listing all loadable blocks" << std::endl;
1047 m_map->listAllLoadableBlocks(loadable_blocks);
1048 infostream << "ServerEnvironment::clearObjects(): "
1049 << "Done listing all loadable blocks: "
1050 << loadable_blocks.size() << std::endl;
1052 loadable_blocks = loaded_blocks;
1055 actionstream << "ServerEnvironment::clearObjects(): "
1056 << "Now clearing objects in " << loadable_blocks.size()
1057 << " blocks" << std::endl;
1059 // Grab a reference on each loaded block to avoid unloading it
1060 for (v3s16 p : loaded_blocks) {
1061 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1062 assert(block != NULL);
1066 // Remove objects in all loadable blocks
1067 u32 unload_interval = U32_MAX;
1068 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1069 unload_interval = g_settings->getS32("max_clearobjects_extra_loaded_blocks");
1070 unload_interval = MYMAX(unload_interval, 1);
1072 u32 report_interval = loadable_blocks.size() / 10;
1073 u32 num_blocks_checked = 0;
1074 u32 num_blocks_cleared = 0;
1075 u32 num_objs_cleared = 0;
1076 for (auto i = loadable_blocks.begin();
1077 i != loadable_blocks.end(); ++i) {
1079 MapBlock *block = m_map->emergeBlock(p, false);
1081 errorstream << "ServerEnvironment::clearObjects(): "
1082 << "Failed to emerge block " << PP(p) << std::endl;
1085 u32 num_stored = block->m_static_objects.m_stored.size();
1086 u32 num_active = block->m_static_objects.m_active.size();
1087 if (num_stored != 0 || num_active != 0) {
1088 block->m_static_objects.m_stored.clear();
1089 block->m_static_objects.m_active.clear();
1090 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1091 MOD_REASON_CLEAR_ALL_OBJECTS);
1092 num_objs_cleared += num_stored + num_active;
1093 num_blocks_cleared++;
1095 num_blocks_checked++;
1097 if (report_interval != 0 &&
1098 num_blocks_checked % report_interval == 0) {
1099 float percent = 100.0 * (float)num_blocks_checked /
1100 loadable_blocks.size();
1101 actionstream << "ServerEnvironment::clearObjects(): "
1102 << "Cleared " << num_objs_cleared << " objects"
1103 << " in " << num_blocks_cleared << " blocks ("
1104 << percent << "%)" << std::endl;
1106 if (num_blocks_checked % unload_interval == 0) {
1107 m_map->unloadUnreferencedBlocks();
1110 m_map->unloadUnreferencedBlocks();
1112 // Drop references that were added above
1113 for (v3s16 p : loaded_blocks) {
1114 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1119 m_last_clear_objects_time = m_game_time;
1121 actionstream << "ServerEnvironment::clearObjects(): "
1122 << "Finished: Cleared " << num_objs_cleared << " objects"
1123 << " in " << num_blocks_cleared << " blocks" << std::endl;
1126 void ServerEnvironment::step(float dtime)
1128 /* Step time of day */
1129 stepTimeOfDay(dtime);
1132 // NOTE: This is kind of funny on a singleplayer game, but doesn't
1133 // really matter that much.
1134 static thread_local const float server_step =
1135 g_settings->getFloat("dedicated_server_step");
1136 m_recommended_send_interval = server_step;
1142 m_game_time_fraction_counter += dtime;
1143 u32 inc_i = (u32)m_game_time_fraction_counter;
1144 m_game_time += inc_i;
1145 m_game_time_fraction_counter -= (float)inc_i;
1152 ScopeProfiler sp(g_profiler, "SEnv: handle players avg", SPT_AVG);
1153 for (RemotePlayer *player : m_players) {
1154 // Ignore disconnected players
1155 if (player->getPeerId() == PEER_ID_INEXISTENT)
1159 player->move(dtime, this, 100 * BS);
1164 Manage active block list
1166 if (m_active_blocks_management_interval.step(dtime, m_cache_active_block_mgmt_interval)) {
1167 ScopeProfiler sp(g_profiler, "SEnv: manage act. block list avg per interval", SPT_AVG);
1169 Get player block positions
1171 std::vector<PlayerSAO*> players;
1172 for (RemotePlayer *player: m_players) {
1173 // Ignore disconnected players
1174 if (player->getPeerId() == PEER_ID_INEXISTENT)
1177 PlayerSAO *playersao = player->getPlayerSAO();
1180 players.push_back(playersao);
1184 Update list of active blocks, collecting changes
1186 // use active_object_send_range_blocks since that is max distance
1187 // for active objects sent the client anyway
1188 static thread_local const s16 active_object_range =
1189 g_settings->getS16("active_object_send_range_blocks");
1190 static thread_local const s16 active_block_range =
1191 g_settings->getS16("active_block_range");
1192 std::set<v3s16> blocks_removed;
1193 std::set<v3s16> blocks_added;
1194 m_active_blocks.update(players, active_block_range, active_object_range,
1195 blocks_removed, blocks_added);
1198 Handle removed blocks
1201 // Convert active objects that are no more in active blocks to static
1202 deactivateFarObjects(false);
1204 for (const v3s16 &p: blocks_removed) {
1205 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1209 // Set current time as timestamp (and let it set ChangedFlag)
1210 block->setTimestamp(m_game_time);
1217 for (const v3s16 &p: blocks_added) {
1218 MapBlock *block = m_map->getBlockOrEmerge(p);
1220 m_active_blocks.m_list.erase(p);
1221 m_active_blocks.m_abm_list.erase(p);
1225 activateBlock(block);
1230 Mess around in active blocks
1232 if (m_active_blocks_nodemetadata_interval.step(dtime, m_cache_nodetimer_interval)) {
1233 ScopeProfiler sp(g_profiler, "SEnv: mess in act. blocks avg per interval", SPT_AVG);
1235 float dtime = m_cache_nodetimer_interval;
1237 for (const v3s16 &p: m_active_blocks.m_list) {
1238 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1242 // Reset block usage timer
1243 block->resetUsageTimer();
1245 // Set current time as timestamp
1246 block->setTimestampNoChangedFlag(m_game_time);
1247 // If time has changed much from the one on disk,
1248 // set block to be saved when it is unloaded
1249 if(block->getTimestamp() > block->getDiskTimestamp() + 60)
1250 block->raiseModified(MOD_STATE_WRITE_AT_UNLOAD,
1251 MOD_REASON_BLOCK_EXPIRED);
1254 std::vector<NodeTimer> elapsed_timers = block->m_node_timers.step(dtime);
1255 if (!elapsed_timers.empty()) {
1258 for (const NodeTimer &elapsed_timer: elapsed_timers) {
1259 n = block->getNodeNoEx(elapsed_timer.position);
1260 p2 = elapsed_timer.position + block->getPosRelative();
1261 if (m_script->node_on_timer(p2, n, elapsed_timer.elapsed)) {
1262 block->setNodeTimer(NodeTimer(
1263 elapsed_timer.timeout, 0, elapsed_timer.position));
1270 if (m_active_block_modifier_interval.step(dtime, m_cache_abm_interval))
1272 if (m_active_block_interval_overload_skip > 0) {
1273 ScopeProfiler sp(g_profiler, "SEnv: ABM overload skips");
1274 m_active_block_interval_overload_skip--;
1277 ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg per interval", SPT_AVG);
1278 TimeTaker timer("modify in active blocks per interval");
1280 // Initialize handling of ActiveBlockModifiers
1281 ABMHandler abmhandler(m_abms, m_cache_abm_interval, this, true);
1283 for (const v3s16 &p : m_active_blocks.m_abm_list) {
1284 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1288 // Set current time as timestamp
1289 block->setTimestampNoChangedFlag(m_game_time);
1291 /* Handle ActiveBlockModifiers */
1292 abmhandler.apply(block);
1295 u32 time_ms = timer.stop(true);
1296 u32 max_time_ms = 200;
1297 if (time_ms > max_time_ms) {
1298 warningstream<<"active block modifiers took "
1299 <<time_ms<<"ms (longer than "
1300 <<max_time_ms<<"ms)"<<std::endl;
1301 m_active_block_interval_overload_skip = (time_ms / max_time_ms) + 1;
1306 Step script environment (run global on_step())
1308 m_script->environment_Step(dtime);
1314 ScopeProfiler sp(g_profiler, "SEnv: step act. objs avg", SPT_AVG);
1315 //TimeTaker timer("Step active objects");
1317 g_profiler->avg("SEnv: num of objects", m_active_objects.size());
1319 // This helps the objects to send data at the same time
1320 bool send_recommended = false;
1321 m_send_recommended_timer += dtime;
1322 if(m_send_recommended_timer > getSendRecommendedInterval())
1324 m_send_recommended_timer -= getSendRecommendedInterval();
1325 send_recommended = true;
1328 for (auto &ao_it : m_active_objects) {
1329 ServerActiveObject* obj = ao_it.second;
1334 obj->step(dtime, send_recommended);
1335 // Read messages from object
1336 while (!obj->m_messages_out.empty()) {
1337 m_active_object_messages.push(obj->m_messages_out.front());
1338 obj->m_messages_out.pop();
1344 Manage active objects
1346 if (m_object_management_interval.step(dtime, 0.5)) {
1347 ScopeProfiler sp(g_profiler, "SEnv: remove removed objs avg /.5s", SPT_AVG);
1348 removeRemovedObjects();
1352 Manage particle spawner expiration
1354 if (m_particle_management_interval.step(dtime, 1.0)) {
1355 for (std::unordered_map<u32, float>::iterator i = m_particle_spawners.begin();
1356 i != m_particle_spawners.end(); ) {
1357 //non expiring spawners
1358 if (i->second == PARTICLE_SPAWNER_NO_EXPIRY) {
1364 if (i->second <= 0.f)
1365 m_particle_spawners.erase(i++);
1372 u32 ServerEnvironment::addParticleSpawner(float exptime)
1374 // Timers with lifetime 0 do not expire
1375 float time = exptime > 0.f ? exptime : PARTICLE_SPAWNER_NO_EXPIRY;
1378 for (;;) { // look for unused particlespawner id
1380 std::unordered_map<u32, float>::iterator f = m_particle_spawners.find(id);
1381 if (f == m_particle_spawners.end()) {
1382 m_particle_spawners[id] = time;
1389 u32 ServerEnvironment::addParticleSpawner(float exptime, u16 attached_id)
1391 u32 id = addParticleSpawner(exptime);
1392 m_particle_spawner_attachments[id] = attached_id;
1393 if (ServerActiveObject *obj = getActiveObject(attached_id)) {
1394 obj->attachParticleSpawner(id);
1399 void ServerEnvironment::deleteParticleSpawner(u32 id, bool remove_from_object)
1401 m_particle_spawners.erase(id);
1402 const auto &it = m_particle_spawner_attachments.find(id);
1403 if (it != m_particle_spawner_attachments.end()) {
1404 u16 obj_id = it->second;
1405 ServerActiveObject *sao = getActiveObject(obj_id);
1406 if (sao != NULL && remove_from_object) {
1407 sao->detachParticleSpawner(id);
1409 m_particle_spawner_attachments.erase(id);
1413 ServerActiveObject* ServerEnvironment::getActiveObject(u16 id)
1415 ServerActiveObjectMap::const_iterator n = m_active_objects.find(id);
1416 return (n != m_active_objects.end() ? n->second : NULL);
1419 bool isFreeServerActiveObjectId(u16 id, ServerActiveObjectMap &objects)
1424 return objects.find(id) == objects.end();
1427 u16 getFreeServerActiveObjectId(ServerActiveObjectMap &objects)
1429 //try to reuse id's as late as possible
1430 static u16 last_used_id = 0;
1431 u16 startid = last_used_id;
1435 if(isFreeServerActiveObjectId(last_used_id, objects))
1436 return last_used_id;
1438 if(last_used_id == startid)
1443 u16 ServerEnvironment::addActiveObject(ServerActiveObject *object)
1445 assert(object); // Pre-condition
1447 u16 id = addActiveObjectRaw(object, true, 0);
1452 Finds out what new objects have been added to
1453 inside a radius around a position
1455 void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius,
1457 std::set<u16> ¤t_objects,
1458 std::queue<u16> &added_objects)
1460 f32 radius_f = radius * BS;
1461 f32 player_radius_f = player_radius * BS;
1463 if (player_radius_f < 0)
1464 player_radius_f = 0;
1466 Go through the object list,
1467 - discard removed/deactivated objects,
1468 - discard objects that are too far away,
1469 - discard objects that are found in current_objects.
1470 - add remaining objects to added_objects
1472 for (auto &ao_it : m_active_objects) {
1473 u16 id = ao_it.first;
1476 ServerActiveObject *object = ao_it.second;
1480 if (object->isGone())
1483 f32 distance_f = object->getBasePosition().
1484 getDistanceFrom(playersao->getBasePosition());
1485 if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1486 // Discard if too far
1487 if (distance_f > player_radius_f && player_radius_f != 0)
1489 } else if (distance_f > radius_f)
1492 // Discard if already on current_objects
1493 std::set<u16>::iterator n;
1494 n = current_objects.find(id);
1495 if(n != current_objects.end())
1497 // Add to added_objects
1498 added_objects.push(id);
1503 Finds out what objects have been removed from
1504 inside a radius around a position
1506 void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius,
1508 std::set<u16> ¤t_objects,
1509 std::queue<u16> &removed_objects)
1511 f32 radius_f = radius * BS;
1512 f32 player_radius_f = player_radius * BS;
1514 if (player_radius_f < 0)
1515 player_radius_f = 0;
1517 Go through current_objects; object is removed if:
1518 - object is not found in m_active_objects (this is actually an
1519 error condition; objects should be removed only after all clients
1520 have been informed about removal), or
1521 - object is to be removed or deactivated, or
1522 - object is too far away
1524 for (u16 id : current_objects) {
1525 ServerActiveObject *object = getActiveObject(id);
1527 if (object == NULL) {
1528 infostream << "ServerEnvironment::getRemovedActiveObjects():"
1529 << " object in current_objects is NULL" << std::endl;
1530 removed_objects.push(id);
1534 if (object->isGone()) {
1535 removed_objects.push(id);
1539 f32 distance_f = object->getBasePosition().getDistanceFrom(playersao->getBasePosition());
1540 if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1541 if (distance_f <= player_radius_f || player_radius_f == 0)
1543 } else if (distance_f <= radius_f)
1546 // Object is no longer visible
1547 removed_objects.push(id);
1551 void ServerEnvironment::setStaticForActiveObjectsInBlock(
1552 v3s16 blockpos, bool static_exists, v3s16 static_block)
1554 MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos);
1558 for (auto &so_it : block->m_static_objects.m_active) {
1559 // Get the ServerActiveObject counterpart to this StaticObject
1560 ServerActiveObjectMap::const_iterator ao_it = m_active_objects.find(so_it.first);
1561 if (ao_it == m_active_objects.end()) {
1562 // If this ever happens, there must be some kind of nasty bug.
1563 errorstream << "ServerEnvironment::setStaticForObjectsInBlock(): "
1564 "Object from MapBlock::m_static_objects::m_active not found "
1565 "in m_active_objects";
1569 ServerActiveObject *sao = ao_it->second;
1570 sao->m_static_exists = static_exists;
1571 sao->m_static_block = static_block;
1575 ActiveObjectMessage ServerEnvironment::getActiveObjectMessage()
1577 if(m_active_object_messages.empty())
1578 return ActiveObjectMessage(0);
1580 ActiveObjectMessage message = m_active_object_messages.front();
1581 m_active_object_messages.pop();
1585 void ServerEnvironment::getSelectedActiveObjects(
1586 const core::line3d<f32> &shootline_on_map,
1587 std::vector<PointedThing> &objects)
1589 std::vector<u16> objectIds;
1590 getObjectsInsideRadius(objectIds, shootline_on_map.start,
1591 shootline_on_map.getLength() + 10.0f);
1592 const v3f line_vector = shootline_on_map.getVector();
1594 for (u16 objectId : objectIds) {
1595 ServerActiveObject* obj = getActiveObject(objectId);
1597 aabb3f selection_box;
1598 if (!obj->getSelectionBox(&selection_box))
1601 v3f pos = obj->getBasePosition();
1603 aabb3f offsetted_box(selection_box.MinEdge + pos,
1604 selection_box.MaxEdge + pos);
1606 v3f current_intersection;
1607 v3s16 current_normal;
1608 if (boxLineCollision(offsetted_box, shootline_on_map.start, line_vector,
1609 ¤t_intersection, ¤t_normal)) {
1610 objects.emplace_back(
1611 (s16) objectId, current_intersection, current_normal,
1612 (current_intersection - shootline_on_map.start).getLengthSQ());
1618 ************ Private methods *************
1621 u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object,
1622 bool set_changed, u32 dtime_s)
1624 assert(object); // Pre-condition
1625 if(object->getId() == 0){
1626 u16 new_id = getFreeServerActiveObjectId(m_active_objects);
1629 errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
1630 <<"no free ids available"<<std::endl;
1631 if(object->environmentDeletes())
1635 object->setId(new_id);
1638 verbosestream<<"ServerEnvironment::addActiveObjectRaw(): "
1639 <<"supplied with id "<<object->getId()<<std::endl;
1642 if(!isFreeServerActiveObjectId(object->getId(), m_active_objects)) {
1643 errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
1644 <<"id is not free ("<<object->getId()<<")"<<std::endl;
1645 if(object->environmentDeletes())
1650 if (objectpos_over_limit(object->getBasePosition())) {
1651 v3f p = object->getBasePosition();
1652 warningstream << "ServerEnvironment::addActiveObjectRaw(): "
1653 << "object position (" << p.X << "," << p.Y << "," << p.Z
1654 << ") outside maximum range" << std::endl;
1655 if (object->environmentDeletes())
1660 /*infostream<<"ServerEnvironment::addActiveObjectRaw(): "
1661 <<"added (id="<<object->getId()<<")"<<std::endl;*/
1663 m_active_objects[object->getId()] = object;
1665 verbosestream<<"ServerEnvironment::addActiveObjectRaw(): "
1666 <<"Added id="<<object->getId()<<"; there are now "
1667 <<m_active_objects.size()<<" active objects."
1670 // Register reference in scripting api (must be done before post-init)
1671 m_script->addObjectReference(object);
1672 // Post-initialize object
1673 object->addedToEnvironment(dtime_s);
1675 // Add static data to block
1676 if(object->isStaticAllowed())
1678 // Add static object to active static list of the block
1679 v3f objectpos = object->getBasePosition();
1680 std::string staticdata;
1681 object->getStaticData(&staticdata);
1682 StaticObject s_obj(object->getType(), objectpos, staticdata);
1683 // Add to the block where the object is located in
1684 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
1685 MapBlock *block = m_map->emergeBlock(blockpos);
1687 block->m_static_objects.m_active[object->getId()] = s_obj;
1688 object->m_static_exists = true;
1689 object->m_static_block = blockpos;
1692 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1693 MOD_REASON_ADD_ACTIVE_OBJECT_RAW);
1695 v3s16 p = floatToInt(objectpos, BS);
1696 errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
1697 <<"could not emerge block for storing id="<<object->getId()
1698 <<" statically (pos="<<PP(p)<<")"<<std::endl;
1702 return object->getId();
1706 Remove objects that satisfy (isGone() && m_known_by_count==0)
1708 void ServerEnvironment::removeRemovedObjects()
1710 std::vector<u16> objects_to_remove;
1711 for (auto &ao_it : m_active_objects) {
1712 u16 id = ao_it.first;
1713 ServerActiveObject* obj = ao_it.second;
1715 // This shouldn't happen but check it
1717 errorstream << "ServerEnvironment::removeRemovedObjects(): "
1718 << "NULL object found. id=" << id << std::endl;
1719 objects_to_remove.push_back(id);
1724 We will handle objects marked for removal or deactivation
1730 Delete static data from block if removed
1732 if (obj->m_pending_removal)
1733 deleteStaticFromBlock(obj, id, MOD_REASON_REMOVE_OBJECTS_REMOVE, false);
1735 // If still known by clients, don't actually remove. On some future
1736 // invocation this will be 0, which is when removal will continue.
1737 if(obj->m_known_by_count > 0)
1741 Move static data from active to stored if deactivated
1743 if (!obj->m_pending_removal && obj->m_static_exists) {
1744 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
1746 std::map<u16, StaticObject>::iterator i =
1747 block->m_static_objects.m_active.find(id);
1748 if (i != block->m_static_objects.m_active.end()) {
1749 block->m_static_objects.m_stored.push_back(i->second);
1750 block->m_static_objects.m_active.erase(id);
1751 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1752 MOD_REASON_REMOVE_OBJECTS_DEACTIVATE);
1754 warningstream << "ServerEnvironment::removeRemovedObjects(): "
1755 << "id=" << id << " m_static_exists=true but "
1756 << "static data doesn't actually exist in "
1757 << PP(obj->m_static_block) << std::endl;
1760 infostream << "Failed to emerge block from which an object to "
1761 << "be deactivated was loaded from. id=" << id << std::endl;
1765 // Tell the object about removal
1766 obj->removingFromEnvironment();
1767 // Deregister in scripting api
1768 m_script->removeObjectReference(obj);
1771 if(obj->environmentDeletes())
1774 objects_to_remove.push_back(id);
1776 // Remove references from m_active_objects
1777 for (u16 i : objects_to_remove) {
1778 m_active_objects.erase(i);
1782 static void print_hexdump(std::ostream &o, const std::string &data)
1784 const int linelength = 16;
1785 for(int l=0; ; l++){
1786 int i0 = linelength * l;
1787 bool at_end = false;
1788 int thislinelength = linelength;
1789 if(i0 + thislinelength > (int)data.size()){
1790 thislinelength = data.size() - i0;
1793 for(int di=0; di<linelength; di++){
1796 if(di<thislinelength)
1797 snprintf(buf, 4, "%.2x ", data[i]);
1799 snprintf(buf, 4, " ");
1803 for(int di=0; di<thislinelength; di++){
1817 Convert stored objects from blocks near the players to active.
1819 void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s)
1824 // Ignore if no stored objects (to not set changed flag)
1825 if(block->m_static_objects.m_stored.empty())
1828 verbosestream<<"ServerEnvironment::activateObjects(): "
1829 <<"activating objects of block "<<PP(block->getPos())
1830 <<" ("<<block->m_static_objects.m_stored.size()
1831 <<" objects)"<<std::endl;
1832 bool large_amount = (block->m_static_objects.m_stored.size() > g_settings->getU16("max_objects_per_block"));
1834 errorstream<<"suspiciously large amount of objects detected: "
1835 <<block->m_static_objects.m_stored.size()<<" in "
1836 <<PP(block->getPos())
1837 <<"; removing all of them."<<std::endl;
1838 // Clear stored list
1839 block->m_static_objects.m_stored.clear();
1840 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1841 MOD_REASON_TOO_MANY_OBJECTS);
1845 // Activate stored objects
1846 std::vector<StaticObject> new_stored;
1847 for (const StaticObject &s_obj : block->m_static_objects.m_stored) {
1848 // Create an active object from the data
1849 ServerActiveObject *obj = ServerActiveObject::create
1850 ((ActiveObjectType) s_obj.type, this, 0, s_obj.pos, s_obj.data);
1851 // If couldn't create object, store static data back.
1853 errorstream<<"ServerEnvironment::activateObjects(): "
1854 <<"failed to create active object from static object "
1855 <<"in block "<<PP(s_obj.pos/BS)
1856 <<" type="<<(int)s_obj.type<<" data:"<<std::endl;
1857 print_hexdump(verbosestream, s_obj.data);
1859 new_stored.push_back(s_obj);
1862 verbosestream<<"ServerEnvironment::activateObjects(): "
1863 <<"activated static object pos="<<PP(s_obj.pos/BS)
1864 <<" type="<<(int)s_obj.type<<std::endl;
1865 // This will also add the object to the active static list
1866 addActiveObjectRaw(obj, false, dtime_s);
1869 // Clear stored list
1870 block->m_static_objects.m_stored.clear();
1871 // Add leftover failed stuff to stored list
1872 for (const StaticObject &s_obj : new_stored) {
1873 block->m_static_objects.m_stored.push_back(s_obj);
1877 Note: Block hasn't really been modified here.
1878 The objects have just been activated and moved from the stored
1879 static list to the active static list.
1880 As such, the block is essentially the same.
1881 Thus, do not call block->raiseModified(MOD_STATE_WRITE_NEEDED).
1882 Otherwise there would be a huge amount of unnecessary I/O.
1887 Convert objects that are not standing inside active blocks to static.
1889 If m_known_by_count != 0, active object is not deleted, but static
1890 data is still updated.
1892 If force_delete is set, active object is deleted nevertheless. It
1893 shall only be set so in the destructor of the environment.
1895 If block wasn't generated (not in memory or on disk),
1897 void ServerEnvironment::deactivateFarObjects(bool _force_delete)
1899 std::vector<u16> objects_to_remove;
1900 for (auto &ao_it : m_active_objects) {
1901 // force_delete might be overriden per object
1902 bool force_delete = _force_delete;
1904 ServerActiveObject* obj = ao_it.second;
1907 // Do not deactivate if static data creation not allowed
1908 if(!force_delete && !obj->isStaticAllowed())
1911 // removeRemovedObjects() is responsible for these
1912 if(!force_delete && obj->isGone())
1915 u16 id = ao_it.first;
1916 v3f objectpos = obj->getBasePosition();
1918 // The block in which the object resides in
1919 v3s16 blockpos_o = getNodeBlockPos(floatToInt(objectpos, BS));
1921 // If object's static data is stored in a deactivated block and object
1922 // is actually located in an active block, re-save to the block in
1923 // which the object is actually located in.
1925 obj->m_static_exists &&
1926 !m_active_blocks.contains(obj->m_static_block) &&
1927 m_active_blocks.contains(blockpos_o))
1929 // Delete from block where object was located
1930 deleteStaticFromBlock(obj, id, MOD_REASON_STATIC_DATA_REMOVED, false);
1932 std::string staticdata_new;
1933 obj->getStaticData(&staticdata_new);
1934 StaticObject s_obj(obj->getType(), objectpos, staticdata_new);
1935 // Save to block where object is located
1936 saveStaticToBlock(blockpos_o, id, obj, s_obj, MOD_REASON_STATIC_DATA_ADDED);
1941 // If block is still active, don't remove
1942 if(!force_delete && m_active_blocks.contains(blockpos_o))
1945 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1946 << "deactivating object id=" << id << " on inactive block "
1947 << PP(blockpos_o) << std::endl;
1949 // If known by some client, don't immediately delete.
1950 bool pending_delete = (obj->m_known_by_count > 0 && !force_delete);
1953 Update the static data
1955 if(obj->isStaticAllowed())
1957 // Create new static object
1958 std::string staticdata_new;
1959 obj->getStaticData(&staticdata_new);
1960 StaticObject s_obj(obj->getType(), objectpos, staticdata_new);
1962 bool stays_in_same_block = false;
1963 bool data_changed = true;
1965 // Check if static data has changed considerably
1966 if (obj->m_static_exists) {
1967 if (obj->m_static_block == blockpos_o)
1968 stays_in_same_block = true;
1970 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
1973 std::map<u16, StaticObject>::iterator n =
1974 block->m_static_objects.m_active.find(id);
1975 if (n != block->m_static_objects.m_active.end()) {
1976 StaticObject static_old = n->second;
1978 float save_movem = obj->getMinimumSavedMovement();
1980 if (static_old.data == staticdata_new &&
1981 (static_old.pos - objectpos).getLength() < save_movem)
1982 data_changed = false;
1984 warningstream << "ServerEnvironment::deactivateFarObjects(): "
1985 << "id=" << id << " m_static_exists=true but "
1986 << "static data doesn't actually exist in "
1987 << PP(obj->m_static_block) << std::endl;
1993 While changes are always saved, blocks are only marked as modified
1994 if the object has moved or different staticdata. (see above)
1996 bool shall_be_written = (!stays_in_same_block || data_changed);
1997 u32 reason = shall_be_written ? MOD_REASON_STATIC_DATA_CHANGED : MOD_REASON_UNKNOWN;
1999 // Delete old static object
2000 deleteStaticFromBlock(obj, id, reason, false);
2002 // Add to the block where the object is located in
2003 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
2004 u16 store_id = pending_delete ? id : 0;
2005 if (!saveStaticToBlock(blockpos, store_id, obj, s_obj, reason))
2006 force_delete = true;
2010 If known by some client, set pending deactivation.
2011 Otherwise delete it immediately.
2013 if(pending_delete && !force_delete)
2015 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
2016 << "object id=" << id << " is known by clients"
2017 << "; not deleting yet" << std::endl;
2019 obj->m_pending_deactivation = true;
2022 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
2023 << "object id=" << id << " is not known by clients"
2024 << "; deleting" << std::endl;
2026 // Tell the object about removal
2027 obj->removingFromEnvironment();
2028 // Deregister in scripting api
2029 m_script->removeObjectReference(obj);
2031 // Delete active object
2032 if(obj->environmentDeletes())
2034 // Id to be removed from m_active_objects
2035 objects_to_remove.push_back(id);
2038 // Remove references from m_active_objects
2039 for (u16 i : objects_to_remove) {
2040 m_active_objects.erase(i);
2044 void ServerEnvironment::deleteStaticFromBlock(
2045 ServerActiveObject *obj, u16 id, u32 mod_reason, bool no_emerge)
2047 if (!obj->m_static_exists)
2052 block = m_map->getBlockNoCreateNoEx(obj->m_static_block);
2054 block = m_map->emergeBlock(obj->m_static_block, false);
2057 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2058 << " when deleting static data of object from it. id=" << id << std::endl;
2062 block->m_static_objects.remove(id);
2063 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2064 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2066 obj->m_static_exists = false;
2069 bool ServerEnvironment::saveStaticToBlock(
2070 v3s16 blockpos, u16 store_id,
2071 ServerActiveObject *obj, const StaticObject &s_obj,
2074 MapBlock *block = nullptr;
2076 block = m_map->emergeBlock(blockpos);
2077 } catch (InvalidPositionException &e) {
2078 // Handled via NULL pointer
2079 // NOTE: emergeBlock's failure is usually determined by it
2080 // actually returning NULL
2084 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2085 << " when saving static data of object to it. id=" << store_id << std::endl;
2088 if (block->m_static_objects.m_stored.size() >= g_settings->getU16("max_objects_per_block")) {
2089 warningstream << "ServerEnv: Trying to store id = " << store_id
2090 << " statically but block " << PP(blockpos)
2091 << " already contains "
2092 << block->m_static_objects.m_stored.size()
2093 << " objects." << std::endl;
2097 block->m_static_objects.insert(store_id, s_obj);
2098 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2099 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2101 obj->m_static_exists = true;
2102 obj->m_static_block = blockpos;
2107 PlayerDatabase *ServerEnvironment::openPlayerDatabase(const std::string &name,
2108 const std::string &savedir, const Settings &conf)
2111 if (name == "sqlite3")
2112 return new PlayerDatabaseSQLite3(savedir);
2114 if (name == "dummy")
2115 return new Database_Dummy();
2117 if (name == "postgresql") {
2118 std::string connect_string;
2119 conf.getNoEx("pgsql_player_connection", connect_string);
2120 return new PlayerDatabasePostgreSQL(connect_string);
2123 if (name == "files")
2124 return new PlayerDatabaseFiles(savedir + DIR_DELIM + "players");
2126 throw BaseException(std::string("Database backend ") + name + " not supported.");
2129 bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params,
2130 const Settings &cmd_args)
2132 std::string migrate_to = cmd_args.get("migrate-players");
2134 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2135 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2136 errorstream << "Cannot read world.mt!" << std::endl;
2140 if (!world_mt.exists("player_backend")) {
2141 errorstream << "Please specify your current backend in world.mt:"
2143 << " player_backend = {files|sqlite3|postgresql}"
2148 std::string backend = world_mt.get("player_backend");
2149 if (backend == migrate_to) {
2150 errorstream << "Cannot migrate: new backend is same"
2151 << " as the old one" << std::endl;
2155 const std::string players_backup_path = game_params.world_path + DIR_DELIM
2158 if (backend == "files") {
2159 // Create backup directory
2160 fs::CreateDir(players_backup_path);
2164 PlayerDatabase *srcdb = ServerEnvironment::openPlayerDatabase(backend,
2165 game_params.world_path, world_mt);
2166 PlayerDatabase *dstdb = ServerEnvironment::openPlayerDatabase(migrate_to,
2167 game_params.world_path, world_mt);
2169 std::vector<std::string> player_list;
2170 srcdb->listPlayers(player_list);
2171 for (std::vector<std::string>::const_iterator it = player_list.begin();
2172 it != player_list.end(); ++it) {
2173 actionstream << "Migrating player " << it->c_str() << std::endl;
2174 RemotePlayer player(it->c_str(), NULL);
2175 PlayerSAO playerSAO(NULL, &player, 15000, false);
2177 srcdb->loadPlayer(&player, &playerSAO);
2179 playerSAO.finalize(&player, std::set<std::string>());
2180 player.setPlayerSAO(&playerSAO);
2182 dstdb->savePlayer(&player);
2184 // For files source, move player files to backup dir
2185 if (backend == "files") {
2187 game_params.world_path + DIR_DELIM + "players" + DIR_DELIM + (*it),
2188 players_backup_path + DIR_DELIM + (*it));
2192 actionstream << "Successfully migrated " << player_list.size() << " players"
2194 world_mt.set("player_backend", migrate_to);
2195 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2196 errorstream << "Failed to update world.mt!" << std::endl;
2198 actionstream << "world.mt updated" << std::endl;
2200 // When migration is finished from file backend, remove players directory if empty
2201 if (backend == "files") {
2202 fs::DeleteSingleFileOrEmptyDirectory(game_params.world_path + DIR_DELIM
2209 } catch (BaseException &e) {
2210 errorstream << "An error occured during migration: " << e.what() << std::endl;