/*
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 "map.h"
#include "mapsector.h"
#include "mapblock.h"
#include "main.h"
#include "filesys.h"
#include "voxel.h"
#include "porting.h"
#include "serialization.h"
#include "nodemetadata.h"
#include "settings.h"
#include "log.h"
#include "profiler.h"
#include "nodedef.h"
#include "gamedef.h"
#include "util/directiontables.h"
#include "util/mathconstants.h"
#include "rollback_interface.h"
#include "environment.h"
#include "emerge.h"
#include "mapgen_v6.h"
#include "biome.h"
#include "config.h"
#include "server.h"
#include "database.h"
#include "database-dummy.h"
#include "database-sqlite3.h"
#if USE_LEVELDB
#include "database-leveldb.h"
#endif
#if USE_REDIS
#include "database-redis.h"
#endif
#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
/*
SQLite format specification:
- Initially only replaces sectors/ and sectors2/
If map.sqlite does not exist in the save dir
or the block was not found in the database
the map will try to load from sectors folder.
In either case, map.sqlite will be created
and all future saves will save there.
Structure of map.sqlite:
Tables:
blocks
(PK) INT pos
BLOB data
*/
/*
Map
*/
Map::Map(std::ostream &dout, IGameDef *gamedef):
m_dout(dout),
m_gamedef(gamedef),
m_sector_cache(NULL)
{
}
Map::~Map()
{
/*
Free all MapSectors
*/
for(std::map<v2s16, MapSector*>::iterator i = m_sectors.begin();
i != m_sectors.end(); ++i)
{
delete i->second;
}
}
void Map::addEventReceiver(MapEventReceiver *event_receiver)
{
m_event_receivers.insert(event_receiver);
}
void Map::removeEventReceiver(MapEventReceiver *event_receiver)
{
m_event_receivers.erase(event_receiver);
}
void Map::dispatchEvent(MapEditEvent *event)
{
for(std::set<MapEventReceiver*>::iterator
i = m_event_receivers.begin();
i != m_event_receivers.end(); ++i)
{
(*i)->onMapEditEvent(event);
}
}
MapSector * Map::getSectorNoGenerateNoExNoLock(v2s16 p)
{
if(m_sector_cache != NULL && p == m_sector_cache_p){
MapSector * sector = m_sector_cache;
return sector;
}
std::map<v2s16, MapSector*>::iterator n = m_sectors.find(p);
if(n == m_sectors.end())
return NULL;
MapSector *sector = n->second;
// Cache the last result
m_sector_cache_p = p;
m_sector_cache = sector;
return sector;
}
MapSector * Map::getSectorNoGenerateNoEx(v2s16 p)
{
return getSectorNoGenerateNoExNoLock(p);
}
MapSector * Map::getSectorNoGenerate(v2s16 p)
{
MapSector *sector = getSectorNoGenerateNoEx(p);
if(sector == NULL)
throw InvalidPositionException();
return sector;
}
MapBlock * Map::getBlockNoCreateNoEx(v3s16 p3d)
{
v2s16 p2d(p3d.X, p3d.Z);
MapSector * sector = getSectorNoGenerateNoEx(p2d);
if(sector == NULL)
return NULL;
MapBlock *block = sector->getBlockNoCreateNoEx(p3d.Y);
return block;
}
MapBlock * Map::getBlockNoCreate(v3s16 p3d)
{
MapBlock *block = getBlockNoCreateNoEx(p3d);
if(block == NULL)
throw InvalidPositionException();
return block;
}
bool Map::isNodeUnderground(v3s16 p)
{
v3s16 blockpos = getNodeBlockPos(p);
try{
MapBlock * block = getBlockNoCreate(blockpos);
return block->getIsUnderground();
}
catch(InvalidPositionException &e)
{
return false;
}
}
bool Map::isValidPosition(v3s16 p)
{
v3s16 blockpos = getNodeBlockPos(p);
MapBlock *block = getBlockNoCreate(blockpos);
return (block != NULL);
}
// Returns a CONTENT_IGNORE node if not found
MapNode Map::getNodeNoEx(v3s16 p)
{
v3s16 blockpos = getNodeBlockPos(p);
MapBlock *block = getBlockNoCreateNoEx(blockpos);
if(block == NULL)
return MapNode(CONTENT_IGNORE);
v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
return block->getNodeNoCheck(relpos);
}
// throws InvalidPositionException if not found
MapNode Map::getNode(v3s16 p)
{
v3s16 blockpos = getNodeBlockPos(p);
MapBlock *block = getBlockNoCreateNoEx(blockpos);
if(block == NULL)
throw InvalidPositionException();
v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
return block->getNodeNoCheck(relpos);
}
// throws InvalidPositionException if not found
void Map::setNode(v3s16 p, MapNode & n)
{
v3s16 blockpos = getNodeBlockPos(p);
MapBlock *block = getBlockNoCreate(blockpos);
v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
// Never allow placing CONTENT_IGNORE, it fucks up stuff
if(n.getContent() == CONTENT_IGNORE){
errorstream<<"Map::setNode(): Not allowing to place CONTENT_IGNORE"
<<" while trying to replace \""
<<m_gamedef->ndef()->get(block->getNodeNoCheck(relpos)).name
<<"\" at "<<PP(p)<<" (block "<<PP(blockpos)<<")"<<std::endl;
debug_stacks_print_to(infostream);
return;
}
block->setNodeNoCheck(relpos, n);
}
/*
Goes recursively through the neighbours of the node.
Alters only transparent nodes.
If the lighting of the neighbour is lower than the lighting of
the node was (before changing it to 0 at the step before), the
lighting of the neighbour is set to 0 and then the same stuff
repeats for the neighbour.
The ending nodes of the routine are stored in light_sources.
This is useful when a light is removed. In such case, this
routine can be called for the light node and then again for
light_sources to re-light the area without the removed light.
values of from_nodes are lighting values.
*/
void Map::unspreadLight(enum LightBank bank,
std::map<v3s16, u8> & from_nodes,
std::set<v3s16> & light_sources,
std::map<v3s16, MapBlock*> & modified_blocks)
{
INodeDefManager *nodemgr = m_gamedef->ndef();
v3s16 dirs[6] = {
v3s16(0,0,1), // back
v3s16(0,1,0), // top
v3s16(1,0,0), // right
v3s16(0,0,-1), // front
v3s16(0,-1,0), // bottom
v3s16(-1,0,0), // left
};
if(from_nodes.size() == 0)
return;
u32 blockchangecount = 0;
std::map<v3s16, u8> unlighted_nodes;
/*
Initialize block cache
*/
v3s16 blockpos_last;
MapBlock *block = NULL;
// Cache this a bit, too
bool block_checked_in_modified = false;
for(std::map<v3s16, u8>::iterator j = from_nodes.begin();
j != from_nodes.end(); ++j)
{
v3s16 pos = j->first;
v3s16 blockpos = getNodeBlockPos(pos);
// Only fetch a new block if the block position has changed
try{
if(block == NULL || blockpos != blockpos_last){
block = getBlockNoCreate(blockpos);
blockpos_last = blockpos;
block_checked_in_modified = false;
blockchangecount++;
}
}
catch(InvalidPositionException &e)
{
continue;
}
if(block->isDummy())
continue;
// Calculate relative position in block
//v3s16 relpos = pos - blockpos_last * MAP_BLOCKSIZE;
// Get node straight from the block
//MapNode n = block->getNode(relpos);
u8 oldlight = j->second;
// Loop through 6 neighbors
for(u16 i=0; i<6; i++)
{
// Get the position of the neighbor node
v3s16 n2pos = pos + dirs[i];
// Get the block where the node is located
v3s16 blockpos = getNodeBlockPos(n2pos);
try
{
// Only fetch a new block if the block position has changed
try{
if(block == NULL || blockpos != blockpos_last){
block = getBlockNoCreate(blockpos);
blockpos_last = blockpos;
block_checked_in_modified = false;
blockchangecount++;
}
}
catch(InvalidPositionException &e)
{
continue;
}
// Calculate relative position in block
v3s16 relpos = n2pos - blockpos * MAP_BLOCKSIZE;
// Get node straight from the block
MapNode n2 = block->getNode(relpos);
bool changed = false;
//TODO: Optimize output by optimizing light_sources?
/*
If the neighbor is dimmer than what was specified
as oldlight (the light of the previous node)
*/
if(n2.getLight(bank, nodemgr) < oldlight)
{
/*
And the neighbor is transparent and it has some light
*/
if(nodemgr->get(n2).light_propagates
&& n2.getLight(bank, nodemgr) != 0)
{
/*
Set light to 0 and add to queue
*/
u8 current_light = n2.getLight(bank, nodemgr);
n2.setLight(bank, 0, nodemgr);
block->setNode(relpos, n2);
unlighted_nodes[n2pos] = current_light;
changed = true;
/*
Remove from light_sources if it is there
NOTE: This doesn't happen nearly at all
*/
/*if(light_sources.find(n2pos))
{
infostream<<"Removed from light_sources"<<std::endl;
light_sources.remove(n2pos);
}*/
}
/*// DEBUG
if(light_sources.find(n2pos) != NULL)
light_sources.remove(n2pos);*/
}
else{
light_sources.insert(n2pos);
}
// Add to modified_blocks
if(changed == true && block_checked_in_modified == false)
{
// If the block is not found in modified_blocks, add.
if(modified_blocks.find(blockpos) == modified_blocks.end())
{
modified_blocks[blockpos] = block;
}
block_checked_in_modified = true;
}
}
catch(InvalidPositionException &e)
{
continue;
}
}
}
/*infostream<<"unspreadLight(): Changed block "
<<blockchangecount<<" times"
<<" for "<<from_nodes.size()<<" nodes"
<<std::endl;*/
if(unlighted_nodes.size() > 0)
unspreadLight(bank, unlighted_nodes, light_sources, modified_blocks);
}
/*
A single-node wrapper of the above
*/
void Map::unLightNeighbors(enum LightBank bank,
v3s16 pos, u8 lightwas,
std::set<v3s16> & light_sources,
std::map<v3s16, MapBlock*> & modified_blocks)
{
std::map<v3s16, u8> from_nodes;
from_nodes[pos] = lightwas;
unspreadLight(bank, from_nodes, light_sources, modified_blocks);
}
/*
Lights neighbors of from_nodes, collects all them and then
goes on recursively.
*/
void Map::spreadLight(enum LightBank bank,
std::set<v3s16> & from_nodes,
std::map<v3s16, MapBlock*> & modified_blocks)
{
INodeDefManager *nodemgr = m_gamedef->ndef();
const v3s16 dirs[6] = {
v3s16(0,0,1), // back
v3s16(0,1,0), // top
v3s16(1,0,0), // right
v3s16(0,0,-1), // front
v3s16(0,-1,0), // bottom
v3s16(-1,0,0), // left
};
if(from_nodes.size() == 0)
return;
u32 blockchangecount = 0;
std::set<v3s16> lighted_nodes;
/*
Initialize block cache
*/
v3s16 blockpos_last;
MapBlock *block = NULL;
// Cache this a bit, too
bool block_checked_in_modified = false;
for(std::set<v3s16>::iterator j = from_nodes.begin();
j != from_nodes.end(); ++j)
{
v3s16 pos = *j;
v3s16 blockpos = getNodeBlockPos(pos);
// Only fetch a new block if the block position has changed
try{
if(block == NULL || blockpos != blockpos_last){
block = getBlockNoCreate(blockpos);
blockpos_last = blockpos;
block_checked_in_modified = false;
blockchangecount++;
}
}
catch(InvalidPositionException &e)
{
continue;
}
if(block->isDummy())
continue;
// Calculate relative position in block
v3s16 relpos = pos - blockpos_last * MAP_BLOCKSIZE;
// Get node straight from the block
MapNode n = block->getNode(relpos);
u8 oldlight = n.getLight(bank, nodemgr);
u8 newlight = diminish_light(oldlight);
// Loop through 6 neighbors
for(u16 i=0; i<6; i++){
// Get the position of the neighbor node
v3s16 n2pos = pos + dirs[i];
// Get the block where the node is located
v3s16 blockpos = getNodeBlockPos(n2pos);
try
{
// Only fetch a new block if the block position has changed
try{
if(block == NULL || blockpos != blockpos_last){
block = getBlockNoCreate(blockpos);
blockpos_last = blockpos;
block_checked_in_modified = false;
blockchangecount++;
}
}
catch(InvalidPositionException &e)
{
continue;
}
// Calculate relative position in block
v3s16 relpos = n2pos - blockpos * MAP_BLOCKSIZE;
// Get node straight from the block
MapNode n2 = block->getNode(relpos);
bool changed = false;
/*
If the neighbor is brighter than the current node,
add to list (it will light up this node on its turn)
*/
if(n2.getLight(bank, nodemgr) > undiminish_light(oldlight))
{
lighted_nodes.insert(n2pos);
changed = true;
}
/*
If the neighbor is dimmer than how much light this node
would spread on it, add to list
*/
if(n2.getLight(bank, nodemgr) < newlight)
{
if(nodemgr->get(n2).light_propagates)
{
n2.setLight(bank, newlight, nodemgr);
block->setNode(relpos, n2);
lighted_nodes.insert(n2pos);
changed = true;
}
}
// Add to modified_blocks
if(changed == true && block_checked_in_modified == false)
{
// If the block is not found in modified_blocks, add.
if(modified_blocks.find(blockpos) == modified_blocks.end())
{
modified_blocks[blockpos] = block;
}
block_checked_in_modified = true;
}
}
catch(InvalidPositionException &e)
{
continue;
}
}
}
/*infostream<<"spreadLight(): Changed block "
<<blockchangecount<<" times"
<<" for "<<from_nodes.size()<<" nodes"
<<std::endl;*/
if(lighted_nodes.size() > 0)
spreadLight(bank, lighted_nodes, modified_blocks);
}
/*
A single-node source variation of the above.
*/
void Map::lightNeighbors(enum LightBank bank,
v3s16 pos,
std::map<v3s16, MapBlock*> & modified_blocks)
{
std::set<v3s16> from_nodes;
from_nodes.insert(pos);
spreadLight(bank, from_nodes, modified_blocks);
}
v3s16 Map::getBrightestNeighbour(enum LightBank bank, v3s16 p)
{
INodeDefManager *nodemgr = m_gamedef->ndef();
v3s16 dirs[6] = {
v3s16(0,0,1), // back
v3s16(0,1,0), // top
v3s16(1,0,0), // right
v3s16(0,0,-1), // front
v3s16(0,-1,0), // bottom
v3s16(-1,0,0), // left
};
u8 brightest_light = 0;
v3s16 brightest_pos(0,0,0);
bool found_something = false;
// Loop through 6 neighbors
for(u16 i=0; i<6; i++){
// Get the position of the neighbor node
v3s16 n2pos = p + dirs[i];
MapNode n2;
try{
n2 = getNode(n2pos);
}
catch(InvalidPositionException &e)
{
continue;
}
if(n2.getLight(bank, nodemgr) > brightest_light || found_something == false){
brightest_light = n2.getLight(bank, nodemgr);
brightest_pos = n2pos;
found_something = true;
}
}
if(found_something == false)
throw InvalidPositionException();
return brightest_pos;
}
/*
Propagates sunlight down from a node.
Starting point gets sunlight.
Returns the lowest y value of where the sunlight went.
Mud is turned into grass in where the sunlight stops.
*/
s16 Map::propagateSunlight(v3s16 start,
std::map<v3s16, MapBlock*> & modified_blocks)
{
INodeDefManager *nodemgr = m_gamedef->ndef();
s16 y = start.Y;
for(; ; y--)
{
v3s16 pos(start.X, y, start.Z);
v3s16 blockpos = getNodeBlockPos(pos);
MapBlock *block;
try{
block = getBlockNoCreate(blockpos);
}
catch(InvalidPositionException &e)
{
break;
}
v3s16 relpos = pos - blockpos*MAP_BLOCKSIZE;
MapNode n = block->getNode(relpos);
if(nodemgr->get(n).sunlight_propagates)
{
n.setLight(LIGHTBANK_DAY, LIGHT_SUN, nodemgr);
block->setNode(relpos, n);
modified_blocks[blockpos] = block;
}
else
{
// Sunlight goes no further
break;
}
}
return y + 1;
}
void Map::updateLighting(enum LightBank bank,
std::map<v3s16, MapBlock*> & a_blocks,
std::map<v3s16, MapBlock*> & modified_blocks)
{
INodeDefManager *nodemgr = m_gamedef->ndef();
/*m_dout<<DTIME<<"Map::updateLighting(): "
<<a_blocks.size()<<" blocks."<<std::endl;*/
//TimeTaker timer("updateLighting");
// For debugging
//bool debug=true;
//u32 count_was = modified_blocks.size();
std::map<v3s16, MapBlock*> blocks_to_update;
std::set<v3s16> light_sources;
std::map<v3s16, u8> unlight_from;
int num_bottom_invalid = 0;
{
//TimeTaker t("first stuff");
for(std::map<v3s16, MapBlock*>::iterator i = a_blocks.begin();
i != a_blocks.end(); ++i)
{
MapBlock *block = i->second;
for(;;)
{
// Don't bother with dummy blocks.
if(block->isDummy())
break;
v3s16 pos = block->getPos();
v3s16 posnodes = block->getPosRelative();
modified_blocks[pos] = block;
blocks_to_update[pos] = block;
/*
Clear all light from block
*/
for(s16 z=0; z<MAP_BLOCKSIZE; z++)
for(s16 x=0; x<MAP_BLOCKSIZE; x++)
for(s16 y=0; y<MAP_BLOCKSIZE; y++)
{
try{
v3s16 p(x,y,z);
MapNode n = block->getNode(p);
u8 oldlight = n.getLight(bank, nodemgr);
n.setLight(bank, 0, nodemgr);
block->setNode(p, n);
// If node sources light, add to list
u8 source = nodemgr->get(n).light_source;
if(source != 0)
light_sources.insert(p + posnodes);
// Collect borders for unlighting
if((x==0 || x == MAP_BLOCKSIZE-1
|| y==0 || y == MAP_BLOCKSIZE-1
|| z==0 || z == MAP_BLOCKSIZE-1)
&& oldlight != 0)
{
v3s16 p_map = p + posnodes;
unlight_from[p_map] = oldlight;
}
}
catch(InvalidPositionException &e)
{
/*
This would happen when dealing with a
dummy block.
*/
//assert(0);
infostream<<"updateLighting(): InvalidPositionException"
<<std::endl;
}
}
if(bank == LIGHTBANK_DAY)
{
bool bottom_valid = block->propagateSunlight(light_sources);
if(!bottom_valid)
num_bottom_invalid++;
// If bottom is valid, we're done.
if(bottom_valid)
break;
}
else if(bank == LIGHTBANK_NIGHT)
{
// For night lighting, sunlight is not propagated
break;
}
else
{
// Invalid lighting bank
assert(0);
}
/*infostream<<"Bottom for sunlight-propagated block ("
<<pos.X<<","<<pos.Y<<","<<pos.Z<<") not valid"
<<std::endl;*/
// Bottom sunlight is not valid; get the block and loop to it
pos.Y--;
try{
block = getBlockNoCreate(pos);
}
catch(InvalidPositionException &e)
{
assert(0);
}
}
}
}
/*
Enable this to disable proper lighting for speeding up map
generation for testing or whatever
*/
#if 0
//if(g_settings->get(""))
{
core::map<v3s16, MapBlock*>::Iterator i;
i = blocks_to_update.getIterator();
for(; i.atEnd() == false; i++)
{
MapBlock *block = i.getNode()->getValue();
v3s16 p = block->getPos();
block->setLightingExpired(false);
}
return;
}
#endif
#if 1
{
//TimeTaker timer("unspreadLight");
unspreadLight(bank, unlight_from, light_sources, modified_blocks);
}
/*if(debug)
{
u32 diff = modified_blocks.size() - count_was;
count_was = modified_blocks.size();
infostream<<"unspreadLight modified "<<diff<<std::endl;
}*/
{
//TimeTaker timer("spreadLight");
spreadLight(bank, light_sources, modified_blocks);
}
/*if(debug)
|