Newer
Older
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
Perttu Ahola
committed
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
Perttu Ahola
committed
GNU Lesser General Public License for more details.
Perttu Ahola
committed
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 "serialization.h"
#include "log.h"
#include "util/directiontables.h"
#include "environment.h"
#include "database.h"
#include "database-dummy.h"
#include "database-sqlite3.h"
#if USE_LEVELDB
#include "database-leveldb.h"
#endif
#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
Perttu Ahola
committed
/*
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
Perttu Ahola
committed
*/
Perttu Ahola
committed
Map::Map(std::ostream &dout, IGameDef *gamedef):
Perttu Ahola
committed
m_gamedef(gamedef),
Perttu Ahola
committed
/*m_sector_mutex.Init();
assert(m_sector_mutex.IsInitialized());*/
for(std::map<v2s16, MapSector*>::iterator i = m_sectors.begin();
i != m_sectors.end(); ++i)
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)
Perttu Ahola
committed
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);
Perttu Ahola
committed
return NULL;
// Cache the last result
m_sector_cache_p = p;
m_sector_cache = sector;
return sector;
}
Perttu Ahola
committed
MapSector * Map::getSectorNoGenerateNoEx(v2s16 p)
{
return getSectorNoGenerateNoExNoLock(p);
}
MapSector * Map::getSectorNoGenerate(v2s16 p)
{
MapSector *sector = getSectorNoGenerateNoEx(p);
if(sector == NULL)
throw InvalidPositionException();
Perttu Ahola
committed
return sector;
}
MapSector * sector = getSectorNoGenerateNoEx(p2d);
if(sector == NULL)
MapBlock *block = getBlockNoCreateNoEx(p3d);
if(block == NULL)
throw InvalidPositionException();
Perttu Ahola
committed
return block;
Perttu Ahola
committed
bool Map::isNodeUnderground(v3s16 p)
{
v3s16 blockpos = getNodeBlockPos(p);
try{
MapBlock * block = getBlockNoCreate(blockpos);
return block->getIsUnderground();
}
catch(InvalidPositionException &e)
{
return false;
}
}
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
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;
Perttu Ahola
committed
// 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;
Perttu Ahola
committed
debug_stacks_print_to(infostream);
Perttu Ahola
committed
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.
*/
std::map<v3s16, u8> & from_nodes,
std::set<v3s16> & light_sources,
std::map<v3s16, MapBlock*> & modified_blocks)
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
};
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)
// 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
Perttu Ahola
committed
Perttu Ahola
committed
// Loop through 6 neighbors
for(u16 i=0; i<6; i++)
{
// Get the position of the neighbor node
v3s16 n2pos = pos + dirs[i];
Perttu Ahola
committed
// 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;
}
Perttu Ahola
committed
// Calculate relative position in block
v3s16 relpos = n2pos - blockpos * MAP_BLOCKSIZE;
// Get node straight from the block
MapNode n2 = block->getNode(relpos);
Perttu Ahola
committed
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)
*/
{
/*
And the neighbor is transparent and it has some light
*/
if(nodemgr->get(n2).light_propagates
&& n2.getLight(bank, nodemgr) != 0)
u8 current_light = n2.getLight(bank, nodemgr);
n2.setLight(bank, 0, nodemgr);
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;
Perttu Ahola
committed
/*// DEBUG
if(light_sources.find(n2pos) != NULL)
light_sources.remove(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;*/
Perttu Ahola
committed
unspreadLight(bank, unlighted_nodes, light_sources, modified_blocks);
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.
*/
std::set<v3s16> & from_nodes,
std::map<v3s16, MapBlock*> & modified_blocks)
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;
Perttu Ahola
committed
/*
Initialize block cache
*/
v3s16 blockpos_last;
MapBlock *block = NULL;
// Cache this a bit, too
bool block_checked_in_modified = false;
Perttu Ahola
committed
for(std::set<v3s16>::iterator j = from_nodes.begin();
j != from_nodes.end(); ++j)
Perttu Ahola
committed
// 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 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];
Perttu Ahola
committed
// 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;
}
Perttu Ahola
committed
// Calculate relative position in block
v3s16 relpos = n2pos - blockpos * MAP_BLOCKSIZE;
// Get node straight from the block
MapNode n2 = block->getNode(relpos);
Perttu Ahola
committed
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))
changed = true;
}
/*
If the neighbor is dimmer than how much light this node
would spread on it, add to list
*/
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;*/
Perttu Ahola
committed
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)
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
};
Perttu Ahola
committed
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();
Perttu Ahola
committed
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.
std::map<v3s16, MapBlock*> & modified_blocks)
s16 y = start.Y;
for(; ; y--)
{
v3s16 pos(start.X, y, start.Z);
Perttu Ahola
committed
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);
modified_blocks[blockpos] = block;
else
{
// Sunlight goes no further
std::map<v3s16, MapBlock*> & a_blocks,
std::map<v3s16, MapBlock*> & modified_blocks)
<<a_blocks.size()<<" blocks."<<std::endl;*/
Perttu Ahola
committed
//TimeTaker timer("updateLighting");
Perttu Ahola
committed
//bool debug=true;
//u32 count_was = modified_blocks.size();
Perttu Ahola
committed
std::map<v3s16, MapBlock*> blocks_to_update;
Perttu Ahola
committed
std::map<v3s16, u8> unlight_from;
Perttu Ahola
committed
for(std::map<v3s16, MapBlock*>::iterator i = a_blocks.begin();
i != a_blocks.end(); ++i)
Perttu Ahola
committed
for(;;)
{
// Don't bother with dummy blocks.
if(block->isDummy())
break;
Perttu Ahola
committed
v3s16 posnodes = block->getPosRelative();
modified_blocks[pos] = block;
blocks_to_update[pos] = block;
Perttu Ahola
committed
/*
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++)
{
Perttu Ahola
committed
MapNode n = block->getNode(p);
u8 oldlight = n.getLight(bank, nodemgr);
n.setLight(bank, 0, nodemgr);
block->setNode(p, n);
Perttu Ahola
committed
// If node sources light, add to list
u8 source = nodemgr->get(n).light_source;
if(source != 0)
light_sources.insert(p + posnodes);
|| y==0 || y == MAP_BLOCKSIZE-1
|| z==0 || z == MAP_BLOCKSIZE-1)
v3s16 p_map = p + posnodes;
}
}
catch(InvalidPositionException &e)
{
/*
This would happen when dealing with a
dummy block.
*/
//assert(0);
infostream<<"updateLighting(): InvalidPositionException"
Perttu Ahola
committed
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)
{
Perttu Ahola
committed
// For night lighting, sunlight is not propagated
Perttu Ahola
committed
// Invalid lighting bank
Perttu Ahola
committed
/*infostream<<"Bottom for sunlight-propagated block ("
<<pos.X<<","<<pos.Y<<","<<pos.Z<<") not valid"
<<std::endl;*/
Perttu Ahola
committed
// Bottom sunlight is not valid; get the block and loop to it
pos.Y--;
try{
block = getBlockNoCreate(pos);
}
catch(InvalidPositionException &e)
{
assert(0);
}
Perttu Ahola
committed
/*
Enable this to disable proper lighting for speeding up map
generation for testing or whatever
*/
#if 0
{
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
unspreadLight(bank, unlight_from, light_sources, modified_blocks);
Perttu Ahola
committed
{
u32 diff = modified_blocks.size() - count_was;
count_was = modified_blocks.size();
infostream<<"unspreadLight modified "<<diff<<std::endl;
spreadLight(bank, light_sources, modified_blocks);
Perttu Ahola
committed
{
u32 diff = modified_blocks.size() - count_was;
count_was = modified_blocks.size();
infostream<<"spreadLight modified "<<diff<<std::endl;
Perttu Ahola
committed
{
//MapVoxelManipulator vmanip(this);
Perttu Ahola
committed
// Make a manual voxel manipulator and load all the blocks
// that touch the requested blocks
ManualMapVoxelManipulator vmanip(this);
{
//TimeTaker timer("initialEmerge");
core::map<v3s16, MapBlock*>::Iterator i;
Perttu Ahola
committed
i = blocks_to_update.getIterator();
for(; i.atEnd() == false; i++)
{
MapBlock *block = i.getNode()->getValue();
v3s16 p = block->getPos();
Perttu Ahola
committed
Perttu Ahola
committed
// Add all surrounding blocks
vmanip.initialEmerge(p - v3s16(1,1,1), p + v3s16(1,1,1));
Perttu Ahola
committed
/*
Add all surrounding blocks that have up-to-date lighting
NOTE: This doesn't quite do the job (not everything
Perttu Ahola
committed
appropriate is lighted)
Perttu Ahola
committed
*/
/*for(s16 z=-1; z<=1; z++)
for(s16 y=-1; y<=1; y++)
for(s16 x=-1; x<=1; x++)
{
Perttu Ahola
committed
v3s16 p2 = p + v3s16(x,y,z);
MapBlock *block = getBlockNoCreateNoEx(p2);
Perttu Ahola
committed
if(block == NULL)
continue;
if(block->isDummy())
continue;
if(block->getLightingExpired())
continue;
Perttu Ahola
committed
vmanip.initialEmerge(p2, p2);
Perttu Ahola
committed
}*/
Perttu Ahola
committed
Perttu Ahola
committed
// Lighting of block will be updated completely
block->setLightingExpired(false);
Perttu Ahola
committed
vmanip.unspreadLight(bank, unlight_from, light_sources, nodemgr);
vmanip.blitBack(modified_blocks);
}
/*infostream<<"emerge_time="<<emerge_time<<std::endl;
//m_dout<<"Done ("<<getTimestamp()<<")"<<std::endl;
}
void Map::updateLighting(std::map<v3s16, MapBlock*> & a_blocks,
std::map<v3s16, MapBlock*> & modified_blocks)
{
updateLighting(LIGHTBANK_DAY, a_blocks, modified_blocks);
updateLighting(LIGHTBANK_NIGHT, a_blocks, modified_blocks);
Perttu Ahola
committed
/*
Update information about whether day and night light differ
*/
for(std::map<v3s16, MapBlock*>::iterator
i = modified_blocks.begin();
i != modified_blocks.end(); ++i)
Perttu Ahola
committed
block->expireDayNightDiff();
/*
*/
void Map::addNodeAndUpdate(v3s16 p, MapNode n,
std::map<v3s16, MapBlock*> &modified_blocks)
INodeDefManager *ndef = m_gamedef->ndef();
Perttu Ahola
committed
m_dout<<DTIME<<"Map::addNodeAndUpdate(): p=("
/*
From this node to nodes underneath:
If lighting is sunlight (1.0), unlight neighbours and
set lighting to 0.
Else discontinue.
*/
v3s16 toppos = p + v3s16(0,1,0);
/*
Collect old node for rollback
*/
RollbackNode rollback_oldnode(this, p, m_gamedef);
/*
If there is a node at top and it doesn't have sunlight,
there has not been any sunlight going down.
Otherwise there probably is.
*/
try{
MapNode topnode = getNode(toppos);
if(topnode.getLight(LIGHTBANK_DAY, ndef) != LIGHT_SUN)
node_under_sunlight = false;
}
catch(InvalidPositionException &e)
{
}
Perttu Ahola
committed
/*
Remove all light that has come out of this node
*/
enum LightBank banks[] =
{
LIGHTBANK_DAY,
LIGHTBANK_NIGHT
};
for(s32 i=0; i<2; i++)
{
enum LightBank bank = banks[i];
u8 lightwas = getNode(p).getLight(bank, ndef);
// Add the block of the added node to modified_blocks
v3s16 blockpos = getNodeBlockPos(p);
MapBlock * block = getBlockNoCreate(blockpos);
assert(block != NULL);
modified_blocks[blockpos] = block;
Perttu Ahola
committed
Perttu Ahola
committed
// Unlight neighbours of node.
// This means setting light of all consequent dimmer nodes