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 (; it != m_lbm_lookup.end(); ++it) {
258 // Cache previous version to speedup lookup which has a very high performance
259 // penalty on each call
260 content_t previous_c{};
261 std::vector<LoadingBlockModifierDef *> *lbm_list = nullptr;
263 for (pos.X = 0; pos.X < MAP_BLOCKSIZE; pos.X++)
264 for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++)
265 for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++) {
266 n = block->getNodeNoEx(pos);
269 // If content_t are not matching perform an LBM lookup
270 if (previous_c != c) {
271 lbm_list = (std::vector<LoadingBlockModifierDef *> *)
272 it->second.lookup(c);
278 for (auto lbmdef : *lbm_list) {
279 lbmdef->trigger(env, pos + pos_of_block, n);
289 void fillRadiusBlock(v3s16 p0, s16 r, std::set<v3s16> &list)
292 for(p.X=p0.X-r; p.X<=p0.X+r; p.X++)
293 for(p.Y=p0.Y-r; p.Y<=p0.Y+r; p.Y++)
294 for(p.Z=p0.Z-r; p.Z<=p0.Z+r; p.Z++)
297 if (p.getDistanceFrom(p0) <= r) {
304 void fillViewConeBlock(v3s16 p0,
306 const v3f camera_pos,
307 const v3f camera_dir,
308 const float camera_fov,
309 std::set<v3s16> &list)
312 const s16 r_nodes = r * BS * MAP_BLOCKSIZE;
313 for (p.X = p0.X - r; p.X <= p0.X+r; p.X++)
314 for (p.Y = p0.Y - r; p.Y <= p0.Y+r; p.Y++)
315 for (p.Z = p0.Z - r; p.Z <= p0.Z+r; p.Z++) {
316 if (isBlockInSight(p, camera_pos, camera_dir, camera_fov, r_nodes)) {
322 void ActiveBlockList::update(std::vector<PlayerSAO*> &active_players,
323 s16 active_block_range,
324 s16 active_object_range,
325 std::set<v3s16> &blocks_removed,
326 std::set<v3s16> &blocks_added)
331 std::set<v3s16> newlist = m_forceloaded_list;
332 m_abm_list = m_forceloaded_list;
333 for (const PlayerSAO *playersao : active_players) {
334 v3s16 pos = getNodeBlockPos(floatToInt(playersao->getBasePosition(), BS));
335 fillRadiusBlock(pos, active_block_range, m_abm_list);
336 fillRadiusBlock(pos, active_block_range, newlist);
338 s16 player_ao_range = std::min(active_object_range, playersao->getWantedRange());
339 // only do this if this would add blocks
340 if (player_ao_range > active_block_range) {
341 v3f camera_dir = v3f(0,0,1);
342 camera_dir.rotateYZBy(playersao->getPitch());
343 camera_dir.rotateXZBy(playersao->getYaw());
344 fillViewConeBlock(pos,
346 playersao->getEyePosition(),
354 Find out which blocks on the old list are not on the new list
356 // Go through old list
357 for (v3s16 p : m_list) {
358 // If not on new list, it's been removed
359 if (newlist.find(p) == newlist.end())
360 blocks_removed.insert(p);
364 Find out which blocks on the new list are not on the old list
366 // Go through new list
367 for (v3s16 p : newlist) {
368 // If not on old list, it's been added
369 if(m_list.find(p) == m_list.end())
370 blocks_added.insert(p);
377 for (v3s16 p : newlist) {
386 ServerEnvironment::ServerEnvironment(ServerMap *map,
387 ServerScripting *scriptIface, Server *server,
388 const std::string &path_world):
391 m_script(scriptIface),
393 m_path_world(path_world)
395 // Determine which database backend to use
396 std::string conf_path = path_world + DIR_DELIM + "world.mt";
398 bool succeeded = conf.readConfigFile(conf_path.c_str());
399 if (!succeeded || !conf.exists("player_backend")) {
400 // fall back to files
401 conf.set("player_backend", "files");
402 warningstream << "/!\\ You are using old player file backend. "
403 << "This backend is deprecated and will be removed in next release /!\\"
404 << std::endl << "Switching to SQLite3 or PostgreSQL is advised, "
405 << "please read http://wiki.minetest.net/Database_backends." << std::endl;
407 if (!conf.updateConfigFile(conf_path.c_str())) {
408 errorstream << "ServerEnvironment::ServerEnvironment(): "
409 << "Failed to update world.mt!" << std::endl;
414 conf.getNoEx("player_backend", name);
415 m_player_database = openPlayerDatabase(name, path_world, conf);
418 ServerEnvironment::~ServerEnvironment()
420 // Clear active block list.
421 // This makes the next one delete all active objects.
422 m_active_blocks.clear();
424 // Convert all objects to static and delete the active objects
425 deactivateFarObjects(true);
430 // Delete ActiveBlockModifiers
431 for (ABMWithState &m_abm : m_abms) {
435 // Deallocate players
436 for (RemotePlayer *m_player : m_players) {
440 delete m_player_database;
443 Map & ServerEnvironment::getMap()
448 ServerMap & ServerEnvironment::getServerMap()
453 RemotePlayer *ServerEnvironment::getPlayer(const session_t peer_id)
455 for (RemotePlayer *player : m_players) {
456 if (player->getPeerId() == peer_id)
462 RemotePlayer *ServerEnvironment::getPlayer(const char* name)
464 for (RemotePlayer *player : m_players) {
465 if (strcmp(player->getName(), name) == 0)
471 void ServerEnvironment::addPlayer(RemotePlayer *player)
474 Check that peer_ids are unique.
475 Also check that names are unique.
476 Exception: there can be multiple players with peer_id=0
478 // If peer id is non-zero, it has to be unique.
479 if (player->getPeerId() != PEER_ID_INEXISTENT)
480 FATAL_ERROR_IF(getPlayer(player->getPeerId()) != NULL, "Peer id not unique");
481 // Name has to be unique.
482 FATAL_ERROR_IF(getPlayer(player->getName()) != NULL, "Player name not unique");
484 m_players.push_back(player);
487 void ServerEnvironment::removePlayer(RemotePlayer *player)
489 for (std::vector<RemotePlayer *>::iterator it = m_players.begin();
490 it != m_players.end(); ++it) {
491 if ((*it) == player) {
499 bool ServerEnvironment::removePlayerFromDatabase(const std::string &name)
501 return m_player_database->removePlayer(name);
504 bool ServerEnvironment::line_of_sight(v3f pos1, v3f pos2, v3s16 *p)
506 // Iterate trough nodes on the line
507 voxalgo::VoxelLineIterator iterator(pos1 / BS, (pos2 - pos1) / BS);
509 MapNode n = getMap().getNodeNoEx(iterator.m_current_node_pos);
512 if (n.param0 != CONTENT_AIR) {
514 *p = iterator.m_current_node_pos;
518 } while (iterator.m_current_index <= iterator.m_last_index);
522 void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason,
523 const std::string &str_reason, bool reconnect)
525 for (RemotePlayer *player : m_players) {
526 m_server->DenyAccessVerCompliant(player->getPeerId(),
527 player->protocol_version, reason, str_reason, reconnect);
531 void ServerEnvironment::saveLoadedPlayers()
533 std::string players_path = m_path_world + DIR_DELIM + "players";
534 fs::CreateDir(players_path);
536 for (RemotePlayer *player : m_players) {
537 if (player->checkModified() || (player->getPlayerSAO() &&
538 player->getPlayerSAO()->getMeta().isModified())) {
540 m_player_database->savePlayer(player);
541 } catch (DatabaseException &e) {
542 errorstream << "Failed to save player " << player->getName() << " exception: "
543 << e.what() << std::endl;
550 void ServerEnvironment::savePlayer(RemotePlayer *player)
553 m_player_database->savePlayer(player);
554 } catch (DatabaseException &e) {
555 errorstream << "Failed to save player " << player->getName() << " exception: "
556 << e.what() << std::endl;
561 PlayerSAO *ServerEnvironment::loadPlayer(RemotePlayer *player, bool *new_player,
562 session_t peer_id, bool is_singleplayer)
564 PlayerSAO *playersao = new PlayerSAO(this, player, peer_id, is_singleplayer);
565 // Create player if it doesn't exist
566 if (!m_player_database->loadPlayer(player, playersao)) {
568 // Set player position
569 infostream << "Server: Finding spawn place for player \""
570 << player->getName() << "\"" << std::endl;
571 playersao->setBasePosition(m_server->findSpawnPos());
573 // Make sure the player is saved
574 player->setModified(true);
576 // If the player exists, ensure that they respawn inside legal bounds
577 // This fixes an assert crash when the player can't be added
578 // to the environment
579 if (objectpos_over_limit(playersao->getBasePosition())) {
580 actionstream << "Respawn position for player \""
581 << player->getName() << "\" outside limits, resetting" << std::endl;
582 playersao->setBasePosition(m_server->findSpawnPos());
586 // Add player to environment
589 /* Clean up old HUD elements from previous sessions */
592 /* Add object to environment */
593 addActiveObject(playersao);
598 void ServerEnvironment::saveMeta()
600 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
602 // Open file and serialize
603 std::ostringstream ss(std::ios_base::binary);
606 args.setU64("game_time", m_game_time);
607 args.setU64("time_of_day", getTimeOfDay());
608 args.setU64("last_clear_objects_time", m_last_clear_objects_time);
609 args.setU64("lbm_introduction_times_version", 1);
610 args.set("lbm_introduction_times",
611 m_lbm_mgr.createIntroductionTimesString());
612 args.setU64("day_count", m_day_count);
616 if(!fs::safeWriteToFile(path, ss.str()))
618 infostream<<"ServerEnvironment::saveMeta(): Failed to write "
620 throw SerializationError("Couldn't save env meta");
624 void ServerEnvironment::loadMeta()
626 // If file doesn't exist, load default environment metadata
627 if (!fs::PathExists(m_path_world + DIR_DELIM "env_meta.txt")) {
628 infostream << "ServerEnvironment: Loading default environment metadata"
634 infostream << "ServerEnvironment: Loading environment metadata" << std::endl;
636 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
638 // Open file and deserialize
639 std::ifstream is(path.c_str(), std::ios_base::binary);
641 infostream << "ServerEnvironment::loadMeta(): Failed to open "
642 << path << std::endl;
643 throw SerializationError("Couldn't load env meta");
648 if (!args.parseConfigLines(is, "EnvArgsEnd")) {
649 throw SerializationError("ServerEnvironment::loadMeta(): "
650 "EnvArgsEnd not found!");
654 m_game_time = args.getU64("game_time");
655 } catch (SettingNotFoundException &e) {
656 // Getting this is crucial, otherwise timestamps are useless
657 throw SerializationError("Couldn't load env meta game_time");
660 setTimeOfDay(args.exists("time_of_day") ?
661 // set day to early morning by default
662 args.getU64("time_of_day") : 5250);
664 m_last_clear_objects_time = args.exists("last_clear_objects_time") ?
665 // If missing, do as if clearObjects was never called
666 args.getU64("last_clear_objects_time") : 0;
668 std::string lbm_introduction_times;
670 u64 ver = args.getU64("lbm_introduction_times_version");
672 lbm_introduction_times = args.get("lbm_introduction_times");
674 infostream << "ServerEnvironment::loadMeta(): Non-supported"
675 << " introduction time version " << ver << std::endl;
677 } catch (SettingNotFoundException &e) {
678 // No problem, this is expected. Just continue with an empty string
680 m_lbm_mgr.loadIntroductionTimes(lbm_introduction_times, m_server, m_game_time);
682 m_day_count = args.exists("day_count") ?
683 args.getU64("day_count") : 0;
687 * called if env_meta.txt doesn't exist (e.g. new world)
689 void ServerEnvironment::loadDefaultMeta()
691 m_lbm_mgr.loadIntroductionTimes("", m_server, m_game_time);
696 ActiveBlockModifier *abm;
698 std::vector<content_t> required_neighbors;
699 bool check_required_neighbors; // false if required_neighbors is known to be empty
705 ServerEnvironment *m_env;
706 std::vector<std::vector<ActiveABM> *> m_aabms;
708 ABMHandler(std::vector<ABMWithState> &abms,
709 float dtime_s, ServerEnvironment *env,
715 const NodeDefManager *ndef = env->getGameDef()->ndef();
716 for (ABMWithState &abmws : abms) {
717 ActiveBlockModifier *abm = abmws.abm;
718 float trigger_interval = abm->getTriggerInterval();
719 if(trigger_interval < 0.001)
720 trigger_interval = 0.001;
721 float actual_interval = dtime_s;
723 abmws.timer += dtime_s;
724 if(abmws.timer < trigger_interval)
726 abmws.timer -= trigger_interval;
727 actual_interval = trigger_interval;
729 float chance = abm->getTriggerChance();
734 if (abm->getSimpleCatchUp()) {
735 float intervals = actual_interval / trigger_interval;
738 aabm.chance = chance / intervals;
742 aabm.chance = chance;
746 const std::vector<std::string> &required_neighbors_s =
747 abm->getRequiredNeighbors();
748 for (const std::string &required_neighbor_s : required_neighbors_s) {
749 ndef->getIds(required_neighbor_s, aabm.required_neighbors);
751 aabm.check_required_neighbors = !required_neighbors_s.empty();
754 const std::vector<std::string> &contents_s = abm->getTriggerContents();
755 for (const std::string &content_s : contents_s) {
756 std::vector<content_t> ids;
757 ndef->getIds(content_s, ids);
758 for (content_t c : ids) {
759 if (c >= m_aabms.size())
760 m_aabms.resize(c + 256, NULL);
762 m_aabms[c] = new std::vector<ActiveABM>;
763 m_aabms[c]->push_back(aabm);
771 for (auto &aabms : m_aabms)
775 // Find out how many objects the given block and its neighbours contain.
776 // Returns the number of objects in the block, and also in 'wider' the
777 // number of objects in the block and all its neighbours. The latter
778 // may an estimate if any neighbours are unloaded.
779 u32 countObjects(MapBlock *block, ServerMap * map, u32 &wider)
782 u32 wider_unknown_count = 0;
783 for(s16 x=-1; x<=1; x++)
784 for(s16 y=-1; y<=1; y++)
785 for(s16 z=-1; z<=1; z++)
787 MapBlock *block2 = map->getBlockNoCreateNoEx(
788 block->getPos() + v3s16(x,y,z));
790 wider_unknown_count++;
793 wider += block2->m_static_objects.m_active.size()
794 + block2->m_static_objects.m_stored.size();
797 u32 active_object_count = block->m_static_objects.m_active.size();
798 u32 wider_known_count = 3*3*3 - wider_unknown_count;
799 wider += wider_unknown_count * wider / wider_known_count;
800 return active_object_count;
803 void apply(MapBlock *block)
805 if(m_aabms.empty() || block->isDummy())
808 ServerMap *map = &m_env->getServerMap();
810 u32 active_object_count_wider;
811 u32 active_object_count = this->countObjects(block, map, active_object_count_wider);
812 m_env->m_added_objects = 0;
815 for(p0.X=0; p0.X<MAP_BLOCKSIZE; p0.X++)
816 for(p0.Y=0; p0.Y<MAP_BLOCKSIZE; p0.Y++)
817 for(p0.Z=0; p0.Z<MAP_BLOCKSIZE; p0.Z++)
819 const MapNode &n = block->getNodeUnsafe(p0);
820 content_t c = n.getContent();
822 if (c >= m_aabms.size() || !m_aabms[c])
825 v3s16 p = p0 + block->getPosRelative();
826 for (ActiveABM &aabm : *m_aabms[c]) {
827 if (myrand() % aabm.chance != 0)
831 if (aabm.check_required_neighbors) {
833 for(p1.X = p0.X-1; p1.X <= p0.X+1; p1.X++)
834 for(p1.Y = p0.Y-1; p1.Y <= p0.Y+1; p1.Y++)
835 for(p1.Z = p0.Z-1; p1.Z <= p0.Z+1; p1.Z++)
840 if (block->isValidPosition(p1)) {
841 // if the neighbor is found on the same map block
842 // get it straight from there
843 const MapNode &n = block->getNodeUnsafe(p1);
846 // otherwise consult the map
847 MapNode n = map->getNodeNoEx(p1 + block->getPosRelative());
850 if (CONTAINS(aabm.required_neighbors, c))
853 // No required neighbor found
858 // Call all the trigger variations
859 aabm.abm->trigger(m_env, p, n);
860 aabm.abm->trigger(m_env, p, n,
861 active_object_count, active_object_count_wider);
863 // Count surrounding objects again if the abms added any
864 if(m_env->m_added_objects > 0) {
865 active_object_count = countObjects(block, map, active_object_count_wider);
866 m_env->m_added_objects = 0;
873 void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime)
875 // Reset usage timer immediately, otherwise a block that becomes active
876 // again at around the same time as it would normally be unloaded will
877 // get unloaded incorrectly. (I think this still leaves a small possibility
878 // of a race condition between this and server::AsyncRunStep, which only
879 // some kind of synchronisation will fix, but it at least reduces the window
880 // of opportunity for it to break from seconds to nanoseconds)
881 block->resetUsageTimer();
883 // Get time difference
885 u32 stamp = block->getTimestamp();
886 if (m_game_time > stamp && stamp != BLOCK_TIMESTAMP_UNDEFINED)
887 dtime_s = m_game_time - stamp;
888 dtime_s += additional_dtime;
890 /*infostream<<"ServerEnvironment::activateBlock(): block timestamp: "
891 <<stamp<<", game time: "<<m_game_time<<std::endl;*/
893 // Remove stored static objects if clearObjects was called since block's timestamp
894 if (stamp == BLOCK_TIMESTAMP_UNDEFINED || stamp < m_last_clear_objects_time) {
895 block->m_static_objects.m_stored.clear();
896 // do not set changed flag to avoid unnecessary mapblock writes
899 // Set current time as timestamp
900 block->setTimestampNoChangedFlag(m_game_time);
902 /*infostream<<"ServerEnvironment::activateBlock(): block is "
903 <<dtime_s<<" seconds old."<<std::endl;*/
905 // Activate stored objects
906 activateObjects(block, dtime_s);
908 /* Handle LoadingBlockModifiers */
909 m_lbm_mgr.applyLBMs(this, block, stamp);
912 std::vector<NodeTimer> elapsed_timers =
913 block->m_node_timers.step((float)dtime_s);
914 if (!elapsed_timers.empty()) {
916 for (const NodeTimer &elapsed_timer : elapsed_timers) {
917 n = block->getNodeNoEx(elapsed_timer.position);
918 v3s16 p = elapsed_timer.position + block->getPosRelative();
919 if (m_script->node_on_timer(p, n, elapsed_timer.elapsed))
920 block->setNodeTimer(NodeTimer(elapsed_timer.timeout, 0,
921 elapsed_timer.position));
926 void ServerEnvironment::addActiveBlockModifier(ActiveBlockModifier *abm)
928 m_abms.emplace_back(abm);
931 void ServerEnvironment::addLoadingBlockModifierDef(LoadingBlockModifierDef *lbm)
933 m_lbm_mgr.addLBMDef(lbm);
936 bool ServerEnvironment::setNode(v3s16 p, const MapNode &n)
938 const NodeDefManager *ndef = m_server->ndef();
939 MapNode n_old = m_map->getNodeNoEx(p);
941 const ContentFeatures &cf_old = ndef->get(n_old);
944 if (cf_old.has_on_destruct)
945 m_script->node_on_destruct(p, n_old);
948 if (!m_map->addNodeWithEvent(p, n))
951 // Update active VoxelManipulator if a mapgen thread
952 m_map->updateVManip(p);
954 // Call post-destructor
955 if (cf_old.has_after_destruct)
956 m_script->node_after_destruct(p, n_old);
958 // Retrieve node content features
959 // if new node is same as old, reuse old definition to prevent a lookup
960 const ContentFeatures &cf_new = n_old == n ? cf_old : ndef->get(n);
963 if (cf_new.has_on_construct)
964 m_script->node_on_construct(p, n);
969 bool ServerEnvironment::removeNode(v3s16 p)
971 const NodeDefManager *ndef = m_server->ndef();
972 MapNode n_old = m_map->getNodeNoEx(p);
975 if (ndef->get(n_old).has_on_destruct)
976 m_script->node_on_destruct(p, n_old);
979 // This is slightly optimized compared to addNodeWithEvent(air)
980 if (!m_map->removeNodeWithEvent(p))
983 // Update active VoxelManipulator if a mapgen thread
984 m_map->updateVManip(p);
986 // Call post-destructor
987 if (ndef->get(n_old).has_after_destruct)
988 m_script->node_after_destruct(p, n_old);
990 // Air doesn't require constructor
994 bool ServerEnvironment::swapNode(v3s16 p, const MapNode &n)
996 if (!m_map->addNodeWithEvent(p, n, false))
999 // Update active VoxelManipulator if a mapgen thread
1000 m_map->updateVManip(p);
1005 void ServerEnvironment::getObjectsInsideRadius(std::vector<u16> &objects, v3f pos,
1008 objects = m_active_objects.getObjectsInsideRadius(pos, radius);
1011 void ServerEnvironment::clearObjects(ClearObjectsMode mode)
1013 infostream << "ServerEnvironment::clearObjects(): "
1014 << "Removing all active objects" << std::endl;
1015 std::vector<u16> objects_to_remove;
1016 for (auto &it : m_active_objects.getObjects()) {
1018 ServerActiveObject* obj = it.second.object;
1019 if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER)
1022 // Delete static object if block is loaded
1023 deleteStaticFromBlock(obj, id, MOD_REASON_CLEAR_ALL_OBJECTS, true);
1025 // If known by some client, don't delete immediately
1026 if (obj->m_known_by_count > 0) {
1027 obj->m_pending_removal = true;
1031 // Tell the object about removal
1032 obj->removingFromEnvironment();
1033 // Deregister in scripting api
1034 m_script->removeObjectReference(obj);
1036 // Delete active object
1037 if (obj->environmentDeletes())
1039 // Id to be removed from m_active_objects
1040 objects_to_remove.push_back(id);
1043 // Remove references from m_active_objects
1044 for (u16 i : objects_to_remove) {
1045 m_active_objects.removeObject(i);
1048 // Get list of loaded blocks
1049 std::vector<v3s16> loaded_blocks;
1050 infostream << "ServerEnvironment::clearObjects(): "
1051 << "Listing all loaded blocks" << std::endl;
1052 m_map->listAllLoadedBlocks(loaded_blocks);
1053 infostream << "ServerEnvironment::clearObjects(): "
1054 << "Done listing all loaded blocks: "
1055 << loaded_blocks.size()<<std::endl;
1057 // Get list of loadable blocks
1058 std::vector<v3s16> loadable_blocks;
1059 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1060 infostream << "ServerEnvironment::clearObjects(): "
1061 << "Listing all loadable blocks" << std::endl;
1062 m_map->listAllLoadableBlocks(loadable_blocks);
1063 infostream << "ServerEnvironment::clearObjects(): "
1064 << "Done listing all loadable blocks: "
1065 << loadable_blocks.size() << std::endl;
1067 loadable_blocks = loaded_blocks;
1070 actionstream << "ServerEnvironment::clearObjects(): "
1071 << "Now clearing objects in " << loadable_blocks.size()
1072 << " blocks" << std::endl;
1074 // Grab a reference on each loaded block to avoid unloading it
1075 for (v3s16 p : loaded_blocks) {
1076 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1077 assert(block != NULL);
1081 // Remove objects in all loadable blocks
1082 u32 unload_interval = U32_MAX;
1083 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1084 unload_interval = g_settings->getS32("max_clearobjects_extra_loaded_blocks");
1085 unload_interval = MYMAX(unload_interval, 1);
1087 u32 report_interval = loadable_blocks.size() / 10;
1088 u32 num_blocks_checked = 0;
1089 u32 num_blocks_cleared = 0;
1090 u32 num_objs_cleared = 0;
1091 for (auto i = loadable_blocks.begin();
1092 i != loadable_blocks.end(); ++i) {
1094 MapBlock *block = m_map->emergeBlock(p, false);
1096 errorstream << "ServerEnvironment::clearObjects(): "
1097 << "Failed to emerge block " << PP(p) << std::endl;
1100 u32 num_stored = block->m_static_objects.m_stored.size();
1101 u32 num_active = block->m_static_objects.m_active.size();
1102 if (num_stored != 0 || num_active != 0) {
1103 block->m_static_objects.m_stored.clear();
1104 block->m_static_objects.m_active.clear();
1105 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1106 MOD_REASON_CLEAR_ALL_OBJECTS);
1107 num_objs_cleared += num_stored + num_active;
1108 num_blocks_cleared++;
1110 num_blocks_checked++;
1112 if (report_interval != 0 &&
1113 num_blocks_checked % report_interval == 0) {
1114 float percent = 100.0 * (float)num_blocks_checked /
1115 loadable_blocks.size();
1116 actionstream << "ServerEnvironment::clearObjects(): "
1117 << "Cleared " << num_objs_cleared << " objects"
1118 << " in " << num_blocks_cleared << " blocks ("
1119 << percent << "%)" << std::endl;
1121 if (num_blocks_checked % unload_interval == 0) {
1122 m_map->unloadUnreferencedBlocks();
1125 m_map->unloadUnreferencedBlocks();
1127 // Drop references that were added above
1128 for (v3s16 p : loaded_blocks) {
1129 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1134 m_last_clear_objects_time = m_game_time;
1136 actionstream << "ServerEnvironment::clearObjects(): "
1137 << "Finished: Cleared " << num_objs_cleared << " objects"
1138 << " in " << num_blocks_cleared << " blocks" << std::endl;
1141 void ServerEnvironment::step(float dtime)
1143 /* Step time of day */
1144 stepTimeOfDay(dtime);
1147 // NOTE: This is kind of funny on a singleplayer game, but doesn't
1148 // really matter that much.
1149 static thread_local const float server_step =
1150 g_settings->getFloat("dedicated_server_step");
1151 m_recommended_send_interval = server_step;
1157 m_game_time_fraction_counter += dtime;
1158 u32 inc_i = (u32)m_game_time_fraction_counter;
1159 m_game_time += inc_i;
1160 m_game_time_fraction_counter -= (float)inc_i;
1167 ScopeProfiler sp(g_profiler, "SEnv: handle players avg", SPT_AVG);
1168 for (RemotePlayer *player : m_players) {
1169 // Ignore disconnected players
1170 if (player->getPeerId() == PEER_ID_INEXISTENT)
1174 player->move(dtime, this, 100 * BS);
1179 Manage active block list
1181 if (m_active_blocks_management_interval.step(dtime, m_cache_active_block_mgmt_interval)) {
1182 ScopeProfiler sp(g_profiler, "SEnv: manage act. block list avg per interval", SPT_AVG);
1184 Get player block positions
1186 std::vector<PlayerSAO*> players;
1187 for (RemotePlayer *player: m_players) {
1188 // Ignore disconnected players
1189 if (player->getPeerId() == PEER_ID_INEXISTENT)
1192 PlayerSAO *playersao = player->getPlayerSAO();
1195 players.push_back(playersao);
1199 Update list of active blocks, collecting changes
1201 // use active_object_send_range_blocks since that is max distance
1202 // for active objects sent the client anyway
1203 static thread_local const s16 active_object_range =
1204 g_settings->getS16("active_object_send_range_blocks");
1205 static thread_local const s16 active_block_range =
1206 g_settings->getS16("active_block_range");
1207 std::set<v3s16> blocks_removed;
1208 std::set<v3s16> blocks_added;
1209 m_active_blocks.update(players, active_block_range, active_object_range,
1210 blocks_removed, blocks_added);
1213 Handle removed blocks
1216 // Convert active objects that are no more in active blocks to static
1217 deactivateFarObjects(false);
1219 for (const v3s16 &p: blocks_removed) {
1220 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1224 // Set current time as timestamp (and let it set ChangedFlag)
1225 block->setTimestamp(m_game_time);
1232 for (const v3s16 &p: blocks_added) {
1233 MapBlock *block = m_map->getBlockOrEmerge(p);
1235 m_active_blocks.m_list.erase(p);
1236 m_active_blocks.m_abm_list.erase(p);
1240 activateBlock(block);
1245 Mess around in active blocks
1247 if (m_active_blocks_nodemetadata_interval.step(dtime, m_cache_nodetimer_interval)) {
1248 ScopeProfiler sp(g_profiler, "SEnv: mess in act. blocks avg per interval", SPT_AVG);
1250 float dtime = m_cache_nodetimer_interval;
1252 for (const v3s16 &p: m_active_blocks.m_list) {
1253 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1257 // Reset block usage timer
1258 block->resetUsageTimer();
1260 // Set current time as timestamp
1261 block->setTimestampNoChangedFlag(m_game_time);
1262 // If time has changed much from the one on disk,
1263 // set block to be saved when it is unloaded
1264 if(block->getTimestamp() > block->getDiskTimestamp() + 60)
1265 block->raiseModified(MOD_STATE_WRITE_AT_UNLOAD,
1266 MOD_REASON_BLOCK_EXPIRED);
1269 std::vector<NodeTimer> elapsed_timers = block->m_node_timers.step(dtime);
1270 if (!elapsed_timers.empty()) {
1273 for (const NodeTimer &elapsed_timer: elapsed_timers) {
1274 n = block->getNodeNoEx(elapsed_timer.position);
1275 p2 = elapsed_timer.position + block->getPosRelative();
1276 if (m_script->node_on_timer(p2, n, elapsed_timer.elapsed)) {
1277 block->setNodeTimer(NodeTimer(
1278 elapsed_timer.timeout, 0, elapsed_timer.position));
1285 if (m_active_block_modifier_interval.step(dtime, m_cache_abm_interval))
1287 if (m_active_block_interval_overload_skip > 0) {
1288 ScopeProfiler sp(g_profiler, "SEnv: ABM overload skips");
1289 m_active_block_interval_overload_skip--;
1292 ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg per interval", SPT_AVG);
1293 TimeTaker timer("modify in active blocks per interval");
1295 // Initialize handling of ActiveBlockModifiers
1296 ABMHandler abmhandler(m_abms, m_cache_abm_interval, this, true);
1298 for (const v3s16 &p : m_active_blocks.m_abm_list) {
1299 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1303 // Set current time as timestamp
1304 block->setTimestampNoChangedFlag(m_game_time);
1306 /* Handle ActiveBlockModifiers */
1307 abmhandler.apply(block);
1310 u32 time_ms = timer.stop(true);
1311 u32 max_time_ms = 200;
1312 if (time_ms > max_time_ms) {
1313 warningstream<<"active block modifiers took "
1314 <<time_ms<<"ms (longer than "
1315 <<max_time_ms<<"ms)"<<std::endl;
1316 m_active_block_interval_overload_skip = (time_ms / max_time_ms) + 1;
1321 Step script environment (run global on_step())
1323 m_script->environment_Step(dtime);
1329 ScopeProfiler sp(g_profiler, "SEnv: step act. objs avg", SPT_AVG);
1330 //TimeTaker timer("Step active objects");
1332 g_profiler->avg("SEnv: num of objects", m_active_objects.size());
1334 // This helps the objects to send data at the same time
1335 bool send_recommended = false;
1336 m_send_recommended_timer += dtime;
1337 if(m_send_recommended_timer > getSendRecommendedInterval())
1339 m_send_recommended_timer -= getSendRecommendedInterval();
1340 send_recommended = true;
1343 for (auto &ao_it : m_active_objects.getObjects()) {
1344 ServerActiveObject* obj = ao_it.second.object;
1349 obj->step(dtime, send_recommended);
1350 // Read messages from object
1351 while (!obj->m_messages_out.empty()) {
1352 m_active_object_messages.push(obj->m_messages_out.front());
1353 obj->m_messages_out.pop();
1359 Manage active objects
1361 if (m_object_management_interval.step(dtime, 0.5)) {
1362 ScopeProfiler sp(g_profiler, "SEnv: remove removed objs avg /.5s", SPT_AVG);
1363 removeRemovedObjects();
1367 Manage particle spawner expiration
1369 if (m_particle_management_interval.step(dtime, 1.0)) {
1370 for (std::unordered_map<u32, float>::iterator i = m_particle_spawners.begin();
1371 i != m_particle_spawners.end(); ) {
1372 //non expiring spawners
1373 if (i->second == PARTICLE_SPAWNER_NO_EXPIRY) {
1379 if (i->second <= 0.f)
1380 m_particle_spawners.erase(i++);
1387 u32 ServerEnvironment::addParticleSpawner(float exptime)
1389 // Timers with lifetime 0 do not expire
1390 float time = exptime > 0.f ? exptime : PARTICLE_SPAWNER_NO_EXPIRY;
1393 for (;;) { // look for unused particlespawner id
1395 std::unordered_map<u32, float>::iterator f = m_particle_spawners.find(id);
1396 if (f == m_particle_spawners.end()) {
1397 m_particle_spawners[id] = time;
1404 u32 ServerEnvironment::addParticleSpawner(float exptime, u16 attached_id)
1406 u32 id = addParticleSpawner(exptime);
1407 m_particle_spawner_attachments[id] = attached_id;
1408 if (ServerActiveObject *obj = getActiveObject(attached_id)) {
1409 obj->attachParticleSpawner(id);
1414 void ServerEnvironment::deleteParticleSpawner(u32 id, bool remove_from_object)
1416 m_particle_spawners.erase(id);
1417 const auto &it = m_particle_spawner_attachments.find(id);
1418 if (it != m_particle_spawner_attachments.end()) {
1419 u16 obj_id = it->second;
1420 ServerActiveObject *sao = getActiveObject(obj_id);
1421 if (sao != NULL && remove_from_object) {
1422 sao->detachParticleSpawner(id);
1424 m_particle_spawner_attachments.erase(id);
1428 ServerActiveObject* ServerEnvironment::getActiveObject(u16 id)
1430 return m_active_objects.getObject(id);
1433 u16 ServerEnvironment::addActiveObject(ServerActiveObject *object)
1435 assert(object); // Pre-condition
1437 u16 id = addActiveObjectRaw(object, true, 0);
1441 void ServerEnvironment::updateActiveObject(ServerActiveObject *object)
1444 m_active_objects.updateObject(object);
1448 Finds out what new objects have been added to
1449 inside a radius around a position
1451 void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius,
1453 std::set<u16> ¤t_objects,
1454 std::queue<u16> &added_objects)
1456 f32 radius_f = radius * BS;
1457 f32 player_radius_f = player_radius * BS;
1459 if (player_radius_f < 0)
1460 player_radius_f = 0;
1462 Go through the object list,
1463 - discard removed/deactivated objects,
1464 - discard objects that are too far away,
1465 - discard objects that are found in current_objects.
1466 - add remaining objects to added_objects
1468 for (auto &ao_it : m_active_objects.getObjects()) {
1469 u16 id = ao_it.first;
1472 ServerActiveObject *object = ao_it.second.object;
1476 if (object->isGone())
1479 f32 distance_f = object->getBasePosition().
1480 getDistanceFrom(playersao->getBasePosition());
1481 if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1482 // Discard if too far
1483 if (distance_f > player_radius_f && player_radius_f != 0)
1485 } else if (distance_f > radius_f)
1488 // Discard if already on current_objects
1489 std::set<u16>::iterator n;
1490 n = current_objects.find(id);
1491 if(n != current_objects.end())
1493 // Add to added_objects
1494 added_objects.push(id);
1499 Finds out what objects have been removed from
1500 inside a radius around a position
1502 void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius,
1504 std::set<u16> ¤t_objects,
1505 std::queue<u16> &removed_objects)
1507 f32 radius_f = radius * BS;
1508 f32 player_radius_f = player_radius * BS;
1510 if (player_radius_f < 0)
1511 player_radius_f = 0;
1513 Go through current_objects; object is removed if:
1514 - object is not found in m_active_objects (this is actually an
1515 error condition; objects should be removed only after all clients
1516 have been informed about removal), or
1517 - object is to be removed or deactivated, or
1518 - object is too far away
1520 for (u16 id : current_objects) {
1521 ServerActiveObject *object = getActiveObject(id);
1523 if (object == NULL) {
1524 infostream << "ServerEnvironment::getRemovedActiveObjects():"
1525 << " object in current_objects is NULL" << std::endl;
1526 removed_objects.push(id);
1530 if (object->isGone()) {
1531 removed_objects.push(id);
1535 f32 distance_f = object->getBasePosition().getDistanceFrom(playersao->getBasePosition());
1536 if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1537 if (distance_f <= player_radius_f || player_radius_f == 0)
1539 } else if (distance_f <= radius_f)
1542 // Object is no longer visible
1543 removed_objects.push(id);
1547 void ServerEnvironment::setStaticForActiveObjectsInBlock(
1548 v3s16 blockpos, bool static_exists, v3s16 static_block)
1550 MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos);
1554 for (auto &so_it : block->m_static_objects.m_active) {
1555 // Get the ServerActiveObject counterpart to this StaticObject
1556 ServerActiveObject *sao = m_active_objects.getObject(so_it.first);
1558 // If this ever happens, there must be some kind of nasty bug.
1559 errorstream << "ServerEnvironment::setStaticForObjectsInBlock(): "
1560 "Object from MapBlock::m_static_objects::m_active not found "
1561 "in m_active_objects";
1564 sao->m_static_exists = static_exists;
1565 sao->m_static_block = static_block;
1569 ActiveObjectMessage ServerEnvironment::getActiveObjectMessage()
1571 if(m_active_object_messages.empty())
1572 return ActiveObjectMessage(0);
1574 ActiveObjectMessage message = m_active_object_messages.front();
1575 m_active_object_messages.pop();
1579 void ServerEnvironment::getSelectedActiveObjects(
1580 const core::line3d<f32> &shootline_on_map,
1581 std::vector<PointedThing> &objects)
1583 std::vector<u16> objectIds;
1584 getObjectsInsideRadius(objectIds, shootline_on_map.start,
1585 shootline_on_map.getLength() + 10.0f);
1586 const v3f line_vector = shootline_on_map.getVector();
1588 for (u16 objectId : objectIds) {
1589 ServerActiveObject* obj = getActiveObject(objectId);
1591 aabb3f selection_box;
1592 if (!obj->getSelectionBox(&selection_box))
1595 v3f pos = obj->getBasePosition();
1597 aabb3f offsetted_box(selection_box.MinEdge + pos,
1598 selection_box.MaxEdge + pos);
1600 v3f current_intersection;
1601 v3s16 current_normal;
1602 if (boxLineCollision(offsetted_box, shootline_on_map.start, line_vector,
1603 ¤t_intersection, ¤t_normal)) {
1604 objects.emplace_back(
1605 (s16) objectId, current_intersection, current_normal,
1606 (current_intersection - shootline_on_map.start).getLengthSQ());
1612 ************ Private methods *************
1615 u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object,
1616 bool set_changed, u32 dtime_s)
1618 assert(object); // Pre-condition
1619 if(object->getId() == 0){
1620 u16 new_id = m_active_objects.getFreeId();
1623 errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
1624 <<"no free ids available"<<std::endl;
1625 if(object->environmentDeletes())
1629 object->setId(new_id);
1632 verbosestream<<"ServerEnvironment::addActiveObjectRaw(): "
1633 <<"supplied with id "<<object->getId()<<std::endl;
1636 if (!m_active_objects.isFreeId(object->getId())) {
1637 errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
1638 <<"id is not free ("<<object->getId()<<")"<<std::endl;
1639 if(object->environmentDeletes())
1644 if (objectpos_over_limit(object->getBasePosition())) {
1645 v3f p = object->getBasePosition();
1646 warningstream << "ServerEnvironment::addActiveObjectRaw(): "
1647 << "object position (" << p.X << "," << p.Y << "," << p.Z
1648 << ") outside maximum range" << std::endl;
1649 if (object->environmentDeletes())
1654 /*infostream<<"ServerEnvironment::addActiveObjectRaw(): "
1655 <<"added (id="<<object->getId()<<")"<<std::endl;*/
1657 m_active_objects.addObject(object);
1659 verbosestream<<"ServerEnvironment::addActiveObjectRaw(): "
1660 <<"Added id="<<object->getId()<<"; there are now "
1661 <<m_active_objects.size()<<" active objects."
1664 // Register reference in scripting api (must be done before post-init)
1665 m_script->addObjectReference(object);
1666 // Post-initialize object
1667 object->addedToEnvironment(dtime_s);
1669 // Add static data to block
1670 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 std::vector<u16> objects_to_remove;
1703 for (auto &ao_it : m_active_objects.getObjects()) {
1704 u16 id = ao_it.first;
1705 ServerActiveObject* obj = ao_it.second.object;
1707 // This shouldn't happen but check it
1709 errorstream << "ServerEnvironment::removeRemovedObjects(): "
1710 << "NULL object found. id=" << id << std::endl;
1711 objects_to_remove.push_back(id);
1716 We will handle objects marked for removal or deactivation
1722 Delete static data from block if removed
1724 if (obj->m_pending_removal)
1725 deleteStaticFromBlock(obj, id, MOD_REASON_REMOVE_OBJECTS_REMOVE, false);
1727 // If still known by clients, don't actually remove. On some future
1728 // invocation this will be 0, which is when removal will continue.
1729 if(obj->m_known_by_count > 0)
1733 Move static data from active to stored if deactivated
1735 if (!obj->m_pending_removal && obj->m_static_exists) {
1736 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
1738 std::map<u16, StaticObject>::iterator i =
1739 block->m_static_objects.m_active.find(id);
1740 if (i != block->m_static_objects.m_active.end()) {
1741 block->m_static_objects.m_stored.push_back(i->second);
1742 block->m_static_objects.m_active.erase(id);
1743 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1744 MOD_REASON_REMOVE_OBJECTS_DEACTIVATE);
1746 warningstream << "ServerEnvironment::removeRemovedObjects(): "
1747 << "id=" << id << " m_static_exists=true but "
1748 << "static data doesn't actually exist in "
1749 << PP(obj->m_static_block) << std::endl;
1752 infostream << "Failed to emerge block from which an object to "
1753 << "be deactivated was loaded from. id=" << id << std::endl;
1757 // Tell the object about removal
1758 obj->removingFromEnvironment();
1759 // Deregister in scripting api
1760 m_script->removeObjectReference(obj);
1763 if(obj->environmentDeletes())
1766 objects_to_remove.push_back(id);
1768 // Remove references from m_active_objects
1769 for (u16 i : objects_to_remove) {
1770 m_active_objects.removeObject(i);
1774 static void print_hexdump(std::ostream &o, const std::string &data)
1776 const int linelength = 16;
1777 for(int l=0; ; l++){
1778 int i0 = linelength * l;
1779 bool at_end = false;
1780 int thislinelength = linelength;
1781 if(i0 + thislinelength > (int)data.size()){
1782 thislinelength = data.size() - i0;
1785 for(int di=0; di<linelength; di++){
1788 if(di<thislinelength)
1789 snprintf(buf, 4, "%.2x ", data[i]);
1791 snprintf(buf, 4, " ");
1795 for(int di=0; di<thislinelength; di++){
1809 Convert stored objects from blocks near the players to active.
1811 void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s)
1816 // Ignore if no stored objects (to not set changed flag)
1817 if(block->m_static_objects.m_stored.empty())
1820 verbosestream<<"ServerEnvironment::activateObjects(): "
1821 <<"activating objects of block "<<PP(block->getPos())
1822 <<" ("<<block->m_static_objects.m_stored.size()
1823 <<" objects)"<<std::endl;
1824 bool large_amount = (block->m_static_objects.m_stored.size() > g_settings->getU16("max_objects_per_block"));
1826 errorstream<<"suspiciously large amount of objects detected: "
1827 <<block->m_static_objects.m_stored.size()<<" in "
1828 <<PP(block->getPos())
1829 <<"; removing all of them."<<std::endl;
1830 // Clear stored list
1831 block->m_static_objects.m_stored.clear();
1832 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1833 MOD_REASON_TOO_MANY_OBJECTS);
1837 // Activate stored objects
1838 std::vector<StaticObject> new_stored;
1839 for (const StaticObject &s_obj : block->m_static_objects.m_stored) {
1840 // Create an active object from the data
1841 ServerActiveObject *obj = ServerActiveObject::create
1842 ((ActiveObjectType) s_obj.type, this, 0, s_obj.pos, s_obj.data);
1843 // If couldn't create object, store static data back.
1845 errorstream<<"ServerEnvironment::activateObjects(): "
1846 <<"failed to create active object from static object "
1847 <<"in block "<<PP(s_obj.pos/BS)
1848 <<" type="<<(int)s_obj.type<<" data:"<<std::endl;
1849 print_hexdump(verbosestream, s_obj.data);
1851 new_stored.push_back(s_obj);
1854 verbosestream<<"ServerEnvironment::activateObjects(): "
1855 <<"activated static object pos="<<PP(s_obj.pos/BS)
1856 <<" type="<<(int)s_obj.type<<std::endl;
1857 // This will also add the object to the active static list
1858 addActiveObjectRaw(obj, false, dtime_s);
1861 // Clear stored list
1862 block->m_static_objects.m_stored.clear();
1863 // Add leftover failed stuff to stored list
1864 for (const StaticObject &s_obj : new_stored) {
1865 block->m_static_objects.m_stored.push_back(s_obj);
1869 Note: Block hasn't really been modified here.
1870 The objects have just been activated and moved from the stored
1871 static list to the active static list.
1872 As such, the block is essentially the same.
1873 Thus, do not call block->raiseModified(MOD_STATE_WRITE_NEEDED).
1874 Otherwise there would be a huge amount of unnecessary I/O.
1879 Convert objects that are not standing inside active blocks to static.
1881 If m_known_by_count != 0, active object is not deleted, but static
1882 data is still updated.
1884 If force_delete is set, active object is deleted nevertheless. It
1885 shall only be set so in the destructor of the environment.
1887 If block wasn't generated (not in memory or on disk),
1889 void ServerEnvironment::deactivateFarObjects(bool _force_delete)
1891 std::vector<u16> objects_to_remove;
1892 for (auto &ao_it : m_active_objects.getObjects()) {
1893 // force_delete might be overriden per object
1894 bool force_delete = _force_delete;
1896 ServerActiveObject* obj = ao_it.second.object;
1899 // Do not deactivate if static data creation not allowed
1900 if(!force_delete && !obj->isStaticAllowed())
1903 // removeRemovedObjects() is responsible for these
1904 if(!force_delete && obj->isGone())
1907 u16 id = ao_it.first;
1908 v3f objectpos = obj->getBasePosition();
1910 // The block in which the object resides in
1911 v3s16 blockpos_o = getNodeBlockPos(floatToInt(objectpos, BS));
1913 // If object's static data is stored in a deactivated block and object
1914 // is actually located in an active block, re-save to the block in
1915 // which the object is actually located in.
1917 obj->m_static_exists &&
1918 !m_active_blocks.contains(obj->m_static_block) &&
1919 m_active_blocks.contains(blockpos_o))
1921 // Delete from block where object was located
1922 deleteStaticFromBlock(obj, id, MOD_REASON_STATIC_DATA_REMOVED, false);
1924 StaticObject s_obj(obj, objectpos);
1925 // Save to block where object is located
1926 saveStaticToBlock(blockpos_o, id, obj, s_obj, MOD_REASON_STATIC_DATA_ADDED);
1931 // If block is still active, don't remove
1932 if(!force_delete && m_active_blocks.contains(blockpos_o))
1935 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1936 << "deactivating object id=" << id << " on inactive block "
1937 << PP(blockpos_o) << std::endl;
1939 // If known by some client, don't immediately delete.
1940 bool pending_delete = (obj->m_known_by_count > 0 && !force_delete);
1943 Update the static data
1945 if (obj->isStaticAllowed()) {
1946 // Create new static object
1947 StaticObject s_obj(obj, objectpos);
1949 bool stays_in_same_block = false;
1950 bool data_changed = true;
1952 // Check if static data has changed considerably
1953 if (obj->m_static_exists) {
1954 if (obj->m_static_block == blockpos_o)
1955 stays_in_same_block = true;
1957 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
1960 std::map<u16, StaticObject>::iterator n =
1961 block->m_static_objects.m_active.find(id);
1962 if (n != block->m_static_objects.m_active.end()) {
1963 StaticObject static_old = n->second;
1965 float save_movem = obj->getMinimumSavedMovement();
1967 if (static_old.data == s_obj.data &&
1968 (static_old.pos - objectpos).getLength() < save_movem)
1969 data_changed = false;
1971 warningstream << "ServerEnvironment::deactivateFarObjects(): "
1972 << "id=" << id << " m_static_exists=true but "
1973 << "static data doesn't actually exist in "
1974 << PP(obj->m_static_block) << std::endl;
1980 While changes are always saved, blocks are only marked as modified
1981 if the object has moved or different staticdata. (see above)
1983 bool shall_be_written = (!stays_in_same_block || data_changed);
1984 u32 reason = shall_be_written ? MOD_REASON_STATIC_DATA_CHANGED : MOD_REASON_UNKNOWN;
1986 // Delete old static object
1987 deleteStaticFromBlock(obj, id, reason, false);
1989 // Add to the block where the object is located in
1990 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
1991 u16 store_id = pending_delete ? id : 0;
1992 if (!saveStaticToBlock(blockpos, store_id, obj, s_obj, reason))
1993 force_delete = true;
1997 If known by some client, set pending deactivation.
1998 Otherwise delete it immediately.
2000 if(pending_delete && !force_delete)
2002 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
2003 << "object id=" << id << " is known by clients"
2004 << "; not deleting yet" << std::endl;
2006 obj->m_pending_deactivation = true;
2009 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
2010 << "object id=" << id << " is not known by clients"
2011 << "; deleting" << std::endl;
2013 // Tell the object about removal
2014 obj->removingFromEnvironment();
2015 // Deregister in scripting api
2016 m_script->removeObjectReference(obj);
2018 // Delete active object
2019 if(obj->environmentDeletes())
2021 // Id to be removed from m_active_objects
2022 objects_to_remove.push_back(id);
2025 // Remove references from m_active_objects
2026 for (u16 i : objects_to_remove) {
2027 m_active_objects.removeObject(i);
2031 void ServerEnvironment::deleteStaticFromBlock(
2032 ServerActiveObject *obj, u16 id, u32 mod_reason, bool no_emerge)
2034 if (!obj->m_static_exists)
2039 block = m_map->getBlockNoCreateNoEx(obj->m_static_block);
2041 block = m_map->emergeBlock(obj->m_static_block, false);
2044 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2045 << " when deleting static data of object from it. id=" << id << std::endl;
2049 block->m_static_objects.remove(id);
2050 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2051 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2053 obj->m_static_exists = false;
2056 bool ServerEnvironment::saveStaticToBlock(
2057 v3s16 blockpos, u16 store_id,
2058 ServerActiveObject *obj, const StaticObject &s_obj,
2061 MapBlock *block = nullptr;
2063 block = m_map->emergeBlock(blockpos);
2064 } catch (InvalidPositionException &e) {
2065 // Handled via NULL pointer
2066 // NOTE: emergeBlock's failure is usually determined by it
2067 // actually returning NULL
2071 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2072 << " when saving static data of object to it. id=" << store_id << std::endl;
2075 if (block->m_static_objects.m_stored.size() >= g_settings->getU16("max_objects_per_block")) {
2076 warningstream << "ServerEnv: Trying to store id = " << store_id
2077 << " statically but block " << PP(blockpos)
2078 << " already contains "
2079 << block->m_static_objects.m_stored.size()
2080 << " objects." << std::endl;
2084 block->m_static_objects.insert(store_id, s_obj);
2085 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2086 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2088 obj->m_static_exists = true;
2089 obj->m_static_block = blockpos;
2094 PlayerDatabase *ServerEnvironment::openPlayerDatabase(const std::string &name,
2095 const std::string &savedir, const Settings &conf)
2098 if (name == "sqlite3")
2099 return new PlayerDatabaseSQLite3(savedir);
2101 if (name == "dummy")
2102 return new Database_Dummy();
2104 if (name == "postgresql") {
2105 std::string connect_string;
2106 conf.getNoEx("pgsql_player_connection", connect_string);
2107 return new PlayerDatabasePostgreSQL(connect_string);
2110 if (name == "files")
2111 return new PlayerDatabaseFiles(savedir + DIR_DELIM + "players");
2113 throw BaseException(std::string("Database backend ") + name + " not supported.");
2116 bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params,
2117 const Settings &cmd_args)
2119 std::string migrate_to = cmd_args.get("migrate-players");
2121 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2122 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2123 errorstream << "Cannot read world.mt!" << std::endl;
2127 if (!world_mt.exists("player_backend")) {
2128 errorstream << "Please specify your current backend in world.mt:"
2130 << " player_backend = {files|sqlite3|postgresql}"
2135 std::string backend = world_mt.get("player_backend");
2136 if (backend == migrate_to) {
2137 errorstream << "Cannot migrate: new backend is same"
2138 << " as the old one" << std::endl;
2142 const std::string players_backup_path = game_params.world_path + DIR_DELIM
2145 if (backend == "files") {
2146 // Create backup directory
2147 fs::CreateDir(players_backup_path);
2151 PlayerDatabase *srcdb = ServerEnvironment::openPlayerDatabase(backend,
2152 game_params.world_path, world_mt);
2153 PlayerDatabase *dstdb = ServerEnvironment::openPlayerDatabase(migrate_to,
2154 game_params.world_path, world_mt);
2156 std::vector<std::string> player_list;
2157 srcdb->listPlayers(player_list);
2158 for (std::vector<std::string>::const_iterator it = player_list.begin();
2159 it != player_list.end(); ++it) {
2160 actionstream << "Migrating player " << it->c_str() << std::endl;
2161 RemotePlayer player(it->c_str(), NULL);
2162 PlayerSAO playerSAO(NULL, &player, 15000, false);
2164 srcdb->loadPlayer(&player, &playerSAO);
2166 playerSAO.finalize(&player, std::set<std::string>());
2167 player.setPlayerSAO(&playerSAO);
2169 dstdb->savePlayer(&player);
2171 // For files source, move player files to backup dir
2172 if (backend == "files") {
2174 game_params.world_path + DIR_DELIM + "players" + DIR_DELIM + (*it),
2175 players_backup_path + DIR_DELIM + (*it));
2179 actionstream << "Successfully migrated " << player_list.size() << " players"
2181 world_mt.set("player_backend", migrate_to);
2182 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2183 errorstream << "Failed to update world.mt!" << std::endl;
2185 actionstream << "world.mt updated" << std::endl;
2187 // When migration is finished from file backend, remove players directory if empty
2188 if (backend == "files") {
2189 fs::DeleteSingleFileOrEmptyDirectory(game_params.world_path + DIR_DELIM
2196 } catch (BaseException &e) {
2197 errorstream << "An error occured during migration: " << e.what() << std::endl;