/*
Minetest
Copyright (C) 2010-2013 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 <fstream>
#include "environment.h"
#include "filesys.h"
#include "porting.h"
#include "collision.h"
#include "content_mapnode.h"
#include "mapblock.h"
#include "serverobject.h"
#include "content_sao.h"
#include "settings.h"
#include "log.h"
#include "profiler.h"
#include "scripting_game.h"
#include "nodedef.h"
#include "nodemetadata.h"
#include "gamedef.h"
#ifndef SERVER
#include "clientmap.h"
#include "localplayer.h"
#include "mapblock_mesh.h"
#include "event.h"
#endif
#include "server.h"
#include "daynightratio.h"
#include "map.h"
#include "emerge.h"
#include "util/serialize.h"
#include "threading/mutex_auto_lock.h"
#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
#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
Environment::Environment():
m_time_of_day_speed(0),
m_time_of_day(9000),
m_time_of_day_f(9000./24000),
m_time_conversion_skew(0.0f),
m_enable_day_night_ratio_override(false),
m_day_night_ratio_override(0.0f)
{
m_cache_enable_shaders = g_settings->getBool("enable_shaders");
m_cache_active_block_mgmt_interval = g_settings->getFloat("active_block_mgmt_interval");
m_cache_abm_interval = g_settings->getFloat("abm_interval");
m_cache_nodetimer_interval = g_settings->getFloat("nodetimer_interval");
}
Environment::~Environment()
{
// Deallocate players
for(std::vector<Player*>::iterator i = m_players.begin();
i != m_players.end(); ++i) {
delete (*i);
}
}
void Environment::addPlayer(Player *player)
{
DSTACK(FUNCTION_NAME);
/*
Check that peer_ids are unique.
Also check that names are unique.
Exception: there can be multiple players with peer_id=0
*/
// If peer id is non-zero, it has to be unique.
if(player->peer_id != 0)
FATAL_ERROR_IF(getPlayer(player->peer_id) != NULL, "Peer id not unique");
// Name has to be unique.
FATAL_ERROR_IF(getPlayer(player->getName()) != NULL, "Player name not unique");
// Add.
m_players.push_back(player);
}
void Environment::removePlayer(Player* player)
{
for (std::vector<Player*>::iterator it = m_players.begin();
it != m_players.end(); ++it) {
if ((*it) == player) {
delete *it;
m_players.erase(it);
return;
}
}
}
Player * Environment::getPlayer(u16 peer_id)
{
for(std::vector<Player*>::iterator i = m_players.begin();
i != m_players.end(); ++i) {
Player *player = *i;
if(player->peer_id == peer_id)
return player;
}
return NULL;
}
Player * Environment::getPlayer(const char *name)
{
for(std::vector<Player*>::iterator i = m_players.begin();
i != m_players.end(); ++i) {
Player *player = *i;
if(strcmp(player->getName(), name) == 0)
return player;
}
return NULL;
}
u32 Environment::getDayNightRatio()
{
MutexAutoLock lock(this->m_time_lock);
if (m_enable_day_night_ratio_override)
return m_day_night_ratio_override;
return time_to_daynight_ratio(m_time_of_day_f * 24000, m_cache_enable_shaders);
}
void Environment::setTimeOfDaySpeed(float speed)
{
m_time_of_day_speed = speed;
}
void Environment::setDayNightRatioOverride(bool enable, u32 value)
{
MutexAutoLock lock(this->m_time_lock);
m_enable_day_night_ratio_override = enable;
m_day_night_ratio_override = value;
}
void Environment::setTimeOfDay(u32 time)
{
MutexAutoLock lock(this->m_time_lock);
if (m_time_of_day > time)
m_day_count++;
m_time_of_day = time;
m_time_of_day_f = (float)time / 24000.0;
}
u32 Environment::getTimeOfDay()
{
MutexAutoLock lock(this->m_time_lock);
return m_time_of_day;
}
float Environment::getTimeOfDayF()
{
MutexAutoLock lock(this->m_time_lock);
return m_time_of_day_f;
}
void Environment::stepTimeOfDay(float dtime)
{
MutexAutoLock lock(this->m_time_lock);
// Cached in order to prevent the two reads we do to give
// different results (can be written by code not under the lock)
f32 cached_time_of_day_speed = m_time_of_day_speed;
f32 speed = cached_time_of_day_speed * 24000. / (24. * 3600);
m_time_conversion_skew += dtime;
u32 units = (u32)(m_time_conversion_skew * speed);
bool sync_f = false;
if (units > 0) {
// Sync at overflow
if (m_time_of_day + units >= 24000) {
sync_f = true;
m_day_count++;
}
m_time_of_day = (m_time_of_day + units) % 24000;
if (sync_f)
m_time_of_day_f = (float)m_time_of_day / 24000.0;
}
if (speed > 0) {
m_time_conversion_skew -= (f32)units / speed;
}
if (!sync_f) {
m_time_of_day_f += cached_time_of_day_speed / 24 / 3600 * dtime;
if (m_time_of_day_f > 1.0)
m_time_of_day_f -= 1.0;
if (m_time_of_day_f < 0.0)
m_time_of_day_f += 1.0;
}
}
u32 Environment::getDayCount()
{
// Atomic<u32> counter
return m_day_count;
}
/*
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 chararacters [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++)
{
// 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,
GameScripting *scriptIface, IGameDef *gamedef,
const std::string &path_world) :
m_map(map),
m_script(scriptIface),
m_gamedef(gamedef),
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)
{
}
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;
}
}
Map & ServerEnvironment::getMap()
{
return *m_map;
}
ServerMap & ServerEnvironment::getServerMap()
{
return *m_map;
}
RemotePlayer *ServerEnvironment::getPlayer(const u16 peer_id)
{
return dynamic_cast<RemotePlayer *>(Environment::getPlayer(peer_id));
}
RemotePlayer *ServerEnvironment::getPlayer(const char* name)
{
return dynamic_cast<RemotePlayer *>(Environment::getPlayer(name));
}
bool ServerEnvironment::line_of_sight(v3f pos1, v3f pos2, float stepsize, v3s16 *p)
{
float distance = pos1.getDistanceFrom(pos2);
//calculate normalized direction vector
v3f normalized_vector = v3f((pos2.X - pos1.X)/distance,
(pos2.Y - pos1.Y)/distance,
(pos2.Z - pos1.Z)/distance);
//find out if there's a node on path between pos1 and pos2
for (float i = 1; i < distance; i += stepsize) {
v3s16 pos = floatToInt(v3f(normalized_vector.X * i,
normalized_vector.Y * i,
normalized_vector.Z * i) +pos1,BS);
MapNode n = getMap().getNodeNoEx(pos);
if(n.param0 != CONTENT_AIR) {
if (p) {
*p = pos;
}
return false;
}
}
return true;
}
void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason,
const std::string &str_reason, bool reconnect)
{
for (std::vector<Player*>::iterator it = m_players.begin();
it != m_players.end();
++it) {
((Server*)m_gamedef)->DenyAccessVerCompliant((*it)->peer_id,
(*it)->protocol_version, (AccessDeniedCode)reason,
str_reason, reconnect);
}
}
void ServerEnvironment::saveLoadedPlayers()
{
std::string players_path = m_path_world + DIR_DELIM "players";
fs::CreateDir(players_path);
for (std::vector<Player*>::iterator it = m_players.begin();
it != m_players.end();
++it) {
RemotePlayer *player = static_cast<RemotePlayer*>(*it);
if (player->checkModified()) {
|