nlist: make selected list accessible globally
[waspsaliva.git] / src / serverenvironment.cpp
blobd044b003db9c2c7456ac94601e63b8ae96047476
1 /*
2 Minetest
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 <algorithm>
21 #include "serverenvironment.h"
22 #include "settings.h"
23 #include "log.h"
24 #include "mapblock.h"
25 #include "nodedef.h"
26 #include "nodemetadata.h"
27 #include "gamedef.h"
28 #include "map.h"
29 #include "porting.h"
30 #include "profiler.h"
31 #include "raycast.h"
32 #include "remoteplayer.h"
33 #include "scripting_server.h"
34 #include "server.h"
35 #include "util/serialize.h"
36 #include "util/basic_macros.h"
37 #include "util/pointedthing.h"
38 #include "threading/mutex_auto_lock.h"
39 #include "filesys.h"
40 #include "gameparams.h"
41 #include "database/database-dummy.h"
42 #include "database/database-files.h"
43 #include "database/database-sqlite3.h"
44 #if USE_POSTGRESQL
45 #include "database/database-postgresql.h"
46 #endif
47 #if USE_LEVELDB
48 #include "database/database-leveldb.h"
49 #endif
50 #include "server/luaentity_sao.h"
51 #include "server/player_sao.h"
53 #define LBM_NAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_:"
55 // A number that is much smaller than the timeout for particle spawners should/could ever be
56 #define PARTICLE_SPAWNER_NO_EXPIRY -1024.f
59 ABMWithState
62 ABMWithState::ABMWithState(ActiveBlockModifier *abm_):
63 abm(abm_)
65 // Initialize timer to random value to spread processing
66 float itv = abm->getTriggerInterval();
67 itv = MYMAX(0.001, itv); // No less than 1ms
68 int minval = MYMAX(-0.51*itv, -60); // Clamp to
69 int maxval = MYMIN(0.51*itv, 60); // +-60 seconds
70 timer = myrand_range(minval, maxval);
74 LBMManager
77 void LBMContentMapping::deleteContents()
79 for (auto &it : lbm_list) {
80 delete it;
84 void LBMContentMapping::addLBM(LoadingBlockModifierDef *lbm_def, IGameDef *gamedef)
86 // Add the lbm_def to the LBMContentMapping.
87 // Unknown names get added to the global NameIdMapping.
88 const NodeDefManager *nodedef = gamedef->ndef();
90 lbm_list.push_back(lbm_def);
92 for (const std::string &nodeTrigger: lbm_def->trigger_contents) {
93 std::vector<content_t> c_ids;
94 bool found = nodedef->getIds(nodeTrigger, c_ids);
95 if (!found) {
96 content_t c_id = gamedef->allocateUnknownNodeId(nodeTrigger);
97 if (c_id == CONTENT_IGNORE) {
98 // Seems it can't be allocated.
99 warningstream << "Could not internalize node name \"" << nodeTrigger
100 << "\" while loading LBM \"" << lbm_def->name << "\"." << std::endl;
101 continue;
103 c_ids.push_back(c_id);
106 for (content_t c_id : c_ids) {
107 map[c_id].push_back(lbm_def);
112 const std::vector<LoadingBlockModifierDef *> *
113 LBMContentMapping::lookup(content_t c) const
115 lbm_map::const_iterator it = map.find(c);
116 if (it == map.end())
117 return NULL;
118 // This first dereferences the iterator, returning
119 // a std::vector<LoadingBlockModifierDef *>
120 // reference, then we convert it to a pointer.
121 return &(it->second);
124 LBMManager::~LBMManager()
126 for (auto &m_lbm_def : m_lbm_defs) {
127 delete m_lbm_def.second;
130 for (auto &it : m_lbm_lookup) {
131 (it.second).deleteContents();
135 void LBMManager::addLBMDef(LoadingBlockModifierDef *lbm_def)
137 // Precondition, in query mode the map isn't used anymore
138 FATAL_ERROR_IF(m_query_mode,
139 "attempted to modify LBMManager in query mode");
141 if (!string_allowed(lbm_def->name, LBM_NAME_ALLOWED_CHARS)) {
142 throw ModError("Error adding LBM \"" + lbm_def->name +
143 "\": Does not follow naming conventions: "
144 "Only characters [a-z0-9_:] are allowed.");
147 m_lbm_defs[lbm_def->name] = lbm_def;
150 void LBMManager::loadIntroductionTimes(const std::string &times,
151 IGameDef *gamedef, u32 now)
153 m_query_mode = true;
155 // name -> time map.
156 // Storing it in a map first instead of
157 // handling the stuff directly in the loop
158 // removes all duplicate entries.
159 // TODO make this std::unordered_map
160 std::map<std::string, u32> introduction_times;
163 The introduction times string consists of name~time entries,
164 with each entry terminated by a semicolon. The time is decimal.
167 size_t idx = 0;
168 size_t idx_new;
169 while ((idx_new = times.find(';', idx)) != std::string::npos) {
170 std::string entry = times.substr(idx, idx_new - idx);
171 std::vector<std::string> components = str_split(entry, '~');
172 if (components.size() != 2)
173 throw SerializationError("Introduction times entry \""
174 + entry + "\" requires exactly one '~'!");
175 const std::string &name = components[0];
176 u32 time = from_string<u32>(components[1]);
177 introduction_times[name] = time;
178 idx = idx_new + 1;
181 // Put stuff from introduction_times into m_lbm_lookup
182 for (std::map<std::string, u32>::const_iterator it = introduction_times.begin();
183 it != introduction_times.end(); ++it) {
184 const std::string &name = it->first;
185 u32 time = it->second;
187 std::map<std::string, LoadingBlockModifierDef *>::iterator def_it =
188 m_lbm_defs.find(name);
189 if (def_it == m_lbm_defs.end()) {
190 // This seems to be an LBM entry for
191 // an LBM we haven't loaded. Discard it.
192 continue;
194 LoadingBlockModifierDef *lbm_def = def_it->second;
195 if (lbm_def->run_at_every_load) {
196 // This seems to be an LBM entry for
197 // an LBM that runs at every load.
198 // Don't add it just yet.
199 continue;
202 m_lbm_lookup[time].addLBM(lbm_def, gamedef);
204 // Erase the entry so that we know later
205 // what elements didn't get put into m_lbm_lookup
206 m_lbm_defs.erase(name);
209 // Now also add the elements from m_lbm_defs to m_lbm_lookup
210 // that weren't added in the previous step.
211 // They are introduced first time to this world,
212 // or are run at every load (introducement time hardcoded to U32_MAX).
214 LBMContentMapping &lbms_we_introduce_now = m_lbm_lookup[now];
215 LBMContentMapping &lbms_running_always = m_lbm_lookup[U32_MAX];
217 for (auto &m_lbm_def : m_lbm_defs) {
218 if (m_lbm_def.second->run_at_every_load) {
219 lbms_running_always.addLBM(m_lbm_def.second, gamedef);
220 } else {
221 lbms_we_introduce_now.addLBM(m_lbm_def.second, gamedef);
225 // Clear the list, so that we don't delete remaining elements
226 // twice in the destructor
227 m_lbm_defs.clear();
230 std::string LBMManager::createIntroductionTimesString()
232 // Precondition, we must be in query mode
233 FATAL_ERROR_IF(!m_query_mode,
234 "attempted to query on non fully set up LBMManager");
236 std::ostringstream oss;
237 for (const auto &it : m_lbm_lookup) {
238 u32 time = it.first;
239 const std::vector<LoadingBlockModifierDef *> &lbm_list = it.second.lbm_list;
240 for (const auto &lbm_def : lbm_list) {
241 // Don't add if the LBM runs at every load,
242 // then introducement time is hardcoded
243 // and doesn't need to be stored
244 if (lbm_def->run_at_every_load)
245 continue;
246 oss << lbm_def->name << "~" << time << ";";
249 return oss.str();
252 void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, u32 stamp)
254 // Precondition, we need m_lbm_lookup to be initialized
255 FATAL_ERROR_IF(!m_query_mode,
256 "attempted to query on non fully set up LBMManager");
257 v3s16 pos_of_block = block->getPosRelative();
258 v3s16 pos;
259 MapNode n;
260 content_t c;
261 lbm_lookup_map::const_iterator it = getLBMsIntroducedAfter(stamp);
262 for (; it != m_lbm_lookup.end(); ++it) {
263 // Cache previous version to speedup lookup which has a very high performance
264 // penalty on each call
265 content_t previous_c{};
266 std::vector<LoadingBlockModifierDef *> *lbm_list = nullptr;
268 for (pos.X = 0; pos.X < MAP_BLOCKSIZE; pos.X++)
269 for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++)
270 for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++) {
271 n = block->getNodeNoEx(pos);
272 c = n.getContent();
274 // If content_t are not matching perform an LBM lookup
275 if (previous_c != c) {
276 lbm_list = (std::vector<LoadingBlockModifierDef *> *)
277 it->second.lookup(c);
278 previous_c = c;
281 if (!lbm_list)
282 continue;
283 for (auto lbmdef : *lbm_list) {
284 lbmdef->trigger(env, pos + pos_of_block, n);
291 ActiveBlockList
294 void fillRadiusBlock(v3s16 p0, s16 r, std::set<v3s16> &list)
296 v3s16 p;
297 for(p.X=p0.X-r; p.X<=p0.X+r; p.X++)
298 for(p.Y=p0.Y-r; p.Y<=p0.Y+r; p.Y++)
299 for(p.Z=p0.Z-r; p.Z<=p0.Z+r; p.Z++)
301 // limit to a sphere
302 if (p.getDistanceFrom(p0) <= r) {
303 // Set in list
304 list.insert(p);
309 void fillViewConeBlock(v3s16 p0,
310 const s16 r,
311 const v3f camera_pos,
312 const v3f camera_dir,
313 const float camera_fov,
314 std::set<v3s16> &list)
316 v3s16 p;
317 const s16 r_nodes = r * BS * MAP_BLOCKSIZE;
318 for (p.X = p0.X - r; p.X <= p0.X+r; p.X++)
319 for (p.Y = p0.Y - r; p.Y <= p0.Y+r; p.Y++)
320 for (p.Z = p0.Z - r; p.Z <= p0.Z+r; p.Z++) {
321 if (isBlockInSight(p, camera_pos, camera_dir, camera_fov, r_nodes)) {
322 list.insert(p);
327 void ActiveBlockList::update(std::vector<PlayerSAO*> &active_players,
328 s16 active_block_range,
329 s16 active_object_range,
330 std::set<v3s16> &blocks_removed,
331 std::set<v3s16> &blocks_added)
334 Create the new list
336 std::set<v3s16> newlist = m_forceloaded_list;
337 m_abm_list = m_forceloaded_list;
338 for (const PlayerSAO *playersao : active_players) {
339 v3s16 pos = getNodeBlockPos(floatToInt(playersao->getBasePosition(), BS));
340 fillRadiusBlock(pos, active_block_range, m_abm_list);
341 fillRadiusBlock(pos, active_block_range, newlist);
343 s16 player_ao_range = std::min(active_object_range, playersao->getWantedRange());
344 // only do this if this would add blocks
345 if (player_ao_range > active_block_range) {
346 v3f camera_dir = v3f(0,0,1);
347 camera_dir.rotateYZBy(playersao->getLookPitch());
348 camera_dir.rotateXZBy(playersao->getRotation().Y);
349 fillViewConeBlock(pos,
350 player_ao_range,
351 playersao->getEyePosition(),
352 camera_dir,
353 playersao->getFov(),
354 newlist);
359 Find out which blocks on the old list are not on the new list
361 // Go through old list
362 for (v3s16 p : m_list) {
363 // If not on new list, it's been removed
364 if (newlist.find(p) == newlist.end())
365 blocks_removed.insert(p);
369 Find out which blocks on the new list are not on the old list
371 // Go through new list
372 for (v3s16 p : newlist) {
373 // If not on old list, it's been added
374 if(m_list.find(p) == m_list.end())
375 blocks_added.insert(p);
379 Update m_list
381 m_list.clear();
382 for (v3s16 p : newlist) {
383 m_list.insert(p);
388 ServerEnvironment
391 // Random device to seed pseudo random generators.
392 static std::random_device seed;
394 ServerEnvironment::ServerEnvironment(ServerMap *map,
395 ServerScripting *scriptIface, Server *server,
396 const std::string &path_world):
397 Environment(server),
398 m_map(map),
399 m_script(scriptIface),
400 m_server(server),
401 m_path_world(path_world),
402 m_rgen(seed())
404 // Determine which database backend to use
405 std::string conf_path = path_world + DIR_DELIM + "world.mt";
406 Settings conf;
408 std::string player_backend_name = "sqlite3";
409 std::string auth_backend_name = "sqlite3";
411 bool succeeded = conf.readConfigFile(conf_path.c_str());
413 // If we open world.mt read the backend configurations.
414 if (succeeded) {
415 // Read those values before setting defaults
416 bool player_backend_exists = conf.exists("player_backend");
417 bool auth_backend_exists = conf.exists("auth_backend");
419 // player backend is not set, assume it's legacy file backend.
420 if (!player_backend_exists) {
421 // fall back to files
422 conf.set("player_backend", "files");
423 player_backend_name = "files";
425 if (!conf.updateConfigFile(conf_path.c_str())) {
426 errorstream << "ServerEnvironment::ServerEnvironment(): "
427 << "Failed to update world.mt!" << std::endl;
429 } else {
430 conf.getNoEx("player_backend", player_backend_name);
433 // auth backend is not set, assume it's legacy file backend.
434 if (!auth_backend_exists) {
435 conf.set("auth_backend", "files");
436 auth_backend_name = "files";
438 if (!conf.updateConfigFile(conf_path.c_str())) {
439 errorstream << "ServerEnvironment::ServerEnvironment(): "
440 << "Failed to update world.mt!" << std::endl;
442 } else {
443 conf.getNoEx("auth_backend", auth_backend_name);
447 if (player_backend_name == "files") {
448 warningstream << "/!\\ You are using old player file backend. "
449 << "This backend is deprecated and will be removed in a future release /!\\"
450 << std::endl << "Switching to SQLite3 or PostgreSQL is advised, "
451 << "please read http://wiki.minetest.net/Database_backends." << std::endl;
454 if (auth_backend_name == "files") {
455 warningstream << "/!\\ You are using old auth file backend. "
456 << "This backend is deprecated and will be removed in a future release /!\\"
457 << std::endl << "Switching to SQLite3 is advised, "
458 << "please read http://wiki.minetest.net/Database_backends." << std::endl;
461 m_player_database = openPlayerDatabase(player_backend_name, path_world, conf);
462 m_auth_database = openAuthDatabase(auth_backend_name, path_world, conf);
465 ServerEnvironment::~ServerEnvironment()
467 // Clear active block list.
468 // This makes the next one delete all active objects.
469 m_active_blocks.clear();
471 // Convert all objects to static and delete the active objects
472 deactivateFarObjects(true);
474 // Drop/delete map
475 m_map->drop();
477 // Delete ActiveBlockModifiers
478 for (ABMWithState &m_abm : m_abms) {
479 delete m_abm.abm;
482 // Deallocate players
483 for (RemotePlayer *m_player : m_players) {
484 delete m_player;
487 delete m_player_database;
488 delete m_auth_database;
491 Map & ServerEnvironment::getMap()
493 return *m_map;
496 ServerMap & ServerEnvironment::getServerMap()
498 return *m_map;
501 RemotePlayer *ServerEnvironment::getPlayer(const session_t peer_id)
503 for (RemotePlayer *player : m_players) {
504 if (player->getPeerId() == peer_id)
505 return player;
507 return NULL;
510 RemotePlayer *ServerEnvironment::getPlayer(const char* name)
512 for (RemotePlayer *player : m_players) {
513 if (strcmp(player->getName(), name) == 0)
514 return player;
516 return NULL;
519 void ServerEnvironment::addPlayer(RemotePlayer *player)
522 Check that peer_ids are unique.
523 Also check that names are unique.
524 Exception: there can be multiple players with peer_id=0
526 // If peer id is non-zero, it has to be unique.
527 if (player->getPeerId() != PEER_ID_INEXISTENT)
528 FATAL_ERROR_IF(getPlayer(player->getPeerId()) != NULL, "Peer id not unique");
529 // Name has to be unique.
530 FATAL_ERROR_IF(getPlayer(player->getName()) != NULL, "Player name not unique");
531 // Add.
532 m_players.push_back(player);
535 void ServerEnvironment::removePlayer(RemotePlayer *player)
537 for (std::vector<RemotePlayer *>::iterator it = m_players.begin();
538 it != m_players.end(); ++it) {
539 if ((*it) == player) {
540 delete *it;
541 m_players.erase(it);
542 return;
547 bool ServerEnvironment::removePlayerFromDatabase(const std::string &name)
549 return m_player_database->removePlayer(name);
552 void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason,
553 const std::string &str_reason, bool reconnect)
555 for (RemotePlayer *player : m_players) {
556 m_server->DenyAccessVerCompliant(player->getPeerId(),
557 player->protocol_version, reason, str_reason, reconnect);
561 void ServerEnvironment::saveLoadedPlayers(bool force)
563 for (RemotePlayer *player : m_players) {
564 if (force || player->checkModified() || (player->getPlayerSAO() &&
565 player->getPlayerSAO()->getMeta().isModified())) {
566 try {
567 m_player_database->savePlayer(player);
568 } catch (DatabaseException &e) {
569 errorstream << "Failed to save player " << player->getName() << " exception: "
570 << e.what() << std::endl;
571 throw;
577 void ServerEnvironment::savePlayer(RemotePlayer *player)
579 try {
580 m_player_database->savePlayer(player);
581 } catch (DatabaseException &e) {
582 errorstream << "Failed to save player " << player->getName() << " exception: "
583 << e.what() << std::endl;
584 throw;
588 PlayerSAO *ServerEnvironment::loadPlayer(RemotePlayer *player, bool *new_player,
589 session_t peer_id, bool is_singleplayer)
591 PlayerSAO *playersao = new PlayerSAO(this, player, peer_id, is_singleplayer);
592 // Create player if it doesn't exist
593 if (!m_player_database->loadPlayer(player, playersao)) {
594 *new_player = true;
595 // Set player position
596 infostream << "Server: Finding spawn place for player \""
597 << player->getName() << "\"" << std::endl;
598 playersao->setBasePosition(m_server->findSpawnPos());
600 // Make sure the player is saved
601 player->setModified(true);
602 } else {
603 // If the player exists, ensure that they respawn inside legal bounds
604 // This fixes an assert crash when the player can't be added
605 // to the environment
606 if (objectpos_over_limit(playersao->getBasePosition())) {
607 actionstream << "Respawn position for player \""
608 << player->getName() << "\" outside limits, resetting" << std::endl;
609 playersao->setBasePosition(m_server->findSpawnPos());
613 // Add player to environment
614 addPlayer(player);
616 /* Clean up old HUD elements from previous sessions */
617 player->clearHud();
619 /* Add object to environment */
620 addActiveObject(playersao);
622 return playersao;
625 void ServerEnvironment::saveMeta()
627 if (!m_meta_loaded)
628 return;
630 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
632 // Open file and serialize
633 std::ostringstream ss(std::ios_base::binary);
635 Settings args;
636 args.setU64("game_time", m_game_time);
637 args.setU64("time_of_day", getTimeOfDay());
638 args.setU64("last_clear_objects_time", m_last_clear_objects_time);
639 args.setU64("lbm_introduction_times_version", 1);
640 args.set("lbm_introduction_times",
641 m_lbm_mgr.createIntroductionTimesString());
642 args.setU64("day_count", m_day_count);
643 args.writeLines(ss);
644 ss<<"EnvArgsEnd\n";
646 if(!fs::safeWriteToFile(path, ss.str()))
648 infostream<<"ServerEnvironment::saveMeta(): Failed to write "
649 <<path<<std::endl;
650 throw SerializationError("Couldn't save env meta");
654 void ServerEnvironment::loadMeta()
656 SANITY_CHECK(!m_meta_loaded);
657 m_meta_loaded = true;
659 // If file doesn't exist, load default environment metadata
660 if (!fs::PathExists(m_path_world + DIR_DELIM "env_meta.txt")) {
661 infostream << "ServerEnvironment: Loading default environment metadata"
662 << std::endl;
663 loadDefaultMeta();
664 return;
667 infostream << "ServerEnvironment: Loading environment metadata" << std::endl;
669 std::string path = m_path_world + DIR_DELIM "env_meta.txt";
671 // Open file and deserialize
672 std::ifstream is(path.c_str(), std::ios_base::binary);
673 if (!is.good()) {
674 infostream << "ServerEnvironment::loadMeta(): Failed to open "
675 << path << std::endl;
676 throw SerializationError("Couldn't load env meta");
679 Settings args;
681 if (!args.parseConfigLines(is, "EnvArgsEnd")) {
682 throw SerializationError("ServerEnvironment::loadMeta(): "
683 "EnvArgsEnd not found!");
686 try {
687 m_game_time = args.getU64("game_time");
688 } catch (SettingNotFoundException &e) {
689 // Getting this is crucial, otherwise timestamps are useless
690 throw SerializationError("Couldn't load env meta game_time");
693 setTimeOfDay(args.exists("time_of_day") ?
694 // set day to early morning by default
695 args.getU64("time_of_day") : 5250);
697 m_last_clear_objects_time = args.exists("last_clear_objects_time") ?
698 // If missing, do as if clearObjects was never called
699 args.getU64("last_clear_objects_time") : 0;
701 std::string lbm_introduction_times;
702 try {
703 u64 ver = args.getU64("lbm_introduction_times_version");
704 if (ver == 1) {
705 lbm_introduction_times = args.get("lbm_introduction_times");
706 } else {
707 infostream << "ServerEnvironment::loadMeta(): Non-supported"
708 << " introduction time version " << ver << std::endl;
710 } catch (SettingNotFoundException &e) {
711 // No problem, this is expected. Just continue with an empty string
713 m_lbm_mgr.loadIntroductionTimes(lbm_introduction_times, m_server, m_game_time);
715 m_day_count = args.exists("day_count") ?
716 args.getU64("day_count") : 0;
720 * called if env_meta.txt doesn't exist (e.g. new world)
722 void ServerEnvironment::loadDefaultMeta()
724 m_lbm_mgr.loadIntroductionTimes("", m_server, m_game_time);
727 struct ActiveABM
729 ActiveBlockModifier *abm;
730 int chance;
731 std::vector<content_t> required_neighbors;
732 bool check_required_neighbors; // false if required_neighbors is known to be empty
735 class ABMHandler
737 private:
738 ServerEnvironment *m_env;
739 std::vector<std::vector<ActiveABM> *> m_aabms;
740 public:
741 ABMHandler(std::vector<ABMWithState> &abms,
742 float dtime_s, ServerEnvironment *env,
743 bool use_timers):
744 m_env(env)
746 if(dtime_s < 0.001)
747 return;
748 const NodeDefManager *ndef = env->getGameDef()->ndef();
749 for (ABMWithState &abmws : abms) {
750 ActiveBlockModifier *abm = abmws.abm;
751 float trigger_interval = abm->getTriggerInterval();
752 if(trigger_interval < 0.001)
753 trigger_interval = 0.001;
754 float actual_interval = dtime_s;
755 if(use_timers){
756 abmws.timer += dtime_s;
757 if(abmws.timer < trigger_interval)
758 continue;
759 abmws.timer -= trigger_interval;
760 actual_interval = trigger_interval;
762 float chance = abm->getTriggerChance();
763 if(chance == 0)
764 chance = 1;
765 ActiveABM aabm;
766 aabm.abm = abm;
767 if (abm->getSimpleCatchUp()) {
768 float intervals = actual_interval / trigger_interval;
769 if(intervals == 0)
770 continue;
771 aabm.chance = chance / intervals;
772 if(aabm.chance == 0)
773 aabm.chance = 1;
774 } else {
775 aabm.chance = chance;
778 // Trigger neighbors
779 const std::vector<std::string> &required_neighbors_s =
780 abm->getRequiredNeighbors();
781 for (const std::string &required_neighbor_s : required_neighbors_s) {
782 ndef->getIds(required_neighbor_s, aabm.required_neighbors);
784 aabm.check_required_neighbors = !required_neighbors_s.empty();
786 // Trigger contents
787 const std::vector<std::string> &contents_s = abm->getTriggerContents();
788 for (const std::string &content_s : contents_s) {
789 std::vector<content_t> ids;
790 ndef->getIds(content_s, ids);
791 for (content_t c : ids) {
792 if (c >= m_aabms.size())
793 m_aabms.resize(c + 256, NULL);
794 if (!m_aabms[c])
795 m_aabms[c] = new std::vector<ActiveABM>;
796 m_aabms[c]->push_back(aabm);
802 ~ABMHandler()
804 for (auto &aabms : m_aabms)
805 delete aabms;
808 // Find out how many objects the given block and its neighbours contain.
809 // Returns the number of objects in the block, and also in 'wider' the
810 // number of objects in the block and all its neighbours. The latter
811 // may an estimate if any neighbours are unloaded.
812 u32 countObjects(MapBlock *block, ServerMap * map, u32 &wider)
814 wider = 0;
815 u32 wider_unknown_count = 0;
816 for(s16 x=-1; x<=1; x++)
817 for(s16 y=-1; y<=1; y++)
818 for(s16 z=-1; z<=1; z++)
820 MapBlock *block2 = map->getBlockNoCreateNoEx(
821 block->getPos() + v3s16(x,y,z));
822 if(block2==NULL){
823 wider_unknown_count++;
824 continue;
826 wider += block2->m_static_objects.m_active.size()
827 + block2->m_static_objects.m_stored.size();
829 // Extrapolate
830 u32 active_object_count = block->m_static_objects.m_active.size();
831 u32 wider_known_count = 3*3*3 - wider_unknown_count;
832 wider += wider_unknown_count * wider / wider_known_count;
833 return active_object_count;
836 void apply(MapBlock *block, int &blocks_scanned, int &abms_run, int &blocks_cached)
838 if(m_aabms.empty() || block->isDummy())
839 return;
841 // Check the content type cache first
842 // to see whether there are any ABMs
843 // to be run at all for this block.
844 if (block->contents_cached) {
845 blocks_cached++;
846 bool run_abms = false;
847 for (content_t c : block->contents) {
848 if (c < m_aabms.size() && m_aabms[c]) {
849 run_abms = true;
850 break;
853 if (!run_abms)
854 return;
855 } else {
856 // Clear any caching
857 block->contents.clear();
859 blocks_scanned++;
861 ServerMap *map = &m_env->getServerMap();
863 u32 active_object_count_wider;
864 u32 active_object_count = this->countObjects(block, map, active_object_count_wider);
865 m_env->m_added_objects = 0;
867 v3s16 p0;
868 for(p0.X=0; p0.X<MAP_BLOCKSIZE; p0.X++)
869 for(p0.Y=0; p0.Y<MAP_BLOCKSIZE; p0.Y++)
870 for(p0.Z=0; p0.Z<MAP_BLOCKSIZE; p0.Z++)
872 const MapNode &n = block->getNodeUnsafe(p0);
873 content_t c = n.getContent();
874 // Cache content types as we go
875 if (!block->contents_cached && !block->do_not_cache_contents) {
876 block->contents.insert(c);
877 if (block->contents.size() > 64) {
878 // Too many different nodes... don't try to cache
879 block->do_not_cache_contents = true;
880 block->contents.clear();
884 if (c >= m_aabms.size() || !m_aabms[c])
885 continue;
887 v3s16 p = p0 + block->getPosRelative();
888 for (ActiveABM &aabm : *m_aabms[c]) {
889 if (myrand() % aabm.chance != 0)
890 continue;
892 // Check neighbors
893 if (aabm.check_required_neighbors) {
894 v3s16 p1;
895 for(p1.X = p0.X-1; p1.X <= p0.X+1; p1.X++)
896 for(p1.Y = p0.Y-1; p1.Y <= p0.Y+1; p1.Y++)
897 for(p1.Z = p0.Z-1; p1.Z <= p0.Z+1; p1.Z++)
899 if(p1 == p0)
900 continue;
901 content_t c;
902 if (block->isValidPosition(p1)) {
903 // if the neighbor is found on the same map block
904 // get it straight from there
905 const MapNode &n = block->getNodeUnsafe(p1);
906 c = n.getContent();
907 } else {
908 // otherwise consult the map
909 MapNode n = map->getNode(p1 + block->getPosRelative());
910 c = n.getContent();
912 if (CONTAINS(aabm.required_neighbors, c))
913 goto neighbor_found;
915 // No required neighbor found
916 continue;
918 neighbor_found:
920 abms_run++;
921 // Call all the trigger variations
922 aabm.abm->trigger(m_env, p, n);
923 aabm.abm->trigger(m_env, p, n,
924 active_object_count, active_object_count_wider);
926 // Count surrounding objects again if the abms added any
927 if(m_env->m_added_objects > 0) {
928 active_object_count = countObjects(block, map, active_object_count_wider);
929 m_env->m_added_objects = 0;
933 block->contents_cached = !block->do_not_cache_contents;
937 void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime)
939 // Reset usage timer immediately, otherwise a block that becomes active
940 // again at around the same time as it would normally be unloaded will
941 // get unloaded incorrectly. (I think this still leaves a small possibility
942 // of a race condition between this and server::AsyncRunStep, which only
943 // some kind of synchronisation will fix, but it at least reduces the window
944 // of opportunity for it to break from seconds to nanoseconds)
945 block->resetUsageTimer();
947 // Get time difference
948 u32 dtime_s = 0;
949 u32 stamp = block->getTimestamp();
950 if (m_game_time > stamp && stamp != BLOCK_TIMESTAMP_UNDEFINED)
951 dtime_s = m_game_time - stamp;
952 dtime_s += additional_dtime;
954 /*infostream<<"ServerEnvironment::activateBlock(): block timestamp: "
955 <<stamp<<", game time: "<<m_game_time<<std::endl;*/
957 // Remove stored static objects if clearObjects was called since block's timestamp
958 if (stamp == BLOCK_TIMESTAMP_UNDEFINED || stamp < m_last_clear_objects_time) {
959 block->m_static_objects.m_stored.clear();
960 // do not set changed flag to avoid unnecessary mapblock writes
963 // Set current time as timestamp
964 block->setTimestampNoChangedFlag(m_game_time);
966 /*infostream<<"ServerEnvironment::activateBlock(): block is "
967 <<dtime_s<<" seconds old."<<std::endl;*/
969 // Activate stored objects
970 activateObjects(block, dtime_s);
972 /* Handle LoadingBlockModifiers */
973 m_lbm_mgr.applyLBMs(this, block, stamp);
975 // Run node timers
976 std::vector<NodeTimer> elapsed_timers =
977 block->m_node_timers.step((float)dtime_s);
978 if (!elapsed_timers.empty()) {
979 MapNode n;
980 for (const NodeTimer &elapsed_timer : elapsed_timers) {
981 n = block->getNodeNoEx(elapsed_timer.position);
982 v3s16 p = elapsed_timer.position + block->getPosRelative();
983 if (m_script->node_on_timer(p, n, elapsed_timer.elapsed))
984 block->setNodeTimer(NodeTimer(elapsed_timer.timeout, 0,
985 elapsed_timer.position));
990 void ServerEnvironment::addActiveBlockModifier(ActiveBlockModifier *abm)
992 m_abms.emplace_back(abm);
995 void ServerEnvironment::addLoadingBlockModifierDef(LoadingBlockModifierDef *lbm)
997 m_lbm_mgr.addLBMDef(lbm);
1000 bool ServerEnvironment::setNode(v3s16 p, const MapNode &n)
1002 const NodeDefManager *ndef = m_server->ndef();
1003 MapNode n_old = m_map->getNode(p);
1005 const ContentFeatures &cf_old = ndef->get(n_old);
1007 // Call destructor
1008 if (cf_old.has_on_destruct)
1009 m_script->node_on_destruct(p, n_old);
1011 // Replace node
1012 if (!m_map->addNodeWithEvent(p, n))
1013 return false;
1015 // Update active VoxelManipulator if a mapgen thread
1016 m_map->updateVManip(p);
1018 // Call post-destructor
1019 if (cf_old.has_after_destruct)
1020 m_script->node_after_destruct(p, n_old);
1022 // Retrieve node content features
1023 // if new node is same as old, reuse old definition to prevent a lookup
1024 const ContentFeatures &cf_new = n_old == n ? cf_old : ndef->get(n);
1026 // Call constructor
1027 if (cf_new.has_on_construct)
1028 m_script->node_on_construct(p, n);
1030 return true;
1033 bool ServerEnvironment::removeNode(v3s16 p)
1035 const NodeDefManager *ndef = m_server->ndef();
1036 MapNode n_old = m_map->getNode(p);
1038 // Call destructor
1039 if (ndef->get(n_old).has_on_destruct)
1040 m_script->node_on_destruct(p, n_old);
1042 // Replace with air
1043 // This is slightly optimized compared to addNodeWithEvent(air)
1044 if (!m_map->removeNodeWithEvent(p))
1045 return false;
1047 // Update active VoxelManipulator if a mapgen thread
1048 m_map->updateVManip(p);
1050 // Call post-destructor
1051 if (ndef->get(n_old).has_after_destruct)
1052 m_script->node_after_destruct(p, n_old);
1054 // Air doesn't require constructor
1055 return true;
1058 bool ServerEnvironment::swapNode(v3s16 p, const MapNode &n)
1060 if (!m_map->addNodeWithEvent(p, n, false))
1061 return false;
1063 // Update active VoxelManipulator if a mapgen thread
1064 m_map->updateVManip(p);
1066 return true;
1069 u8 ServerEnvironment::findSunlight(v3s16 pos) const
1071 // Directions for neighbouring nodes with specified order
1072 static const v3s16 dirs[] = {
1073 v3s16(-1, 0, 0), v3s16(1, 0, 0), v3s16(0, 0, -1), v3s16(0, 0, 1),
1074 v3s16(0, -1, 0), v3s16(0, 1, 0)
1077 const NodeDefManager *ndef = m_server->ndef();
1079 // found_light remembers the highest known sunlight value at pos
1080 u8 found_light = 0;
1082 struct stack_entry {
1083 v3s16 pos;
1084 s16 dist;
1086 std::stack<stack_entry> stack;
1087 stack.push({pos, 0});
1089 std::unordered_map<s64, s8> dists;
1090 dists[MapDatabase::getBlockAsInteger(pos)] = 0;
1092 while (!stack.empty()) {
1093 struct stack_entry e = stack.top();
1094 stack.pop();
1096 v3s16 currentPos = e.pos;
1097 s8 dist = e.dist + 1;
1099 for (const v3s16& off : dirs) {
1100 v3s16 neighborPos = currentPos + off;
1101 s64 neighborHash = MapDatabase::getBlockAsInteger(neighborPos);
1103 // Do not walk neighborPos multiple times unless the distance to the start
1104 // position is shorter
1105 auto it = dists.find(neighborHash);
1106 if (it != dists.end() && dist >= it->second)
1107 continue;
1109 // Position to walk
1110 bool is_position_ok;
1111 MapNode node = m_map->getNode(neighborPos, &is_position_ok);
1112 if (!is_position_ok) {
1113 // This happens very rarely because the map at currentPos is loaded
1114 m_map->emergeBlock(neighborPos, false);
1115 node = m_map->getNode(neighborPos, &is_position_ok);
1116 if (!is_position_ok)
1117 continue; // not generated
1120 const ContentFeatures &def = ndef->get(node);
1121 if (!def.sunlight_propagates) {
1122 // Do not test propagation here again
1123 dists[neighborHash] = -1;
1124 continue;
1127 // Sunlight could have come from here
1128 dists[neighborHash] = dist;
1129 u8 daylight = node.param1 & 0x0f;
1131 // In the special case where sunlight shines from above and thus
1132 // does not decrease with upwards distance, daylight is always
1133 // bigger than nightlight, which never reaches 15
1134 int possible_finlight = daylight - dist;
1135 if (possible_finlight <= found_light) {
1136 // Light from here cannot make a brighter light at currentPos than
1137 // found_light
1138 continue;
1141 u8 nightlight = node.param1 >> 4;
1142 if (daylight > nightlight) {
1143 // Found a valid daylight
1144 found_light = possible_finlight;
1145 } else {
1146 // Sunlight may be darker, so walk the neighbours
1147 stack.push({neighborPos, dist});
1151 return found_light;
1154 void ServerEnvironment::clearObjects(ClearObjectsMode mode)
1156 infostream << "ServerEnvironment::clearObjects(): "
1157 << "Removing all active objects" << std::endl;
1158 auto cb_removal = [this] (ServerActiveObject *obj, u16 id) {
1159 if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER)
1160 return false;
1162 // Delete static object if block is loaded
1163 deleteStaticFromBlock(obj, id, MOD_REASON_CLEAR_ALL_OBJECTS, true);
1165 // If known by some client, don't delete immediately
1166 if (obj->m_known_by_count > 0) {
1167 obj->m_pending_removal = true;
1168 return false;
1171 // Tell the object about removal
1172 obj->removingFromEnvironment();
1173 // Deregister in scripting api
1174 m_script->removeObjectReference(obj);
1176 // Delete active object
1177 if (obj->environmentDeletes())
1178 delete obj;
1180 return true;
1183 m_ao_manager.clear(cb_removal);
1185 // Get list of loaded blocks
1186 std::vector<v3s16> loaded_blocks;
1187 infostream << "ServerEnvironment::clearObjects(): "
1188 << "Listing all loaded blocks" << std::endl;
1189 m_map->listAllLoadedBlocks(loaded_blocks);
1190 infostream << "ServerEnvironment::clearObjects(): "
1191 << "Done listing all loaded blocks: "
1192 << loaded_blocks.size()<<std::endl;
1194 // Get list of loadable blocks
1195 std::vector<v3s16> loadable_blocks;
1196 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1197 infostream << "ServerEnvironment::clearObjects(): "
1198 << "Listing all loadable blocks" << std::endl;
1199 m_map->listAllLoadableBlocks(loadable_blocks);
1200 infostream << "ServerEnvironment::clearObjects(): "
1201 << "Done listing all loadable blocks: "
1202 << loadable_blocks.size() << std::endl;
1203 } else {
1204 loadable_blocks = loaded_blocks;
1207 actionstream << "ServerEnvironment::clearObjects(): "
1208 << "Now clearing objects in " << loadable_blocks.size()
1209 << " blocks" << std::endl;
1211 // Grab a reference on each loaded block to avoid unloading it
1212 for (v3s16 p : loaded_blocks) {
1213 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1214 assert(block != NULL);
1215 block->refGrab();
1218 // Remove objects in all loadable blocks
1219 u32 unload_interval = U32_MAX;
1220 if (mode == CLEAR_OBJECTS_MODE_FULL) {
1221 unload_interval = g_settings->getS32("max_clearobjects_extra_loaded_blocks");
1222 unload_interval = MYMAX(unload_interval, 1);
1224 u32 report_interval = loadable_blocks.size() / 10;
1225 u32 num_blocks_checked = 0;
1226 u32 num_blocks_cleared = 0;
1227 u32 num_objs_cleared = 0;
1228 for (auto i = loadable_blocks.begin();
1229 i != loadable_blocks.end(); ++i) {
1230 v3s16 p = *i;
1231 MapBlock *block = m_map->emergeBlock(p, false);
1232 if (!block) {
1233 errorstream << "ServerEnvironment::clearObjects(): "
1234 << "Failed to emerge block " << PP(p) << std::endl;
1235 continue;
1237 u32 num_stored = block->m_static_objects.m_stored.size();
1238 u32 num_active = block->m_static_objects.m_active.size();
1239 if (num_stored != 0 || num_active != 0) {
1240 block->m_static_objects.m_stored.clear();
1241 block->m_static_objects.m_active.clear();
1242 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1243 MOD_REASON_CLEAR_ALL_OBJECTS);
1244 num_objs_cleared += num_stored + num_active;
1245 num_blocks_cleared++;
1247 num_blocks_checked++;
1249 if (report_interval != 0 &&
1250 num_blocks_checked % report_interval == 0) {
1251 float percent = 100.0 * (float)num_blocks_checked /
1252 loadable_blocks.size();
1253 actionstream << "ServerEnvironment::clearObjects(): "
1254 << "Cleared " << num_objs_cleared << " objects"
1255 << " in " << num_blocks_cleared << " blocks ("
1256 << percent << "%)" << std::endl;
1258 if (num_blocks_checked % unload_interval == 0) {
1259 m_map->unloadUnreferencedBlocks();
1262 m_map->unloadUnreferencedBlocks();
1264 // Drop references that were added above
1265 for (v3s16 p : loaded_blocks) {
1266 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1267 assert(block);
1268 block->refDrop();
1271 m_last_clear_objects_time = m_game_time;
1273 actionstream << "ServerEnvironment::clearObjects(): "
1274 << "Finished: Cleared " << num_objs_cleared << " objects"
1275 << " in " << num_blocks_cleared << " blocks" << std::endl;
1278 void ServerEnvironment::step(float dtime)
1280 ScopeProfiler sp2(g_profiler, "ServerEnv::step()", SPT_AVG);
1281 /* Step time of day */
1282 stepTimeOfDay(dtime);
1284 // Update this one
1285 // NOTE: This is kind of funny on a singleplayer game, but doesn't
1286 // really matter that much.
1287 static thread_local const float server_step =
1288 g_settings->getFloat("dedicated_server_step");
1289 m_recommended_send_interval = server_step;
1292 Increment game time
1295 m_game_time_fraction_counter += dtime;
1296 u32 inc_i = (u32)m_game_time_fraction_counter;
1297 m_game_time += inc_i;
1298 m_game_time_fraction_counter -= (float)inc_i;
1302 Handle players
1305 ScopeProfiler sp(g_profiler, "ServerEnv: move players", SPT_AVG);
1306 for (RemotePlayer *player : m_players) {
1307 // Ignore disconnected players
1308 if (player->getPeerId() == PEER_ID_INEXISTENT)
1309 continue;
1311 // Move
1312 player->move(dtime, this, 100 * BS);
1317 Manage active block list
1319 if (m_active_blocks_management_interval.step(dtime, m_cache_active_block_mgmt_interval)) {
1320 ScopeProfiler sp(g_profiler, "ServerEnv: update active blocks", SPT_AVG);
1322 Get player block positions
1324 std::vector<PlayerSAO*> players;
1325 for (RemotePlayer *player: m_players) {
1326 // Ignore disconnected players
1327 if (player->getPeerId() == PEER_ID_INEXISTENT)
1328 continue;
1330 PlayerSAO *playersao = player->getPlayerSAO();
1331 assert(playersao);
1333 players.push_back(playersao);
1337 Update list of active blocks, collecting changes
1339 // use active_object_send_range_blocks since that is max distance
1340 // for active objects sent the client anyway
1341 static thread_local const s16 active_object_range =
1342 g_settings->getS16("active_object_send_range_blocks");
1343 static thread_local const s16 active_block_range =
1344 g_settings->getS16("active_block_range");
1345 std::set<v3s16> blocks_removed;
1346 std::set<v3s16> blocks_added;
1347 m_active_blocks.update(players, active_block_range, active_object_range,
1348 blocks_removed, blocks_added);
1351 Handle removed blocks
1354 // Convert active objects that are no more in active blocks to static
1355 deactivateFarObjects(false);
1357 for (const v3s16 &p: blocks_removed) {
1358 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1359 if (!block)
1360 continue;
1362 // Set current time as timestamp (and let it set ChangedFlag)
1363 block->setTimestamp(m_game_time);
1367 Handle added blocks
1370 for (const v3s16 &p: blocks_added) {
1371 MapBlock *block = m_map->getBlockOrEmerge(p);
1372 if (!block) {
1373 m_active_blocks.m_list.erase(p);
1374 m_active_blocks.m_abm_list.erase(p);
1375 continue;
1378 activateBlock(block);
1383 Mess around in active blocks
1385 if (m_active_blocks_nodemetadata_interval.step(dtime, m_cache_nodetimer_interval)) {
1386 ScopeProfiler sp(g_profiler, "ServerEnv: Run node timers", SPT_AVG);
1388 float dtime = m_cache_nodetimer_interval;
1390 for (const v3s16 &p: m_active_blocks.m_list) {
1391 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1392 if (!block)
1393 continue;
1395 // Reset block usage timer
1396 block->resetUsageTimer();
1398 // Set current time as timestamp
1399 block->setTimestampNoChangedFlag(m_game_time);
1400 // If time has changed much from the one on disk,
1401 // set block to be saved when it is unloaded
1402 if(block->getTimestamp() > block->getDiskTimestamp() + 60)
1403 block->raiseModified(MOD_STATE_WRITE_AT_UNLOAD,
1404 MOD_REASON_BLOCK_EXPIRED);
1406 // Run node timers
1407 std::vector<NodeTimer> elapsed_timers = block->m_node_timers.step(dtime);
1408 if (!elapsed_timers.empty()) {
1409 MapNode n;
1410 v3s16 p2;
1411 for (const NodeTimer &elapsed_timer: elapsed_timers) {
1412 n = block->getNodeNoEx(elapsed_timer.position);
1413 p2 = elapsed_timer.position + block->getPosRelative();
1414 if (m_script->node_on_timer(p2, n, elapsed_timer.elapsed)) {
1415 block->setNodeTimer(NodeTimer(
1416 elapsed_timer.timeout, 0, elapsed_timer.position));
1423 if (m_active_block_modifier_interval.step(dtime, m_cache_abm_interval)) {
1424 ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg per interval", SPT_AVG);
1425 TimeTaker timer("modify in active blocks per interval");
1427 // Initialize handling of ActiveBlockModifiers
1428 ABMHandler abmhandler(m_abms, m_cache_abm_interval, this, true);
1430 int blocks_scanned = 0;
1431 int abms_run = 0;
1432 int blocks_cached = 0;
1434 std::vector<v3s16> output(m_active_blocks.m_abm_list.size());
1436 // Shuffle the active blocks so that each block gets an equal chance
1437 // of having its ABMs run.
1438 std::copy(m_active_blocks.m_abm_list.begin(), m_active_blocks.m_abm_list.end(), output.begin());
1439 std::shuffle(output.begin(), output.end(), m_rgen);
1441 int i = 0;
1442 // determine the time budget for ABMs
1443 u32 max_time_ms = m_cache_abm_interval * 1000 * m_cache_abm_time_budget;
1444 for (const v3s16 &p : output) {
1445 MapBlock *block = m_map->getBlockNoCreateNoEx(p);
1446 if (!block)
1447 continue;
1449 i++;
1451 // Set current time as timestamp
1452 block->setTimestampNoChangedFlag(m_game_time);
1454 /* Handle ActiveBlockModifiers */
1455 abmhandler.apply(block, blocks_scanned, abms_run, blocks_cached);
1457 u32 time_ms = timer.getTimerTime();
1459 if (time_ms > max_time_ms) {
1460 warningstream << "active block modifiers took "
1461 << time_ms << "ms (processed " << i << " of "
1462 << output.size() << " active blocks)" << std::endl;
1463 break;
1466 g_profiler->avg("ServerEnv: active blocks", m_active_blocks.m_abm_list.size());
1467 g_profiler->avg("ServerEnv: active blocks cached", blocks_cached);
1468 g_profiler->avg("ServerEnv: active blocks scanned for ABMs", blocks_scanned);
1469 g_profiler->avg("ServerEnv: ABMs run", abms_run);
1471 timer.stop(true);
1475 Step script environment (run global on_step())
1477 m_script->environment_Step(dtime);
1480 Step active objects
1483 ScopeProfiler sp(g_profiler, "ServerEnv: Run SAO::step()", SPT_AVG);
1485 // This helps the objects to send data at the same time
1486 bool send_recommended = false;
1487 m_send_recommended_timer += dtime;
1488 if (m_send_recommended_timer > getSendRecommendedInterval()) {
1489 m_send_recommended_timer -= getSendRecommendedInterval();
1490 send_recommended = true;
1493 auto cb_state = [this, dtime, send_recommended] (ServerActiveObject *obj) {
1494 if (obj->isGone())
1495 return;
1497 // Step object
1498 obj->step(dtime, send_recommended);
1499 // Read messages from object
1500 obj->dumpAOMessagesToQueue(m_active_object_messages);
1502 m_ao_manager.step(dtime, cb_state);
1506 Manage active objects
1508 if (m_object_management_interval.step(dtime, 0.5)) {
1509 removeRemovedObjects();
1513 Manage particle spawner expiration
1515 if (m_particle_management_interval.step(dtime, 1.0)) {
1516 for (std::unordered_map<u32, float>::iterator i = m_particle_spawners.begin();
1517 i != m_particle_spawners.end(); ) {
1518 //non expiring spawners
1519 if (i->second == PARTICLE_SPAWNER_NO_EXPIRY) {
1520 ++i;
1521 continue;
1524 i->second -= 1.0f;
1525 if (i->second <= 0.f)
1526 m_particle_spawners.erase(i++);
1527 else
1528 ++i;
1532 // Send outdated player inventories
1533 for (RemotePlayer *player : m_players) {
1534 if (player->getPeerId() == PEER_ID_INEXISTENT)
1535 continue;
1537 PlayerSAO *sao = player->getPlayerSAO();
1538 if (sao && player->inventory.checkModified())
1539 m_server->SendInventory(sao, true);
1542 // Send outdated detached inventories
1543 m_server->sendDetachedInventories(PEER_ID_INEXISTENT, true);
1546 u32 ServerEnvironment::addParticleSpawner(float exptime)
1548 // Timers with lifetime 0 do not expire
1549 float time = exptime > 0.f ? exptime : PARTICLE_SPAWNER_NO_EXPIRY;
1551 u32 id = 0;
1552 for (;;) { // look for unused particlespawner id
1553 id++;
1554 std::unordered_map<u32, float>::iterator f = m_particle_spawners.find(id);
1555 if (f == m_particle_spawners.end()) {
1556 m_particle_spawners[id] = time;
1557 break;
1560 return id;
1563 u32 ServerEnvironment::addParticleSpawner(float exptime, u16 attached_id)
1565 u32 id = addParticleSpawner(exptime);
1566 m_particle_spawner_attachments[id] = attached_id;
1567 if (ServerActiveObject *obj = getActiveObject(attached_id)) {
1568 obj->attachParticleSpawner(id);
1570 return id;
1573 void ServerEnvironment::deleteParticleSpawner(u32 id, bool remove_from_object)
1575 m_particle_spawners.erase(id);
1576 const auto &it = m_particle_spawner_attachments.find(id);
1577 if (it != m_particle_spawner_attachments.end()) {
1578 u16 obj_id = it->second;
1579 ServerActiveObject *sao = getActiveObject(obj_id);
1580 if (sao != NULL && remove_from_object) {
1581 sao->detachParticleSpawner(id);
1583 m_particle_spawner_attachments.erase(id);
1587 u16 ServerEnvironment::addActiveObject(ServerActiveObject *object)
1589 assert(object); // Pre-condition
1590 m_added_objects++;
1591 u16 id = addActiveObjectRaw(object, true, 0);
1592 return id;
1596 Finds out what new objects have been added to
1597 inside a radius around a position
1599 void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius,
1600 s16 player_radius,
1601 std::set<u16> &current_objects,
1602 std::queue<u16> &added_objects)
1604 f32 radius_f = radius * BS;
1605 f32 player_radius_f = player_radius * BS;
1607 if (player_radius_f < 0.0f)
1608 player_radius_f = 0.0f;
1610 m_ao_manager.getAddedActiveObjectsAroundPos(playersao->getBasePosition(), radius_f,
1611 player_radius_f, current_objects, added_objects);
1615 Finds out what objects have been removed from
1616 inside a radius around a position
1618 void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius,
1619 s16 player_radius,
1620 std::set<u16> &current_objects,
1621 std::queue<u16> &removed_objects)
1623 f32 radius_f = radius * BS;
1624 f32 player_radius_f = player_radius * BS;
1626 if (player_radius_f < 0)
1627 player_radius_f = 0;
1629 Go through current_objects; object is removed if:
1630 - object is not found in m_active_objects (this is actually an
1631 error condition; objects should be removed only after all clients
1632 have been informed about removal), or
1633 - object is to be removed or deactivated, or
1634 - object is too far away
1636 for (u16 id : current_objects) {
1637 ServerActiveObject *object = getActiveObject(id);
1639 if (object == NULL) {
1640 infostream << "ServerEnvironment::getRemovedActiveObjects():"
1641 << " object in current_objects is NULL" << std::endl;
1642 removed_objects.push(id);
1643 continue;
1646 if (object->isGone()) {
1647 removed_objects.push(id);
1648 continue;
1651 f32 distance_f = object->getBasePosition().getDistanceFrom(playersao->getBasePosition());
1652 if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1653 if (distance_f <= player_radius_f || player_radius_f == 0)
1654 continue;
1655 } else if (distance_f <= radius_f)
1656 continue;
1658 // Object is no longer visible
1659 removed_objects.push(id);
1663 void ServerEnvironment::setStaticForActiveObjectsInBlock(
1664 v3s16 blockpos, bool static_exists, v3s16 static_block)
1666 MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos);
1667 if (!block)
1668 return;
1670 for (auto &so_it : block->m_static_objects.m_active) {
1671 // Get the ServerActiveObject counterpart to this StaticObject
1672 ServerActiveObject *sao = m_ao_manager.getActiveObject(so_it.first);
1673 if (!sao) {
1674 // If this ever happens, there must be some kind of nasty bug.
1675 errorstream << "ServerEnvironment::setStaticForObjectsInBlock(): "
1676 "Object from MapBlock::m_static_objects::m_active not found "
1677 "in m_active_objects";
1678 continue;
1681 sao->m_static_exists = static_exists;
1682 sao->m_static_block = static_block;
1686 bool ServerEnvironment::getActiveObjectMessage(ActiveObjectMessage *dest)
1688 if(m_active_object_messages.empty())
1689 return false;
1691 *dest = std::move(m_active_object_messages.front());
1692 m_active_object_messages.pop();
1693 return true;
1696 void ServerEnvironment::getSelectedActiveObjects(
1697 const core::line3d<f32> &shootline_on_map,
1698 std::vector<PointedThing> &objects)
1700 std::vector<ServerActiveObject *> objs;
1701 getObjectsInsideRadius(objs, shootline_on_map.start,
1702 shootline_on_map.getLength() + 10.0f, nullptr);
1703 const v3f line_vector = shootline_on_map.getVector();
1705 for (auto obj : objs) {
1706 if (obj->isGone())
1707 continue;
1708 aabb3f selection_box;
1709 if (!obj->getSelectionBox(&selection_box))
1710 continue;
1712 v3f pos = obj->getBasePosition();
1714 aabb3f offsetted_box(selection_box.MinEdge + pos,
1715 selection_box.MaxEdge + pos);
1717 v3f current_intersection;
1718 v3s16 current_normal;
1719 if (boxLineCollision(offsetted_box, shootline_on_map.start, line_vector,
1720 &current_intersection, &current_normal)) {
1721 objects.emplace_back(
1722 (s16) obj->getId(), current_intersection, current_normal,
1723 (current_intersection - shootline_on_map.start).getLengthSQ());
1729 ************ Private methods *************
1732 u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object,
1733 bool set_changed, u32 dtime_s)
1735 if (!m_ao_manager.registerObject(object)) {
1736 return 0;
1739 // Register reference in scripting api (must be done before post-init)
1740 m_script->addObjectReference(object);
1741 // Post-initialize object
1742 object->addedToEnvironment(dtime_s);
1744 // Add static data to block
1745 if (object->isStaticAllowed()) {
1746 // Add static object to active static list of the block
1747 v3f objectpos = object->getBasePosition();
1748 StaticObject s_obj(object, objectpos);
1749 // Add to the block where the object is located in
1750 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
1751 MapBlock *block = m_map->emergeBlock(blockpos);
1752 if(block){
1753 block->m_static_objects.m_active[object->getId()] = s_obj;
1754 object->m_static_exists = true;
1755 object->m_static_block = blockpos;
1757 if(set_changed)
1758 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1759 MOD_REASON_ADD_ACTIVE_OBJECT_RAW);
1760 } else {
1761 v3s16 p = floatToInt(objectpos, BS);
1762 errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
1763 <<"could not emerge block for storing id="<<object->getId()
1764 <<" statically (pos="<<PP(p)<<")"<<std::endl;
1768 return object->getId();
1772 Remove objects that satisfy (isGone() && m_known_by_count==0)
1774 void ServerEnvironment::removeRemovedObjects()
1776 ScopeProfiler sp(g_profiler, "ServerEnvironment::removeRemovedObjects()", SPT_AVG);
1778 auto clear_cb = [this] (ServerActiveObject *obj, u16 id) {
1779 // This shouldn't happen but check it
1780 if (!obj) {
1781 errorstream << "ServerEnvironment::removeRemovedObjects(): "
1782 << "NULL object found. id=" << id << std::endl;
1783 return true;
1787 We will handle objects marked for removal or deactivation
1789 if (!obj->isGone())
1790 return false;
1793 Delete static data from block if removed
1795 if (obj->m_pending_removal)
1796 deleteStaticFromBlock(obj, id, MOD_REASON_REMOVE_OBJECTS_REMOVE, false);
1798 // If still known by clients, don't actually remove. On some future
1799 // invocation this will be 0, which is when removal will continue.
1800 if(obj->m_known_by_count > 0)
1801 return false;
1804 Move static data from active to stored if deactivated
1806 if (!obj->m_pending_removal && obj->m_static_exists) {
1807 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
1808 if (block) {
1809 const auto i = block->m_static_objects.m_active.find(id);
1810 if (i != block->m_static_objects.m_active.end()) {
1811 block->m_static_objects.m_stored.push_back(i->second);
1812 block->m_static_objects.m_active.erase(id);
1813 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1814 MOD_REASON_REMOVE_OBJECTS_DEACTIVATE);
1815 } else {
1816 warningstream << "ServerEnvironment::removeRemovedObjects(): "
1817 << "id=" << id << " m_static_exists=true but "
1818 << "static data doesn't actually exist in "
1819 << PP(obj->m_static_block) << std::endl;
1821 } else {
1822 infostream << "Failed to emerge block from which an object to "
1823 << "be deactivated was loaded from. id=" << id << std::endl;
1827 // Tell the object about removal
1828 obj->removingFromEnvironment();
1829 // Deregister in scripting api
1830 m_script->removeObjectReference(obj);
1832 // Delete
1833 if (obj->environmentDeletes())
1834 delete obj;
1836 return true;
1839 m_ao_manager.clear(clear_cb);
1842 static void print_hexdump(std::ostream &o, const std::string &data)
1844 const int linelength = 16;
1845 for(int l=0; ; l++){
1846 int i0 = linelength * l;
1847 bool at_end = false;
1848 int thislinelength = linelength;
1849 if(i0 + thislinelength > (int)data.size()){
1850 thislinelength = data.size() - i0;
1851 at_end = true;
1853 for(int di=0; di<linelength; di++){
1854 int i = i0 + di;
1855 char buf[4];
1856 if(di<thislinelength)
1857 porting::mt_snprintf(buf, sizeof(buf), "%.2x ", data[i]);
1858 else
1859 porting::mt_snprintf(buf, sizeof(buf), " ");
1860 o<<buf;
1862 o<<" ";
1863 for(int di=0; di<thislinelength; di++){
1864 int i = i0 + di;
1865 if(data[i] >= 32)
1866 o<<data[i];
1867 else
1868 o<<".";
1870 o<<std::endl;
1871 if(at_end)
1872 break;
1876 ServerActiveObject* ServerEnvironment::createSAO(ActiveObjectType type, v3f pos,
1877 const std::string &data)
1879 switch (type) {
1880 case ACTIVEOBJECT_TYPE_LUAENTITY:
1881 return new LuaEntitySAO(this, pos, data);
1882 default:
1883 warningstream << "ServerActiveObject: No factory for type=" << type << std::endl;
1885 return nullptr;
1889 Convert stored objects from blocks near the players to active.
1891 void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s)
1893 if(block == NULL)
1894 return;
1896 // Ignore if no stored objects (to not set changed flag)
1897 if(block->m_static_objects.m_stored.empty())
1898 return;
1900 verbosestream<<"ServerEnvironment::activateObjects(): "
1901 <<"activating objects of block "<<PP(block->getPos())
1902 <<" ("<<block->m_static_objects.m_stored.size()
1903 <<" objects)"<<std::endl;
1904 bool large_amount = (block->m_static_objects.m_stored.size() > g_settings->getU16("max_objects_per_block"));
1905 if (large_amount) {
1906 errorstream<<"suspiciously large amount of objects detected: "
1907 <<block->m_static_objects.m_stored.size()<<" in "
1908 <<PP(block->getPos())
1909 <<"; removing all of them."<<std::endl;
1910 // Clear stored list
1911 block->m_static_objects.m_stored.clear();
1912 block->raiseModified(MOD_STATE_WRITE_NEEDED,
1913 MOD_REASON_TOO_MANY_OBJECTS);
1914 return;
1917 // Activate stored objects
1918 std::vector<StaticObject> new_stored;
1919 for (const StaticObject &s_obj : block->m_static_objects.m_stored) {
1920 // Create an active object from the data
1921 ServerActiveObject *obj = createSAO((ActiveObjectType) s_obj.type, s_obj.pos,
1922 s_obj.data);
1923 // If couldn't create object, store static data back.
1924 if (!obj) {
1925 errorstream<<"ServerEnvironment::activateObjects(): "
1926 <<"failed to create active object from static object "
1927 <<"in block "<<PP(s_obj.pos/BS)
1928 <<" type="<<(int)s_obj.type<<" data:"<<std::endl;
1929 print_hexdump(verbosestream, s_obj.data);
1931 new_stored.push_back(s_obj);
1932 continue;
1934 verbosestream<<"ServerEnvironment::activateObjects(): "
1935 <<"activated static object pos="<<PP(s_obj.pos/BS)
1936 <<" type="<<(int)s_obj.type<<std::endl;
1937 // This will also add the object to the active static list
1938 addActiveObjectRaw(obj, false, dtime_s);
1941 // Clear stored list
1942 block->m_static_objects.m_stored.clear();
1943 // Add leftover failed stuff to stored list
1944 for (const StaticObject &s_obj : new_stored) {
1945 block->m_static_objects.m_stored.push_back(s_obj);
1949 Note: Block hasn't really been modified here.
1950 The objects have just been activated and moved from the stored
1951 static list to the active static list.
1952 As such, the block is essentially the same.
1953 Thus, do not call block->raiseModified(MOD_STATE_WRITE_NEEDED).
1954 Otherwise there would be a huge amount of unnecessary I/O.
1959 Convert objects that are not standing inside active blocks to static.
1961 If m_known_by_count != 0, active object is not deleted, but static
1962 data is still updated.
1964 If force_delete is set, active object is deleted nevertheless. It
1965 shall only be set so in the destructor of the environment.
1967 If block wasn't generated (not in memory or on disk),
1969 void ServerEnvironment::deactivateFarObjects(bool _force_delete)
1971 auto cb_deactivate = [this, _force_delete] (ServerActiveObject *obj, u16 id) {
1972 // force_delete might be overriden per object
1973 bool force_delete = _force_delete;
1975 // Do not deactivate if disallowed
1976 if (!force_delete && !obj->shouldUnload())
1977 return false;
1979 // removeRemovedObjects() is responsible for these
1980 if (!force_delete && obj->isGone())
1981 return false;
1983 const v3f &objectpos = obj->getBasePosition();
1985 // The block in which the object resides in
1986 v3s16 blockpos_o = getNodeBlockPos(floatToInt(objectpos, BS));
1988 // If object's static data is stored in a deactivated block and object
1989 // is actually located in an active block, re-save to the block in
1990 // which the object is actually located in.
1991 if (!force_delete && obj->m_static_exists &&
1992 !m_active_blocks.contains(obj->m_static_block) &&
1993 m_active_blocks.contains(blockpos_o)) {
1994 // Delete from block where object was located
1995 deleteStaticFromBlock(obj, id, MOD_REASON_STATIC_DATA_REMOVED, false);
1997 StaticObject s_obj(obj, objectpos);
1998 // Save to block where object is located
1999 saveStaticToBlock(blockpos_o, id, obj, s_obj, MOD_REASON_STATIC_DATA_ADDED);
2001 return false;
2004 // If block is still active, don't remove
2005 bool still_active = obj->isStaticAllowed() ?
2006 m_active_blocks.contains(blockpos_o) :
2007 getMap().getBlockNoCreateNoEx(blockpos_o) != nullptr;
2008 if (!force_delete && still_active)
2009 return false;
2011 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
2012 << "deactivating object id=" << id << " on inactive block "
2013 << PP(blockpos_o) << std::endl;
2015 // If known by some client, don't immediately delete.
2016 bool pending_delete = (obj->m_known_by_count > 0 && !force_delete);
2019 Update the static data
2021 if (obj->isStaticAllowed()) {
2022 // Create new static object
2023 StaticObject s_obj(obj, objectpos);
2025 bool stays_in_same_block = false;
2026 bool data_changed = true;
2028 // Check if static data has changed considerably
2029 if (obj->m_static_exists) {
2030 if (obj->m_static_block == blockpos_o)
2031 stays_in_same_block = true;
2033 MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
2035 if (block) {
2036 const auto n = block->m_static_objects.m_active.find(id);
2037 if (n != block->m_static_objects.m_active.end()) {
2038 StaticObject static_old = n->second;
2040 float save_movem = obj->getMinimumSavedMovement();
2042 if (static_old.data == s_obj.data &&
2043 (static_old.pos - objectpos).getLength() < save_movem)
2044 data_changed = false;
2045 } else {
2046 warningstream << "ServerEnvironment::deactivateFarObjects(): "
2047 << "id=" << id << " m_static_exists=true but "
2048 << "static data doesn't actually exist in "
2049 << PP(obj->m_static_block) << std::endl;
2055 While changes are always saved, blocks are only marked as modified
2056 if the object has moved or different staticdata. (see above)
2058 bool shall_be_written = (!stays_in_same_block || data_changed);
2059 u32 reason = shall_be_written ? MOD_REASON_STATIC_DATA_CHANGED : MOD_REASON_UNKNOWN;
2061 // Delete old static object
2062 deleteStaticFromBlock(obj, id, reason, false);
2064 // Add to the block where the object is located in
2065 v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
2066 u16 store_id = pending_delete ? id : 0;
2067 if (!saveStaticToBlock(blockpos, store_id, obj, s_obj, reason))
2068 force_delete = true;
2072 If known by some client, set pending deactivation.
2073 Otherwise delete it immediately.
2075 if (pending_delete && !force_delete) {
2076 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
2077 << "object id=" << id << " is known by clients"
2078 << "; not deleting yet" << std::endl;
2080 obj->m_pending_deactivation = true;
2081 return false;
2084 verbosestream << "ServerEnvironment::deactivateFarObjects(): "
2085 << "object id=" << id << " is not known by clients"
2086 << "; deleting" << std::endl;
2088 // Tell the object about removal
2089 obj->removingFromEnvironment();
2090 // Deregister in scripting api
2091 m_script->removeObjectReference(obj);
2093 // Delete active object
2094 if (obj->environmentDeletes())
2095 delete obj;
2097 return true;
2100 m_ao_manager.clear(cb_deactivate);
2103 void ServerEnvironment::deleteStaticFromBlock(
2104 ServerActiveObject *obj, u16 id, u32 mod_reason, bool no_emerge)
2106 if (!obj->m_static_exists)
2107 return;
2109 MapBlock *block;
2110 if (no_emerge)
2111 block = m_map->getBlockNoCreateNoEx(obj->m_static_block);
2112 else
2113 block = m_map->emergeBlock(obj->m_static_block, false);
2114 if (!block) {
2115 if (!no_emerge)
2116 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2117 << " when deleting static data of object from it. id=" << id << std::endl;
2118 return;
2121 block->m_static_objects.remove(id);
2122 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2123 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2125 obj->m_static_exists = false;
2128 bool ServerEnvironment::saveStaticToBlock(
2129 v3s16 blockpos, u16 store_id,
2130 ServerActiveObject *obj, const StaticObject &s_obj,
2131 u32 mod_reason)
2133 MapBlock *block = nullptr;
2134 try {
2135 block = m_map->emergeBlock(blockpos);
2136 } catch (InvalidPositionException &e) {
2137 // Handled via NULL pointer
2138 // NOTE: emergeBlock's failure is usually determined by it
2139 // actually returning NULL
2142 if (!block) {
2143 errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block)
2144 << " when saving static data of object to it. id=" << store_id << std::endl;
2145 return false;
2147 if (block->m_static_objects.m_stored.size() >= g_settings->getU16("max_objects_per_block")) {
2148 warningstream << "ServerEnv: Trying to store id = " << store_id
2149 << " statically but block " << PP(blockpos)
2150 << " already contains "
2151 << block->m_static_objects.m_stored.size()
2152 << " objects." << std::endl;
2153 return false;
2156 block->m_static_objects.insert(store_id, s_obj);
2157 if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested
2158 block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason);
2160 obj->m_static_exists = true;
2161 obj->m_static_block = blockpos;
2163 return true;
2166 PlayerDatabase *ServerEnvironment::openPlayerDatabase(const std::string &name,
2167 const std::string &savedir, const Settings &conf)
2170 if (name == "sqlite3")
2171 return new PlayerDatabaseSQLite3(savedir);
2173 if (name == "dummy")
2174 return new Database_Dummy();
2176 #if USE_POSTGRESQL
2177 if (name == "postgresql") {
2178 std::string connect_string;
2179 conf.getNoEx("pgsql_player_connection", connect_string);
2180 return new PlayerDatabasePostgreSQL(connect_string);
2182 #endif
2184 #if USE_LEVELDB
2185 if (name == "leveldb")
2186 return new PlayerDatabaseLevelDB(savedir);
2187 #endif
2189 if (name == "files")
2190 return new PlayerDatabaseFiles(savedir + DIR_DELIM + "players");
2192 throw BaseException(std::string("Database backend ") + name + " not supported.");
2195 bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params,
2196 const Settings &cmd_args)
2198 std::string migrate_to = cmd_args.get("migrate-players");
2199 Settings world_mt;
2200 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2201 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2202 errorstream << "Cannot read world.mt!" << std::endl;
2203 return false;
2206 if (!world_mt.exists("player_backend")) {
2207 errorstream << "Please specify your current backend in world.mt:"
2208 << std::endl
2209 << " player_backend = {files|sqlite3|leveldb|postgresql}"
2210 << std::endl;
2211 return false;
2214 std::string backend = world_mt.get("player_backend");
2215 if (backend == migrate_to) {
2216 errorstream << "Cannot migrate: new backend is same"
2217 << " as the old one" << std::endl;
2218 return false;
2221 const std::string players_backup_path = game_params.world_path + DIR_DELIM
2222 + "players.bak";
2224 if (backend == "files") {
2225 // Create backup directory
2226 fs::CreateDir(players_backup_path);
2229 try {
2230 PlayerDatabase *srcdb = ServerEnvironment::openPlayerDatabase(backend,
2231 game_params.world_path, world_mt);
2232 PlayerDatabase *dstdb = ServerEnvironment::openPlayerDatabase(migrate_to,
2233 game_params.world_path, world_mt);
2235 std::vector<std::string> player_list;
2236 srcdb->listPlayers(player_list);
2237 for (std::vector<std::string>::const_iterator it = player_list.begin();
2238 it != player_list.end(); ++it) {
2239 actionstream << "Migrating player " << it->c_str() << std::endl;
2240 RemotePlayer player(it->c_str(), NULL);
2241 PlayerSAO playerSAO(NULL, &player, 15000, false);
2243 srcdb->loadPlayer(&player, &playerSAO);
2245 playerSAO.finalize(&player, std::set<std::string>());
2246 player.setPlayerSAO(&playerSAO);
2248 dstdb->savePlayer(&player);
2250 // For files source, move player files to backup dir
2251 if (backend == "files") {
2252 fs::Rename(
2253 game_params.world_path + DIR_DELIM + "players" + DIR_DELIM + (*it),
2254 players_backup_path + DIR_DELIM + (*it));
2258 actionstream << "Successfully migrated " << player_list.size() << " players"
2259 << std::endl;
2260 world_mt.set("player_backend", migrate_to);
2261 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2262 errorstream << "Failed to update world.mt!" << std::endl;
2263 else
2264 actionstream << "world.mt updated" << std::endl;
2266 // When migration is finished from file backend, remove players directory if empty
2267 if (backend == "files") {
2268 fs::DeleteSingleFileOrEmptyDirectory(game_params.world_path + DIR_DELIM
2269 + "players");
2272 delete srcdb;
2273 delete dstdb;
2275 } catch (BaseException &e) {
2276 errorstream << "An error occurred during migration: " << e.what() << std::endl;
2277 return false;
2279 return true;
2282 AuthDatabase *ServerEnvironment::openAuthDatabase(
2283 const std::string &name, const std::string &savedir, const Settings &conf)
2285 if (name == "sqlite3")
2286 return new AuthDatabaseSQLite3(savedir);
2288 #if USE_POSTGRESQL
2289 if (name == "postgresql") {
2290 std::string connect_string;
2291 conf.getNoEx("pgsql_auth_connection", connect_string);
2292 return new AuthDatabasePostgreSQL(connect_string);
2294 #endif
2296 if (name == "files")
2297 return new AuthDatabaseFiles(savedir);
2299 #if USE_LEVELDB
2300 if (name == "leveldb")
2301 return new AuthDatabaseLevelDB(savedir);
2302 #endif
2304 throw BaseException(std::string("Database backend ") + name + " not supported.");
2307 bool ServerEnvironment::migrateAuthDatabase(
2308 const GameParams &game_params, const Settings &cmd_args)
2310 std::string migrate_to = cmd_args.get("migrate-auth");
2311 Settings world_mt;
2312 std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
2313 if (!world_mt.readConfigFile(world_mt_path.c_str())) {
2314 errorstream << "Cannot read world.mt!" << std::endl;
2315 return false;
2318 std::string backend = "files";
2319 if (world_mt.exists("auth_backend"))
2320 backend = world_mt.get("auth_backend");
2321 else
2322 warningstream << "No auth_backend found in world.mt, "
2323 "assuming \"files\"." << std::endl;
2325 if (backend == migrate_to) {
2326 errorstream << "Cannot migrate: new backend is same"
2327 << " as the old one" << std::endl;
2328 return false;
2331 try {
2332 const std::unique_ptr<AuthDatabase> srcdb(ServerEnvironment::openAuthDatabase(
2333 backend, game_params.world_path, world_mt));
2334 const std::unique_ptr<AuthDatabase> dstdb(ServerEnvironment::openAuthDatabase(
2335 migrate_to, game_params.world_path, world_mt));
2337 std::vector<std::string> names_list;
2338 srcdb->listNames(names_list);
2339 for (const std::string &name : names_list) {
2340 actionstream << "Migrating auth entry for " << name << std::endl;
2341 bool success;
2342 AuthEntry authEntry;
2343 success = srcdb->getAuth(name, authEntry);
2344 success = success && dstdb->createAuth(authEntry);
2345 if (!success)
2346 errorstream << "Failed to migrate " << name << std::endl;
2349 actionstream << "Successfully migrated " << names_list.size()
2350 << " auth entries" << std::endl;
2351 world_mt.set("auth_backend", migrate_to);
2352 if (!world_mt.updateConfigFile(world_mt_path.c_str()))
2353 errorstream << "Failed to update world.mt!" << std::endl;
2354 else
2355 actionstream << "world.mt updated" << std::endl;
2357 if (backend == "files") {
2358 // special-case files migration:
2359 // move auth.txt to auth.txt.bak if possible
2360 std::string auth_txt_path =
2361 game_params.world_path + DIR_DELIM + "auth.txt";
2362 std::string auth_bak_path = auth_txt_path + ".bak";
2363 if (!fs::PathExists(auth_bak_path))
2364 if (fs::Rename(auth_txt_path, auth_bak_path))
2365 actionstream << "Renamed auth.txt to auth.txt.bak"
2366 << std::endl;
2367 else
2368 errorstream << "Could not rename auth.txt to "
2369 "auth.txt.bak" << std::endl;
2370 else
2371 warningstream << "auth.txt.bak already exists, auth.txt "
2372 "not renamed" << std::endl;
2375 } catch (BaseException &e) {
2376 errorstream << "An error occurred during migration: " << e.what()
2377 << std::endl;
2378 return false;
2380 return true;