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 INodeDefManager *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 ServerMap &map = getServerMap();
571 if (map.getMapgenParams()->saoPosOverLimit(playersao->getBasePosition())) {
572 actionstream << "Respawn position for player \""
573 << player->getName() << "\" outside limits, resetting" << std::endl;
574 playersao->setBasePosition(m_server->findSpawnPos());
578 // Add player to environment
581 /* Clean up old HUD elements from previous sessions */
584 /* Add object to environment */
585 addActiveObject(playersao);
590 void ServerEnvironment::saveMeta()
592 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
594 // Open file and serialize
595 std::ostringstream ss(std::ios_base::binary);
598 args.setU64("game_time", m_game_time);
599 args.setU64("time_of_day", getTimeOfDay());
600 args.setU64("last_clear_objects_time", m_last_clear_objects_time);
601 args.setU64("lbm_introduction_times_version", 1);
602 args.set("lbm_introduction_times",
603 m_lbm_mgr.createIntroductionTimesString());
604 args.setU64("day_count", m_day_count);
608 if(!fs::safeWriteToFile(path, ss.str()))
610 infostream<<"ServerEnvironment::saveMeta(): Failed to write "
612 throw SerializationError("Couldn't save env meta");
616 void ServerEnvironment::loadMeta()
618 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
620 // Open file and deserialize
621 std::ifstream is(path.c_str(), std::ios_base::binary);
623 infostream << "ServerEnvironment::loadMeta(): Failed to open "
624 << path << std::endl;
625 throw SerializationError("Couldn't load env meta");
630 if (!args.parseConfigLines(is, "EnvArgsEnd")) {
631 throw SerializationError("ServerEnvironment::loadMeta(): "
632 "EnvArgsEnd not found!");
636 m_game_time = args.getU64("game_time");
637 } catch (SettingNotFoundException &e) {
638 // Getting this is crucial, otherwise timestamps are useless
639 throw SerializationError("Couldn't load env meta game_time");
642 setTimeOfDay(args.exists("time_of_day") ?
643 // set day to early morning by default
644 args.getU64("time_of_day") : 5250);
646 m_last_clear_objects_time = args.exists("last_clear_objects_time") ?
647 // If missing, do as if clearObjects was never called
648 args.getU64("last_clear_objects_time") : 0;
650 std::string lbm_introduction_times;
652 u64 ver = args.getU64("lbm_introduction_times_version");
654 lbm_introduction_times = args.get("lbm_introduction_times");
656 infostream << "ServerEnvironment::loadMeta(): Non-supported"
657 << " introduction time version " << ver << std::endl;
659 } catch (SettingNotFoundException &e) {
660 // No problem, this is expected. Just continue with an empty string
662 m_lbm_mgr.loadIntroductionTimes(lbm_introduction_times, m_server, m_game_time);
664 m_day_count = args.exists("day_count") ?
665 args.getU64("day_count") : 0;
668 void ServerEnvironment::loadDefaultMeta()
670 m_lbm_mgr.loadIntroductionTimes("", m_server, m_game_time);
675 ActiveBlockModifier *abm;
677 std::vector<content_t> required_neighbors;
678 bool check_required_neighbors; // false if required_neighbors is known to be empty
684 ServerEnvironment *m_env;
685 std::vector<std::vector<ActiveABM> *> m_aabms;
687 ABMHandler(std::vector<ABMWithState> &abms,
688 float dtime_s, ServerEnvironment *env,
694 INodeDefManager *ndef = env->getGameDef()->ndef();
695 for (ABMWithState &abmws : abms) {
696 ActiveBlockModifier *abm = abmws.abm;
697 float trigger_interval = abm->getTriggerInterval();
698 if(trigger_interval < 0.001)
699 trigger_interval = 0.001;
700 float actual_interval = dtime_s;
702 abmws.timer += dtime_s;
703 if(abmws.timer < trigger_interval)
705 abmws.timer -= trigger_interval;
706 actual_interval = trigger_interval;
708 float chance = abm->getTriggerChance();
713 if (abm->getSimpleCatchUp()) {
714 float intervals = actual_interval / trigger_interval;
717 aabm.chance = chance / intervals;
721 aabm.chance = chance;
725 const std::vector<std::string> &required_neighbors_s =
726 abm->getRequiredNeighbors();
727 for (const std::string &required_neighbor_s : required_neighbors_s) {
728 ndef->getIds(required_neighbor_s, aabm.required_neighbors);
730 aabm.check_required_neighbors = !required_neighbors_s.empty();
733 const std::vector<std::string> &contents_s = abm->getTriggerContents();
734 for (const std::string &content_s : contents_s) {
735 std::vector<content_t> ids;
736 ndef->getIds(content_s, ids);
737 for (content_t c : ids) {
738 if (c >= m_aabms.size())
739 m_aabms.resize(c + 256, NULL);
741 m_aabms[c] = new std::vector<ActiveABM>;
742 m_aabms[c]->push_back(aabm);
750 for (auto &aabms : m_aabms)
754 // Find out how many objects the given block and its neighbours contain.
755 // Returns the number of objects in the block, and also in 'wider' the
756 // number of objects in the block and all its neighbours. The latter
757 // may an estimate if any neighbours are unloaded.
758 u32 countObjects(MapBlock *block, ServerMap * map, u32 &wider)
761 u32 wider_unknown_count = 0;
762 for(s16 x=-1; x<=1; x++)
763 for(s16 y=-1; y<=1; y++)
764 for(s16 z=-1; z<=1; z++)
766 MapBlock *block2 = map->getBlockNoCreateNoEx(
767 block->getPos() + v3s16(x,y,z));
769 wider_unknown_count++;
772 wider += block2->m_static_objects.m_active.size()
773 + block2->m_static_objects.m_stored.size();
776 u32 active_object_count = block->m_static_objects.m_active.size();
777 u32 wider_known_count = 3*3*3 - wider_unknown_count;
778 wider += wider_unknown_count * wider / wider_known_count;
779 return active_object_count;
782 void apply(MapBlock *block)
784 if(m_aabms.empty() || block->isDummy())
787 ServerMap *map = &m_env->getServerMap();
789 u32 active_object_count_wider;
790 u32 active_object_count = this->countObjects(block, map, active_object_count_wider);
791 m_env->m_added_objects = 0;
794 for(p0.X=0; p0.X<MAP_BLOCKSIZE; p0.X++)
795 for(p0.Y=0; p0.Y<MAP_BLOCKSIZE; p0.Y++)
796 for(p0.Z=0; p0.Z<MAP_BLOCKSIZE; p0.Z++)
798 const MapNode &n = block->getNodeUnsafe(p0);
799 content_t c = n.getContent();
801 if (c >= m_aabms.size() || !m_aabms[c])
804 v3s16 p = p0 + block->getPosRelative();
805 for (ActiveABM &aabm : *m_aabms[c]) {
806 if (myrand() % aabm.chance != 0)
810 if (aabm.check_required_neighbors) {
812 for(p1.X = p0.X-1; p1.X <= p0.X+1; p1.X++)
813 for(p1.Y = p0.Y-1; p1.Y <= p0.Y+1; p1.Y++)
814 for(p1.Z = p0.Z-1; p1.Z <= p0.Z+1; p1.Z++)
819 if (block->isValidPosition(p1)) {
820 // if the neighbor is found on the same map block
821 // get it straight from there
822 const MapNode &n = block->getNodeUnsafe(p1);
825 // otherwise consult the map
826 MapNode n = map->getNodeNoEx(p1 + block->getPosRelative());
829 if (CONTAINS(aabm.required_neighbors, c))
832 // No required neighbor found
837 // Call all the trigger variations
838 aabm.abm->trigger(m_env, p, n);
839 aabm.abm->trigger(m_env, p, n,
840 active_object_count, active_object_count_wider);
842 // Count surrounding objects again if the abms added any
843 if(m_env->m_added_objects > 0) {
844 active_object_count = countObjects(block, map, active_object_count_wider);
845 m_env->m_added_objects = 0;
852 void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime)
854 // Reset usage timer immediately, otherwise a block that becomes active
855 // again at around the same time as it would normally be unloaded will
856 // get unloaded incorrectly. (I think this still leaves a small possibility
857 // of a race condition between this and server::AsyncRunStep, which only
858 // some kind of synchronisation will fix, but it at least reduces the window
859 // of opportunity for it to break from seconds to nanoseconds)
860 block->resetUsageTimer();
862 // Get time difference
864 u32 stamp = block->getTimestamp();
865 if (m_game_time > stamp && stamp != BLOCK_TIMESTAMP_UNDEFINED)
866 dtime_s = m_game_time - stamp;
867 dtime_s += additional_dtime;
869 /*infostream<<"ServerEnvironment::activateBlock(): block timestamp: "
870 <<stamp<<", game time: "<<m_game_time<<std::endl;*/
872 // Remove stored static objects if clearObjects was called since block's timestamp
873 if (stamp == BLOCK_TIMESTAMP_UNDEFINED || stamp < m_last_clear_objects_time) {
874 block->m_static_objects.m_stored.clear();
875 // do not set changed flag to avoid unnecessary mapblock writes
878 // Set current time as timestamp
879 block->setTimestampNoChangedFlag(m_game_time);
881 /*infostream<<"ServerEnvironment::activateBlock(): block is "
882 <<dtime_s<<" seconds old."<<std::endl;*/
884 // Activate stored objects
885 activateObjects(block, dtime_s);
887 /* Handle LoadingBlockModifiers */
888 m_lbm_mgr.applyLBMs(this, block, stamp);
891 std::vector<NodeTimer> elapsed_timers =
892 block->m_node_timers.step((float)dtime_s);
893 if (!elapsed_timers.empty()) {
895 for (const NodeTimer &elapsed_timer : elapsed_timers) {
896 n = block->getNodeNoEx(elapsed_timer.position);
897 v3s16 p = elapsed_timer.position + block->getPosRelative();
898 if (m_script->node_on_timer(p, n, elapsed_timer.elapsed))
899 block->setNodeTimer(NodeTimer(elapsed_timer.timeout, 0,
900 elapsed_timer.position));
905 void ServerEnvironment::addActiveBlockModifier(ActiveBlockModifier *abm)
907 m_abms.emplace_back(abm);
910 void ServerEnvironment::addLoadingBlockModifierDef(LoadingBlockModifierDef *lbm)
912 m_lbm_mgr.addLBMDef(lbm);
915 bool ServerEnvironment::setNode(v3s16 p, const MapNode &n)
917 INodeDefManager *ndef = m_server->ndef();
918 MapNode n_old = m_map->getNodeNoEx(p);
921 if (ndef->get(n_old).has_on_destruct)
922 m_script->node_on_destruct(p, n_old);
925 if (!m_map->addNodeWithEvent(p, n))
928 // Update active VoxelManipulator if a mapgen thread
929 m_map->updateVManip(p);
931 // Call post-destructor
932 if (ndef->get(n_old).has_after_destruct)
933 m_script->node_after_destruct(p, n_old);
936 if (ndef->get(n).has_on_construct)
937 m_script->node_on_construct(p, n);
942 bool ServerEnvironment::removeNode(v3s16 p)
944 INodeDefManager *ndef = m_server->ndef();
945 MapNode n_old = m_map->getNodeNoEx(p);
948 if (ndef->get(n_old).has_on_destruct)
949 m_script->node_on_destruct(p, n_old);
952 // This is slightly optimized compared to addNodeWithEvent(air)
953 if (!m_map->removeNodeWithEvent(p))
956 // Update active VoxelManipulator if a mapgen thread
957 m_map->updateVManip(p);
959 // Call post-destructor
960 if (ndef->get(n_old).has_after_destruct)
961 m_script->node_after_destruct(p, n_old);
963 // Air doesn't require constructor
967 bool ServerEnvironment::swapNode(v3s16 p, const MapNode &n)
969 if (!m_map->addNodeWithEvent(p, n, false))
972 // Update active VoxelManipulator if a mapgen thread
973 m_map->updateVManip(p);
978 void ServerEnvironment::getObjectsInsideRadius(std::vector<u16> &objects, v3f pos,
981 for (auto &activeObject : m_active_objects) {
982 ServerActiveObject* obj = activeObject.second;
983 u16 id = activeObject.first;
984 v3f objectpos = obj->getBasePosition();
985 if (objectpos.getDistanceFrom(pos) > radius)
987 objects.push_back(id);
991 void ServerEnvironment::clearObjects(ClearObjectsMode mode)
993 infostream << "ServerEnvironment::clearObjects(): "
994 << "Removing all active objects" << std::endl;
995 std::vector<u16> objects_to_remove;
996 for (auto &it : m_active_objects) {
998 ServerActiveObject* obj = it.second;
999 if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER)
1002 // Delete static object if block is loaded
1003 deleteStaticFromBlock(obj, id, MOD_REASON_CLEAR_ALL_OBJECTS, true);
1005 // If known by some client, don't delete immediately
1006 if (obj->m_known_by_count > 0) {
1007 obj->m_pending_removal = true;
1011 // Tell the object about removal
1012 obj->removingFromEnvironment();
1013 // Deregister in scripting api
1014 m_script->removeObjectReference(obj);
1016 // Delete active object
1017 if (obj->environmentDeletes())
1019 // Id to be removed from m_active_objects
1020 objects_to_remove.push_back(id);
1023 // Remove references from m_active_objects
1024 for (u16 i : objects_to_remove) {
1025 m_active_objects.erase(i);
1028 // Get list of loaded blocks
1029 std::vector<v3s16> loaded_blocks;
1030 infostream << "ServerEnvironment::clearObjects(): "
1031 << "Listing all loaded blocks" << std::endl;
1032 m_map->listAllLoadedBlocks(loaded_blocks);
1033 infostream << "ServerEnvironment::clearObjects(): "
1034 << "Done listing all loaded blocks: "
1035 << loaded_blocks.size()<<std::endl;
1037 // Get list of loadable blocks
1038 std::vector<v3s16> loadable_blocks;
1039 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1040 infostream << "ServerEnvironment::clearObjects(): "
1041 << "Listing all loadable blocks" << std::endl;
1042 m_map->listAllLoadableBlocks(loadable_blocks);
1043 infostream << "ServerEnvironment::clearObjects(): "
1044 << "Done listing all loadable blocks: "
1045 << loadable_blocks.size() << std::endl;
1047 loadable_blocks = loaded_blocks;
1050 actionstream << "ServerEnvironment::clearObjects(): "
1051 << "Now clearing objects in " << loadable_blocks.size()
1052 << " blocks" << std::endl;
1054 // Grab a reference on each loaded block to avoid unloading it
1055 for (v3s16 p : loaded_blocks) {
1056 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1057 assert(block != NULL);
1061 // Remove objects in all loadable blocks
1062 u32 unload_interval = U32_MAX;
1063 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1064 unload_interval = g_settings->getS32("max_clearobjects_extra_loaded_blocks");
1065 unload_interval = MYMAX(unload_interval, 1);
1067 u32 report_interval = loadable_blocks.size() / 10;
1068 u32 num_blocks_checked = 0;
1069 u32 num_blocks_cleared = 0;
1070 u32 num_objs_cleared = 0;
1071 for (auto i = loadable_blocks.begin();
1072 i != loadable_blocks.end(); ++i) {
1074 MapBlock *block = m_map->emergeBlock(p, false);
1076 errorstream << "ServerEnvironment::clearObjects(): "
1077 << "Failed to emerge block " << PP(p) << std::endl;
1080 u32 num_stored = block->m_static_objects.m_stored.size();
1081 u32 num_active = block->m_static_objects.m_active.size();
1082 if (num_stored != 0 || num_active != 0) {
1083 block->m_static_objects.m_stored.clear();
1084 block->m_static_objects.m_active.clear();
1085 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1086 MOD_REASON_CLEAR_ALL_OBJECTS);
1087 num_objs_cleared += num_stored + num_active;
1088 num_blocks_cleared++;
1090 num_blocks_checked++;
1092 if (report_interval != 0 &&
1093 num_blocks_checked % report_interval == 0) {
1094 float percent = 100.0 * (float)num_blocks_checked /
1095 loadable_blocks.size();
1096 actionstream << "ServerEnvironment::clearObjects(): "
1097 << "Cleared " << num_objs_cleared << " objects"
1098 << " in " << num_blocks_cleared << " blocks ("
1099 << percent << "%)" << std::endl;
1101 if (num_blocks_checked % unload_interval == 0) {
1102 m_map->unloadUnreferencedBlocks();
1105 m_map->unloadUnreferencedBlocks();
1107 // Drop references that were added above
1108 for (v3s16 p : loaded_blocks) {
1109 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1114 m_last_clear_objects_time = m_game_time;
1116 actionstream << "ServerEnvironment::clearObjects(): "
1117 << "Finished: Cleared " << num_objs_cleared << " objects"
1118 << " in " << num_blocks_cleared << " blocks" << std::endl;
1121 void ServerEnvironment::step(float dtime)
1123 /* Step time of day */
1124 stepTimeOfDay(dtime);
1127 // NOTE: This is kind of funny on a singleplayer game, but doesn't
1128 // really matter that much.
1129 static thread_local const float server_step =
1130 g_settings->getFloat("dedicated_server_step");
1131 m_recommended_send_interval = server_step;
1137 m_game_time_fraction_counter += dtime;
1138 u32 inc_i = (u32)m_game_time_fraction_counter;
1139 m_game_time += inc_i;
1140 m_game_time_fraction_counter -= (float)inc_i;
1147 ScopeProfiler sp(g_profiler, "SEnv: handle players avg", SPT_AVG);
1148 for (RemotePlayer *player : m_players) {
1149 // Ignore disconnected players
1150 if (player->getPeerId() == PEER_ID_INEXISTENT)
1154 player->move(dtime, this, 100 * BS);
1159 Manage active block list
1161 if (m_active_blocks_management_interval.step(dtime, m_cache_active_block_mgmt_interval)) {
1162 ScopeProfiler sp(g_profiler, "SEnv: manage act. block list avg per interval", SPT_AVG);
1164 Get player block positions
1166 std::vector<PlayerSAO*> players;
1167 for (RemotePlayer *player: m_players) {
1168 // Ignore disconnected players
1169 if (player->getPeerId() == PEER_ID_INEXISTENT)
1172 PlayerSAO *playersao = player->getPlayerSAO();
1175 players.push_back(playersao);
1179 Update list of active blocks, collecting changes
1181 // use active_object_send_range_blocks since that is max distance
1182 // for active objects sent the client anyway
1183 static thread_local const s16 active_object_range =
1184 g_settings->getS16("active_object_send_range_blocks");
1185 static thread_local const s16 active_block_range =
1186 g_settings->getS16("active_block_range");
1187 std::set<v3s16> blocks_removed;
1188 std::set<v3s16> blocks_added;
1189 m_active_blocks.update(players, active_block_range, active_object_range,
1190 blocks_removed, blocks_added);
1193 Handle removed blocks
1196 // Convert active objects that are no more in active blocks to static
1197 deactivateFarObjects(false);
1199 for (const v3s16 &p: blocks_removed) {
1200 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1204 // Set current time as timestamp (and let it set ChangedFlag)
1205 block->setTimestamp(m_game_time);
1212 for (const v3s16 &p: blocks_added) {
1213 MapBlock *block = m_map->getBlockOrEmerge(p);
1215 m_active_blocks.m_list.erase(p);
1216 m_active_blocks.m_abm_list.erase(p);
1220 activateBlock(block);
1225 Mess around in active blocks
1227 if (m_active_blocks_nodemetadata_interval.step(dtime, m_cache_nodetimer_interval)) {
1228 ScopeProfiler sp(g_profiler, "SEnv: mess in act. blocks avg per interval", SPT_AVG);
1230 float dtime = m_cache_nodetimer_interval;
1232 for (const v3s16 &p: m_active_blocks.m_list) {
1233 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1237 // Reset block usage timer
1238 block->resetUsageTimer();
1240 // Set current time as timestamp
1241 block->setTimestampNoChangedFlag(m_game_time);
1242 // If time has changed much from the one on disk,
1243 // set block to be saved when it is unloaded
1244 if(block->getTimestamp() > block->getDiskTimestamp() + 60)
1245 block->raiseModified(MOD_STATE_WRITE_AT_UNLOAD,
1246 MOD_REASON_BLOCK_EXPIRED);
1249 std::vector<NodeTimer> elapsed_timers = block->m_node_timers.step(dtime);
1250 if (!elapsed_timers.empty()) {
1253 for (const NodeTimer &elapsed_timer: elapsed_timers) {
1254 n = block->getNodeNoEx(elapsed_timer.position);
1255 p2 = elapsed_timer.position + block->getPosRelative();
1256 if (m_script->node_on_timer(p2, n, elapsed_timer.elapsed)) {
1257 block->setNodeTimer(NodeTimer(
1258 elapsed_timer.timeout, 0, elapsed_timer.position));
1265 if (m_active_block_modifier_interval.step(dtime, m_cache_abm_interval))
1267 if (m_active_block_interval_overload_skip > 0) {
1268 ScopeProfiler sp(g_profiler, "SEnv: ABM overload skips");
1269 m_active_block_interval_overload_skip--;
1272 ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg per interval", SPT_AVG);
1273 TimeTaker timer("modify in active blocks per interval");
1275 // Initialize handling of ActiveBlockModifiers
1276 ABMHandler abmhandler(m_abms, m_cache_abm_interval, this, true);
1278 for (const v3s16 &p : m_active_blocks.m_abm_list) {
1279 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1283 // Set current time as timestamp
1284 block->setTimestampNoChangedFlag(m_game_time);
1286 /* Handle ActiveBlockModifiers */
1287 abmhandler.apply(block);
1290 u32 time_ms = timer.stop(true);
1291 u32 max_time_ms = 200;
1292 if (time_ms > max_time_ms) {
1293 warningstream<<"active block modifiers took "
1294 <<time_ms<<"ms (longer than "
1295 <<max_time_ms<<"ms)"<<std::endl;
1296 m_active_block_interval_overload_skip = (time_ms / max_time_ms) + 1;
1301 Step script environment (run global on_step())
1303 m_script->environment_Step(dtime);
1309 ScopeProfiler sp(g_profiler, "SEnv: step act. objs avg", SPT_AVG);
1310 //TimeTaker timer("Step active objects");
1312 g_profiler->avg("SEnv: num of objects", m_active_objects.size());
1314 // This helps the objects to send data at the same time
1315 bool send_recommended = false;
1316 m_send_recommended_timer += dtime;
1317 if(m_send_recommended_timer > getSendRecommendedInterval())
1319 m_send_recommended_timer -= getSendRecommendedInterval();
1320 send_recommended = true;
1323 for (auto &ao_it : m_active_objects) {
1324 ServerActiveObject* obj = ao_it.second;
1329 obj->step(dtime, send_recommended);
1330 // Read messages from object
1331 while (!obj->m_messages_out.empty()) {
1332 m_active_object_messages.push(obj->m_messages_out.front());
1333 obj->m_messages_out.pop();
1339 Manage active objects
1341 if (m_object_management_interval.step(dtime, 0.5)) {
1342 ScopeProfiler sp(g_profiler, "SEnv: remove removed objs avg /.5s", SPT_AVG);
1343 removeRemovedObjects();
1347 Manage particle spawner expiration
1349 if (m_particle_management_interval.step(dtime, 1.0)) {
1350 for (std::unordered_map<u32, float>::iterator i = m_particle_spawners.begin();
1351 i != m_particle_spawners.end(); ) {
1352 //non expiring spawners
1353 if (i->second == PARTICLE_SPAWNER_NO_EXPIRY) {
1359 if (i->second <= 0.f)
1360 m_particle_spawners.erase(i++);
1367 u32 ServerEnvironment::addParticleSpawner(float exptime)
1369 // Timers with lifetime 0 do not expire
1370 float time = exptime > 0.f ? exptime : PARTICLE_SPAWNER_NO_EXPIRY;
1373 for (;;) { // look for unused particlespawner id
1375 std::unordered_map<u32, float>::iterator f = m_particle_spawners.find(id);
1376 if (f == m_particle_spawners.end()) {
1377 m_particle_spawners[id] = time;
1384 u32 ServerEnvironment::addParticleSpawner(float exptime, u16 attached_id)
1386 u32 id = addParticleSpawner(exptime);
1387 m_particle_spawner_attachments[id] = attached_id;
1388 if (ServerActiveObject *obj = getActiveObject(attached_id)) {
1389 obj->attachParticleSpawner(id);
1394 void ServerEnvironment::deleteParticleSpawner(u32 id, bool remove_from_object)
1396 m_particle_spawners.erase(id);
1397 const auto &it = m_particle_spawner_attachments.find(id);
1398 if (it != m_particle_spawner_attachments.end()) {
1399 u16 obj_id = it->second;
1400 ServerActiveObject *sao = getActiveObject(obj_id);
1401 if (sao != NULL && remove_from_object) {
1402 sao->detachParticleSpawner(id);
1404 m_particle_spawner_attachments.erase(id);
1408 ServerActiveObject* ServerEnvironment::getActiveObject(u16 id)
1410 ServerActiveObjectMap::const_iterator n = m_active_objects.find(id);
1411 return (n != m_active_objects.end() ? n->second : NULL);
1414 bool isFreeServerActiveObjectId(u16 id, ServerActiveObjectMap &objects)
1419 return objects.find(id) == objects.end();
1422 u16 getFreeServerActiveObjectId(ServerActiveObjectMap &objects)
1424 //try to reuse id's as late as possible
1425 static u16 last_used_id = 0;
1426 u16 startid = last_used_id;
1430 if(isFreeServerActiveObjectId(last_used_id, objects))
1431 return last_used_id;
1433 if(last_used_id == startid)
1438 u16 ServerEnvironment::addActiveObject(ServerActiveObject *object)
1440 assert(object); // Pre-condition
1442 u16 id = addActiveObjectRaw(object, true, 0);
1447 Finds out what new objects have been added to
1448 inside a radius around a position
1450 void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius,
1452 std::set<u16> ¤t_objects,
1453 std::queue<u16> &added_objects)
1455 f32 radius_f = radius * BS;
1456 f32 player_radius_f = player_radius * BS;
1458 if (player_radius_f < 0)
1459 player_radius_f = 0;
1461 Go through the object list,
1462 - discard removed/deactivated objects,
1463 - discard objects that are too far away,
1464 - discard objects that are found in current_objects.
1465 - add remaining objects to added_objects
1467 for (auto &ao_it : m_active_objects) {
1468 u16 id = ao_it.first;
1471 ServerActiveObject *object = ao_it.second;
1475 if (object->isGone())
1478 f32 distance_f = object->getBasePosition().
1479 getDistanceFrom(playersao->getBasePosition());
1480 if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1481 // Discard if too far
1482 if (distance_f > player_radius_f && player_radius_f != 0)
1484 } else if (distance_f > radius_f)
1487 // Discard if already on current_objects
1488 std::set<u16>::iterator n;
1489 n = current_objects.find(id);
1490 if(n != current_objects.end())
1492 // Add to added_objects
1493 added_objects.push(id);
1498 Finds out what objects have been removed from
1499 inside a radius around a position
1501 void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius,
1503 std::set<u16> ¤t_objects,
1504 std::queue<u16> &removed_objects)
1506 f32 radius_f = radius * BS;
1507 f32 player_radius_f = player_radius * BS;
1509 if (player_radius_f < 0)
1510 player_radius_f = 0;
1512 Go through current_objects; object is removed if:
1513 - object is not found in m_active_objects (this is actually an
1514 error condition; objects should be removed only after all clients
1515 have been informed about removal), or
1516 - object is to be removed or deactivated, or
1517 - object is too far away
1519 for (u16 id : current_objects) {
1520 ServerActiveObject *object = getActiveObject(id);
1522 if (object == NULL) {
1523 infostream << "ServerEnvironment::getRemovedActiveObjects():"
1524 << " object in current_objects is NULL" << std::endl;
1525 removed_objects.push(id);
1529 if (object->isGone()) {
1530 removed_objects.push(id);
1534 f32 distance_f = object->getBasePosition().getDistanceFrom(playersao->getBasePosition());
1535 if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1536 if (distance_f <= player_radius_f || player_radius_f == 0)
1538 } else if (distance_f <= radius_f)
1541 // Object is no longer visible
1542 removed_objects.push(id);
1546 void ServerEnvironment::setStaticForActiveObjectsInBlock(
1547 v3s16 blockpos, bool static_exists, v3s16 static_block)
1549 MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos);
1553 for (auto &so_it : block->m_static_objects.m_active) {
1554 // Get the ServerActiveObject counterpart to this StaticObject
1555 ServerActiveObjectMap::const_iterator ao_it = m_active_objects.find(so_it.first);
1556 if (ao_it == m_active_objects.end()) {
1557 // If this ever happens, there must be some kind of nasty bug.
1558 errorstream << "ServerEnvironment::setStaticForObjectsInBlock(): "
1559 "Object from MapBlock::m_static_objects::m_active not found "
1560 "in m_active_objects";
1564 ServerActiveObject *sao = ao_it->second;
1565 sao->m_static_exists = static_exists;
1566 sao->m_static_block = static_block;
1570 ActiveObjectMessage ServerEnvironment::getActiveObjectMessage()
1572 if(m_active_object_messages.empty())
1573 return ActiveObjectMessage(0);
1575 ActiveObjectMessage message = m_active_object_messages.front();
1576 m_active_object_messages.pop();
1580 void ServerEnvironment::getSelectedActiveObjects(
1581 const core::line3d<f32> &shootline_on_map,
1582 std::vector<PointedThing> &objects)
1584 std::vector<u16> objectIds;
1585 getObjectsInsideRadius(objectIds, shootline_on_map.start,
1586 shootline_on_map.getLength() + 10.0f);
1587 const v3f line_vector = shootline_on_map.getVector();
1589 for (u16 objectId : objectIds) {
1590 ServerActiveObject* obj = getActiveObject(objectId);
1592 aabb3f selection_box;
1593 if (!obj->getSelectionBox(&selection_box))
1596 v3f pos = obj->getBasePosition();
1598 aabb3f offsetted_box(selection_box.MinEdge + pos,
1599 selection_box.MaxEdge + pos);
1601 v3f current_intersection;
1602 v3s16 current_normal;
1603 if (boxLineCollision(offsetted_box, shootline_on_map.start, line_vector,
1604 ¤t_intersection, ¤t_normal)) {
1605 objects.emplace_back(
1606 (s16) objectId, current_intersection, current_normal,
1607 (current_intersection - shootline_on_map.start).getLengthSQ());
1613 ************ Private methods *************
1616 u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object,
1617 bool set_changed, u32 dtime_s)
1619 assert(object); // Pre-condition
1620 if(object->getId() == 0){
1621 u16 new_id = getFreeServerActiveObjectId(m_active_objects);
1624 errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
1625 <<"no free ids available"<<std::endl;
1626 if(object->environmentDeletes())
1630 object->setId(new_id);
1633 verbosestream<<"ServerEnvironment::addActiveObjectRaw(): "
1634 <<"supplied with id "<<object->getId()<<std::endl;
1637 if(!isFreeServerActiveObjectId(object->getId(), m_active_objects)) {
1638 errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
1639 <<"id is not free ("<<object->getId()<<")"<<std::endl;
1640 if(object->environmentDeletes())
1645 if (objectpos_over_limit(object->getBasePosition())) {
1646 v3f p = object->getBasePosition();
1647 warningstream << "ServerEnvironment::addActiveObjectRaw(): "
1648 << "object position (" << p.X << "," << p.Y << "," << p.Z
1649 << ") outside maximum range" << std::endl;
1650 if (object->environmentDeletes())
1655 /*infostream<<"ServerEnvironment::addActiveObjectRaw(): "
1656 <<"added (id="<<object->getId()<<")"<<std::endl;*/
1658 m_active_objects[object->getId()] = object;
1660 verbosestream<<"ServerEnvironment::addActiveObjectRaw(): "
1661 <<"Added id="<<object->getId()<<"; there are now "
1662 <<m_active_objects.size()<<" active objects."
1665 // Register reference in scripting api (must be done before post-init)
1666 m_script->addObjectReference(object);
1667 // Post-initialize object
1668 object->addedToEnvironment(dtime_s);
1670 // Add static data to block
1671 if(object->isStaticAllowed())
1673 // Add static object to active static list of the block
1674 v3f objectpos = object->getBasePosition();
1675 std::string staticdata;
1676 object->getStaticData(&staticdata);
1677 StaticObject s_obj(object->getType(), objectpos, staticdata);
1678 // Add to the block where the object is located in
1679 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
1680 MapBlock *block = m_map->emergeBlock(blockpos);
1682 block->m_static_objects.m_active[object->getId()] = s_obj;
1683 object->m_static_exists = true;
1684 object->m_static_block = blockpos;
1687 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1688 MOD_REASON_ADD_ACTIVE_OBJECT_RAW);
1690 v3s16 p = floatToInt(objectpos, BS);
1691 errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
1692 <<"could not emerge block for storing id="<<object->getId()
1693 <<" statically (pos="<<PP(p)<<")"<<std::endl;
1697 return object->getId();
1701 Remove objects that satisfy (isGone() && m_known_by_count==0)
1703 void ServerEnvironment::removeRemovedObjects()
1705 std::vector<u16> objects_to_remove;
1706 for (auto &ao_it : m_active_objects) {
1707 u16 id = ao_it.first;
1708 ServerActiveObject* obj = ao_it.second;
1710 // This shouldn't happen but check it
1712 errorstream << "ServerEnvironment::removeRemovedObjects(): "
1713 << "NULL object found. id=" << id << std::endl;
1714 objects_to_remove.push_back(id);
1719 We will handle objects marked for removal or deactivation
1725 Delete static data from block if removed
1727 if (obj->m_pending_removal)
1728 deleteStaticFromBlock(obj, id, MOD_REASON_REMOVE_OBJECTS_REMOVE, false);
1730 // If still known by clients, don't actually remove. On some future
1731 // invocation this will be 0, which is when removal will continue.
1732 if(obj->m_known_by_count > 0)
1736 Move static data from active to stored if deactivated
1738 if (!obj->m_pending_removal && obj->m_static_exists) {
1739 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
1741 std::map<u16, StaticObject>::iterator i =
1742 block->m_static_objects.m_active.find(id);
1743 if (i != block->m_static_objects.m_active.end()) {
1744 block->m_static_objects.m_stored.push_back(i->second);
1745 block->m_static_objects.m_active.erase(id);
1746 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1747 MOD_REASON_REMOVE_OBJECTS_DEACTIVATE);
1749 warningstream << "ServerEnvironment::removeRemovedObjects(): "
1750 << "id=" << id << " m_static_exists=true but "
1751 << "static data doesn't actually exist in "
1752 << PP(obj->m_static_block) << std::endl;
1755 infostream << "Failed to emerge block from which an object to "
1756 << "be deactivated was loaded from. id=" << id << std::endl;
1760 // Tell the object about removal
1761 obj->removingFromEnvironment();
1762 // Deregister in scripting api
1763 m_script->removeObjectReference(obj);
1766 if(obj->environmentDeletes())
1769 objects_to_remove.push_back(id);
1771 // Remove references from m_active_objects
1772 for (u16 i : objects_to_remove) {
1773 m_active_objects.erase(i);
1777 static void print_hexdump(std::ostream &o, const std::string &data)
1779 const int linelength = 16;
1780 for(int l=0; ; l++){
1781 int i0 = linelength * l;
1782 bool at_end = false;
1783 int thislinelength = linelength;
1784 if(i0 + thislinelength > (int)data.size()){
1785 thislinelength = data.size() - i0;
1788 for(int di=0; di<linelength; di++){
1791 if(di<thislinelength)
1792 snprintf(buf, 4, "%.2x ", data[i]);
1794 snprintf(buf, 4, " ");
1798 for(int di=0; di<thislinelength; di++){
1812 Convert stored objects from blocks near the players to active.
1814 void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s)
1819 // Ignore if no stored objects (to not set changed flag)
1820 if(block->m_static_objects.m_stored.empty())
1823 verbosestream<<"ServerEnvironment::activateObjects(): "
1824 <<"activating objects of block "<<PP(block->getPos())
1825 <<" ("<<block->m_static_objects.m_stored.size()
1826 <<" objects)"<<std::endl;
1827 bool large_amount = (block->m_static_objects.m_stored.size() > g_settings->getU16("max_objects_per_block"));
1829 errorstream<<"suspiciously large amount of objects detected: "
1830 <<block->m_static_objects.m_stored.size()<<" in "
1831 <<PP(block->getPos())
1832 <<"; removing all of them."<<std::endl;
1833 // Clear stored list
1834 block->m_static_objects.m_stored.clear();
1835 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1836 MOD_REASON_TOO_MANY_OBJECTS);
1840 // Activate stored objects
1841 std::vector<StaticObject> new_stored;
1842 for (const StaticObject &s_obj : block->m_static_objects.m_stored) {
1843 // Create an active object from the data
1844 ServerActiveObject *obj = ServerActiveObject::create
1845 ((ActiveObjectType) s_obj.type, this, 0, s_obj.pos, s_obj.data);
1846 // If couldn't create object, store static data back.
1848 errorstream<<"ServerEnvironment::activateObjects(): "
1849 <<"failed to create active object from static object "
1850 <<"in block "<<PP(s_obj.pos/BS)
1851 <<" type="<<(int)s_obj.type<<" data:"<<std::endl;
1852 print_hexdump(verbosestream, s_obj.data);
1854 new_stored.push_back(s_obj);
1857 verbosestream<<"ServerEnvironment::activateObjects(): "
1858 <<"activated static object pos="<<PP(s_obj.pos/BS)
1859 <<" type="<<(int)s_obj.type<<std::endl;
1860 // This will also add the object to the active static list
1861 addActiveObjectRaw(obj, false, dtime_s);
1864 // Clear stored list
1865 block->m_static_objects.m_stored.clear();
1866 // Add leftover failed stuff to stored list
1867 for (const StaticObject &s_obj : new_stored) {
1868 block->m_static_objects.m_stored.push_back(s_obj);
1872 Note: Block hasn't really been modified here.
1873 The objects have just been activated and moved from the stored
1874 static list to the active static list.
1875 As such, the block is essentially the same.
1876 Thus, do not call block->raiseModified(MOD_STATE_WRITE_NEEDED).
1877 Otherwise there would be a huge amount of unnecessary I/O.
1882 Convert objects that are not standing inside active blocks to static.
1884 If m_known_by_count != 0, active object is not deleted, but static
1885 data is still updated.
1887 If force_delete is set, active object is deleted nevertheless. It
1888 shall only be set so in the destructor of the environment.
1890 If block wasn't generated (not in memory or on disk),
1892 void ServerEnvironment::deactivateFarObjects(bool _force_delete)
1894 std::vector<u16> objects_to_remove;
1895 for (auto &ao_it : m_active_objects) {
1896 // force_delete might be overriden per object
1897 bool force_delete = _force_delete;
1899 ServerActiveObject* obj = ao_it.second;
1902 // Do not deactivate if static data creation not allowed
1903 if(!force_delete && !obj->isStaticAllowed())
1906 // removeRemovedObjects() is responsible for these
1907 if(!force_delete && obj->isGone())
1910 u16 id = ao_it.first;
1911 v3f objectpos = obj->getBasePosition();
1913 // The block in which the object resides in
1914 v3s16 blockpos_o = getNodeBlockPos(floatToInt(objectpos, BS));
1916 // If object's static data is stored in a deactivated block and object
1917 // is actually located in an active block, re-save to the block in
1918 // which the object is actually located in.
1920 obj->m_static_exists &&
1921 !m_active_blocks.contains(obj->m_static_block) &&
1922 m_active_blocks.contains(blockpos_o))
1924 // Delete from block where object was located
1925 deleteStaticFromBlock(obj, id, MOD_REASON_STATIC_DATA_REMOVED, false);
1927 std::string staticdata_new;
1928 obj->getStaticData(&staticdata_new);
1929 StaticObject s_obj(obj->getType(), objectpos, staticdata_new);
1930 // Save to block where object is located
1931 saveStaticToBlock(blockpos_o, id, obj, s_obj, MOD_REASON_STATIC_DATA_ADDED);
1936 // If block is still active, don't remove
1937 if(!force_delete && m_active_blocks.contains(blockpos_o))
1940 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
1941 << "deactivating object id=" << id << " on inactive block "
1942 << PP(blockpos_o) << std::endl;
1944 // If known by some client, don't immediately delete.
1945 bool pending_delete = (obj->m_known_by_count > 0 && !force_delete);
1948 Update the static data
1950 if(obj->isStaticAllowed())
1952 // Create new static object
1953 std::string staticdata_new;
1954 obj->getStaticData(&staticdata_new);
1955 StaticObject s_obj(obj->getType(), objectpos, staticdata_new);
1957 bool stays_in_same_block = false;
1958 bool data_changed = true;
1960 // Check if static data has changed considerably
1961 if (obj->m_static_exists) {
1962 if (obj->m_static_block == blockpos_o)
1963 stays_in_same_block = true;
1965 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
1968 std::map<u16, StaticObject>::iterator n =
1969 block->m_static_objects.m_active.find(id);
1970 if (n != block->m_static_objects.m_active.end()) {
1971 StaticObject static_old = n->second;
1973 float save_movem = obj->getMinimumSavedMovement();
1975 if (static_old.data == staticdata_new &&
1976 (static_old.pos - objectpos).getLength() < save_movem)
1977 data_changed = false;
1979 warningstream << "ServerEnvironment::deactivateFarObjects(): "
1980 << "id=" << id << " m_static_exists=true but "
1981 << "static data doesn't actually exist in "
1982 << PP(obj->m_static_block) << std::endl;
1988 While changes are always saved, blocks are only marked as modified
1989 if the object has moved or different staticdata. (see above)
1991 bool shall_be_written = (!stays_in_same_block || data_changed);
1992 u32 reason = shall_be_written ? MOD_REASON_STATIC_DATA_CHANGED : MOD_REASON_UNKNOWN;
1994 // Delete old static object
1995 deleteStaticFromBlock(obj, id, reason, false);
1997 // Add to the block where the object is located in
1998 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
1999 u16 store_id = pending_delete ? id : 0;
2000 if (!saveStaticToBlock(blockpos, store_id, obj, s_obj, reason))
2001 force_delete = true;
2005 If known by some client, set pending deactivation.
2006 Otherwise delete it immediately.
2008 if(pending_delete && !force_delete)
2010 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
2011 << "object id=" << id << " is known by clients"
2012 << "; not deleting yet" << std::endl;
2014 obj->m_pending_deactivation = true;
2017 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
2018 << "object id=" << id << " is not known by clients"
2019 << "; deleting" << std::endl;
2021 // Tell the object about removal
2022 obj->removingFromEnvironment();
2023 // Deregister in scripting api
2024 m_script->removeObjectReference(obj);
2026 // Delete active object
2027 if(obj->environmentDeletes())
2029 // Id to be removed from m_active_objects
2030 objects_to_remove.push_back(id);
2033 // Remove references from m_active_objects
2034 for (u16 i : objects_to_remove) {
2035 m_active_objects.erase(i);
2039 void ServerEnvironment::deleteStaticFromBlock(
2040 ServerActiveObject *obj, u16 id, u32 mod_reason, bool no_emerge)
2042 if (!obj->m_static_exists)
2047 block = m_map->getBlockNoCreateNoEx(obj->m_static_block);
2049 block = m_map->emergeBlock(obj->m_static_block, false);
2052 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2053 << " when deleting static data of object from it. id=" << id << std::endl;
2057 block->m_static_objects.remove(id);
2058 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2059 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2061 obj->m_static_exists = false;
2064 bool ServerEnvironment::saveStaticToBlock(
2065 v3s16 blockpos, u16 store_id,
2066 ServerActiveObject *obj, const StaticObject &s_obj,
2069 MapBlock *block = nullptr;
2071 block = m_map->emergeBlock(blockpos);
2072 } catch (InvalidPositionException &e) {
2073 // Handled via NULL pointer
2074 // NOTE: emergeBlock's failure is usually determined by it
2075 // actually returning NULL
2079 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2080 << " when saving static data of object to it. id=" << store_id << std::endl;
2083 if (block->m_static_objects.m_stored.size() >= g_settings->getU16("max_objects_per_block")) {
2084 warningstream << "ServerEnv: Trying to store id = " << store_id
2085 << " statically but block " << PP(blockpos)
2086 << " already contains "
2087 << block->m_static_objects.m_stored.size()
2088 << " objects." << std::endl;
2092 block->m_static_objects.insert(store_id, s_obj);
2093 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2094 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2096 obj->m_static_exists = true;
2097 obj->m_static_block = blockpos;
2102 PlayerDatabase *ServerEnvironment::openPlayerDatabase(const std::string &name,
2103 const std::string &savedir, const Settings &conf)
2106 if (name == "sqlite3")
2107 return new PlayerDatabaseSQLite3(savedir);
2109 if (name == "dummy")
2110 return new Database_Dummy();
2112 if (name == "postgresql") {
2113 std::string connect_string;
2114 conf.getNoEx("pgsql_player_connection", connect_string);
2115 return new PlayerDatabasePostgreSQL(connect_string);
2118 if (name == "files")
2119 return new PlayerDatabaseFiles(savedir + DIR_DELIM + "players");
2121 throw BaseException(std::string("Database backend ") + name + " not supported.");
2124 bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params,
2125 const Settings &cmd_args)
2127 std::string migrate_to = cmd_args.get("migrate-players");
2129 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2130 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2131 errorstream << "Cannot read world.mt!" << std::endl;
2135 if (!world_mt.exists("player_backend")) {
2136 errorstream << "Please specify your current backend in world.mt:"
2138 << " player_backend = {files|sqlite3|postgresql}"
2143 std::string backend = world_mt.get("player_backend");
2144 if (backend == migrate_to) {
2145 errorstream << "Cannot migrate: new backend is same"
2146 << " as the old one" << std::endl;
2150 const std::string players_backup_path = game_params.world_path + DIR_DELIM
2153 if (backend == "files") {
2154 // Create backup directory
2155 fs::CreateDir(players_backup_path);
2159 PlayerDatabase *srcdb = ServerEnvironment::openPlayerDatabase(backend,
2160 game_params.world_path, world_mt);
2161 PlayerDatabase *dstdb = ServerEnvironment::openPlayerDatabase(migrate_to,
2162 game_params.world_path, world_mt);
2164 std::vector<std::string> player_list;
2165 srcdb->listPlayers(player_list);
2166 for (std::vector<std::string>::const_iterator it = player_list.begin();
2167 it != player_list.end(); ++it) {
2168 actionstream << "Migrating player " << it->c_str() << std::endl;
2169 RemotePlayer player(it->c_str(), NULL);
2170 PlayerSAO playerSAO(NULL, &player, 15000, false);
2172 srcdb->loadPlayer(&player, &playerSAO);
2174 playerSAO.finalize(&player, std::set<std::string>());
2175 player.setPlayerSAO(&playerSAO);
2177 dstdb->savePlayer(&player);
2179 // For files source, move player files to backup dir
2180 if (backend == "files") {
2182 game_params.world_path + DIR_DELIM + "players" + DIR_DELIM + (*it),
2183 players_backup_path + DIR_DELIM + (*it));
2187 actionstream << "Successfully migrated " << player_list.size() << " players"
2189 world_mt.set("player_backend", migrate_to);
2190 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2191 errorstream << "Failed to update world.mt!" << std::endl;
2193 actionstream << "world.mt updated" << std::endl;
2195 // When migration is finished from file backend, remove players directory if empty
2196 if (backend == "files") {
2197 fs::DeleteSingleFileOrEmptyDirectory(game_params.world_path + DIR_DELIM
2204 } catch (BaseException &e) {
2205 errorstream << "An error occured during migration: " << e.what() << std::endl;