/*
Minetest
Copyright (C) 2010-2017 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "serverenvironment.h"
#include "content_sao.h"
#include "settings.h"
#include "log.h"
#include "nodedef.h"
#include "nodemetadata.h"
#include "gamedef.h"
#include "map.h"
#include "profiler.h"
#include "raycast.h"
#include "remoteplayer.h"
#include "scripting_server.h"
#include "server.h"
#include "voxelalgorithms.h"
#include "util/serialize.h"
#include "util/basic_macros.h"
#include "util/pointedthing.h"
#include "threading/mutex_auto_lock.h"
#include "filesys.h"
#include "gameparams.h"
#include "database-dummy.h"
#include "database-files.h"
#include "database-sqlite3.h"
#if USE_POSTGRESQL
#include "database-postgresql.h"
#endif
#define LBM_NAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_:"
// A number that is much smaller than the timeout for particle spawners should/could ever be
#define PARTICLE_SPAWNER_NO_EXPIRY -1024.f
/*
ABMWithState
*/
ABMWithState::ABMWithState(ActiveBlockModifier *abm_):
abm(abm_),
timer(0)
{
// Initialize timer to random value to spread processing
float itv = abm->getTriggerInterval();
itv = MYMAX(0.001, itv); // No less than 1ms
int minval = MYMAX(-0.51*itv, -60); // Clamp to
int maxval = MYMIN(0.51*itv, 60); // +-60 seconds
timer = myrand_range(minval, maxval);
}
/*
LBMManager
*/
void LBMContentMapping::deleteContents()
{
for (std::vector<LoadingBlockModifierDef *>::iterator it = lbm_list.begin();
it != lbm_list.end(); ++it) {
delete *it;
}
}
void LBMContentMapping::addLBM(LoadingBlockModifierDef *lbm_def, IGameDef *gamedef)
{
// Add the lbm_def to the LBMContentMapping.
// Unknown names get added to the global NameIdMapping.
INodeDefManager *nodedef = gamedef->ndef();
lbm_list.push_back(lbm_def);
for (std::set<std::string>::const_iterator it = lbm_def->trigger_contents.begin();
it != lbm_def->trigger_contents.end(); ++it) {
std::set<content_t> c_ids;
bool found = nodedef->getIds(*it, c_ids);
if (!found) {
content_t c_id = gamedef->allocateUnknownNodeId(*it);
if (c_id == CONTENT_IGNORE) {
// Seems it can't be allocated.
warningstream << "Could not internalize node name \"" << *it
<< "\" while loading LBM \"" << lbm_def->name << "\"." << std::endl;
continue;
}
c_ids.insert(c_id);
}
for (std::set<content_t>::const_iterator iit =
c_ids.begin(); iit != c_ids.end(); ++iit) {
content_t c_id = *iit;
map[c_id].push_back(lbm_def);
}
}
}
const std::vector<LoadingBlockModifierDef *> *
LBMContentMapping::lookup(content_t c) const
{
container_map::const_iterator it = map.find(c);
if (it == map.end())
return NULL;
// This first dereferences the iterator, returning
// a std::vector<LoadingBlockModifierDef *>
// reference, then we convert it to a pointer.
return &(it->second);
}
LBMManager::~LBMManager()
{
for (std::map<std::string, LoadingBlockModifierDef *>::iterator it =
m_lbm_defs.begin(); it != m_lbm_defs.end(); ++it) {
delete it->second;
}
for (lbm_lookup_map::iterator it = m_lbm_lookup.begin();
it != m_lbm_lookup.end(); ++it) {
(it->second).deleteContents();
}
}
void LBMManager::addLBMDef(LoadingBlockModifierDef *lbm_def)
{
// Precondition, in query mode the map isn't used anymore
FATAL_ERROR_IF(m_query_mode == true,
"attempted to modify LBMManager in query mode");
if (!string_allowed(lbm_def->name, LBM_NAME_ALLOWED_CHARS)) {
throw ModError("Error adding LBM \"" + lbm_def->name +
"\": Does not follow naming conventions: "
"Only characters [a-z0-9_:] are allowed.");
}
m_lbm_defs[lbm_def->name] = lbm_def;
}
void LBMManager::loadIntroductionTimes(const std::string ×,
IGameDef *gamedef, u32 now)
{
m_query_mode = true;
// name -> time map.
// Storing it in a map first instead of
// handling the stuff directly in the loop
// removes all duplicate entries.
// TODO make this std::unordered_map
std::map<std::string, u32> introduction_times;
/*
The introduction times string consists of name~time entries,
with each entry terminated by a semicolon. The time is decimal.
*/
size_t idx = 0;
size_t idx_new;
while ((idx_new = times.find(";", idx)) != std::string::npos) {
std::string entry = times.substr(idx, idx_new - idx);
std::vector<std::string> components = str_split(entry, '~');
if (components.size() != 2)
throw SerializationError("Introduction times entry \""
+ entry + "\" requires exactly one '~'!");
const std::string &name = components[0];
u32 time = from_string<u32>(components[1]);
introduction_times[name] = time;
idx = idx_new + 1;
}
// Put stuff from introduction_times into m_lbm_lookup
for (std::map<std::string, u32>::const_iterator it = introduction_times.begin();
it != introduction_times.end(); ++it) {
const std::string &name = it->first;
u32 time = it->second;
std::map<std::string, LoadingBlockModifierDef *>::iterator def_it =
m_lbm_defs.find(name);
if (def_it == m_lbm_defs.end()) {
// This seems to be an LBM entry for
// an LBM we haven't loaded. Discard it.
continue;
}
LoadingBlockModifierDef *lbm_def = def_it->second;
if (lbm_def->run_at_every_load) {
// This seems to be an LBM entry for
// an LBM that runs at every load.
// Don't add it just yet.
continue;
}
m_lbm_lookup[time].addLBM(lbm_def, gamedef);
// Erase the entry so that we know later
// what elements didn't get put into m_lbm_lookup
m_lbm_defs.erase(name);
}
// Now also add the elements from m_lbm_defs to m_lbm_lookup
// that weren't added in the previous step.
// They are introduced first time to this world,
// or are run at every load (introducement time hardcoded to U32_MAX).
LBMContentMapping &lbms_we_introduce_now = m_lbm_lookup[now];
LBMContentMapping &lbms_running_always = m_lbm_lookup[U32_MAX];
for (std::map<std::string, LoadingBlockModifierDef *>::iterator it =
m_lbm_defs.begin(); it != m_lbm_defs.end(); ++it) {
if (it->second->run_at_every_load) {
lbms_running_always.addLBM(it->second, gamedef);
} else {
lbms_we_introduce_now.addLBM(it->second, gamedef);
}
}
// Clear the list, so that we don't delete remaining elements
// twice in the destructor
m_lbm_defs.clear();
}
std::string LBMManager::createIntroductionTimesString()
{
// Precondition, we must be in query mode
FATAL_ERROR_IF(m_query_mode == false,
"attempted to query on non fully set up LBMManager");
std::ostringstream oss;
for (lbm_lookup_map::iterator it = m_lbm_lookup.begin();
it != m_lbm_lookup.end(); ++it) {
u32 time = it->first;
std::vector<LoadingBlockModifierDef *> &lbm_list = it->second.lbm_list;
for (std::vector<LoadingBlockModifierDef *>::iterator iit = lbm_list.begin();
iit != lbm_list.end(); ++iit) {
// Don't add if the LBM runs at every load,
// then introducement time is hardcoded
// and doesn't need to be stored
if ((*iit)->run_at_every_load)
continue;
oss << (*iit)->name << "~" << time << ";";
}
}
return oss.str();
}
void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, u32 stamp)
{
// Precondition, we need m_lbm_lookup to be initialized
FATAL_ERROR_IF(m_query_mode == false,
"attempted to query on non fully set up LBMManager");
v3s16 pos_of_block = block->getPosRelative();
v3s16 pos;
MapNode n;
content_t c;
lbm_lookup_map::const_iterator it = getLBMsIntroducedAfter(stamp);
for (pos.X = 0; pos.X < MAP_BLOCKSIZE; pos.X++)
for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++)
for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++)
{
n = block->getNodeNoEx(pos);
c = n.getContent();
for (LBMManager::lbm_lookup_map::const_iterator iit = it;
iit != m_lbm_lookup.end(); ++iit) {
const std::vector<LoadingBlockModifierDef *> *lbm_list =
iit->second.lookup(c);
if (!lbm_list)
continue;
for (std::vector<LoadingBlockModifierDef *>::const_iterator iit =
lbm_list->begin(); iit != lbm_list->end(); ++iit) {
(*iit)->trigger(env, pos + pos_of_block, n);
}
}
}
}
/*
ActiveBlockList
*/
void fillRadiusBlock(v3s16 p0, s16 r, std::set<v3s16> &list)
{
v3s16 p;
for(p.X=p0.X-r; p.X<=p0.X+r; p.X++)
for(p.Y=p0.Y-r; p.Y<=p0.Y+r; p.Y++)
for(p.Z=p0.Z-r; p.Z<=p0.Z+r; p.Z++)
{
// limit to a sphere
if (p.getDistanceFrom(p0) <= r) {
// Set in list
list.insert(p);
}
}
}
void ActiveBlockList::update(std::vector<v3s16> &active_positions,
s16 radius,
std::set<v3s16> &blocks_removed,
std::set<v3s16> &blocks_added)
{
/*
Create the new list
*/
std::set<v3s16> newlist = m_forceloaded_list;
for(std::vector<v3s16>::iterator i = active_positions.begin();
i != active_positions.end(); ++i)
{
fillRadiusBlock(*i, radius, newlist);
}
/*
Find out which blocks on the old list are not on the new list
*/
// Go through old list
for(std::set<v3s16>::iterator i = m_list.begin();
i != m_list.end(); ++i)
{
v3s16 p = *i;
// If not on new list, it's been removed
if(newlist.find(p) == newlist.end())
blocks_removed.insert(p);
}
/*
Find out which blocks on the new list are not on the old list
*/
// Go through new list
for(std::set<v3s16>::iterator i = newlist.begin();
i != newlist.end(); ++i)
{
v3s16 p = *i;
// If not on old list, it's been added
if(m_list.find(p) == m_list.end())
blocks_added.insert(p);
}
/*
Update m_list
*/
m_list.clear();
for(std::set<v3s16>::iterator i = newlist.begin();
i != newlist.end(); ++i)
{
v3s16 p = *i;
m_list.insert(p);
}
}
/*
ServerEnvironment
*/
ServerEnvironment::ServerEnvironment(ServerMap *map,
ServerScripting *scriptIface, Server *server,
const std::string &path_world):
Environment(server),
m_map(map),
m_script(scriptIface),
m_server(server),
m_path_world(path_world),
m_send_recommended_timer(0),
m_active_block_interval_overload_skip(0),
m_game_time(0),
m_game_time_fraction_counter(0),
m_last_clear_objects_time(0),
m_recommended_send_interval(0.1),
m_max_lag_estimate(0.1),
m_player_database(NULL)
{
// Determine which database backend to use
std::string conf_path = path_world + DIR_DELIM + "world.mt";
Settings conf;
bool succeeded = conf.readConfigFile(conf_path.c_str());
if (!succeeded || !conf.exists("player_backend")) {
// fall back to files
conf.set("player_backend", "files");
warningstream << "/!\\ You are using old player file backend. "
<< "This backend is deprecated and will be removed in next release /!\\"
<< std::endl << "Switching to SQLite3 or PostgreSQL is advised, "
<< "please read http://wiki.minetest.net/Database_backends." << std::endl;
if (!conf.updateConfigFile(conf_path.c_str())) {
errorstream << "ServerEnvironment::ServerEnvironment(): "
<< "Failed to update world.mt!" << std::endl;
}
}
std::string name = "";
conf.getNoEx("player_backend", name);
m_player_database = openPlayerDatabase(name, path_world, conf);
}
ServerEnvironment::~ServerEnvironment()
{
// Clear active block list.
// This makes the next one delete all active objects.
m_active_blocks.clear();
// Convert all objects to static and delete the active objects
deactivateFarObjects(true);
// Drop/delete map
m_map->drop();
// Delete ActiveBlockModifiers
for (std::vector<ABMWithState>::iterator
i = m_abms.begin(); i != m_abms.end(); ++i){
delete i->abm;
}
// Deallocate players
for (std::vector<RemotePlayer *>::iterator i = m_players.begin();
|