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;
}
}
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
// to 0.
// This also collects the nodes at the border which will spread
// light again into this.
unLightNeighbors(bank, p, lightwas, light_sources, modified_blocks);
Perttu Ahola
committed
/*
If node lets sunlight through and is under sunlight, it has
sunlight too.
*/
if(node_under_sunlight && ndef->get(n).sunlight_propagates)
n.setLight(LIGHTBANK_DAY, LIGHT_SUN, ndef);
Perttu Ahola
committed
/*
Remove node metadata
*/
removeNodeMetadata(p);
Perttu Ahola
committed
/*
Set the node on the map
*/
Perttu Ahola
committed
If node is under sunlight and doesn't let sunlight through,
take all sunlighted nodes under it and clear light from them
and from where the light has been spread.
TODO: This could be optimized by mass-unlighting instead
Perttu Ahola
committed
of looping
if(node_under_sunlight && !ndef->get(n).sunlight_propagates)
{
s16 y = p.Y - 1;
for(;; y--){
//m_dout<<DTIME<<"y="<<y<<std::endl;
v3s16 n2pos(p.X, y, p.Z);
Perttu Ahola
committed
MapNode n2;
try{
n2 = getNode(n2pos);
}
catch(InvalidPositionException &e)
{
break;
}
if(n2.getLight(LIGHTBANK_DAY, ndef) == LIGHT_SUN)
n2pos, n2.getLight(LIGHTBANK_DAY, ndef),
n2.setLight(LIGHTBANK_DAY, 0, ndef);
setNode(n2pos, n2);
}
else
break;
}
}
for(s32 i=0; i<2; i++)
{
enum LightBank bank = banks[i];
Perttu Ahola
committed
/*
Spread light from all nodes that might be capable of doing so
*/
spreadLight(bank, light_sources, modified_blocks);
}
/*
Update information about whether day and night light differ
*/
for(std::map<v3s16, MapBlock*>::iterator
i = modified_blocks.begin();
i != modified_blocks.end(); ++i)
i->second->expireDayNightDiff();
/*
Report for rollback
*/
if(m_gamedef->rollback())
{
RollbackNode rollback_newnode(this, p, m_gamedef);
RollbackAction action;
action.setSetNode(p, rollback_oldnode, rollback_newnode);
m_gamedef->rollback()->reportAction(action);
}
/*
Add neighboring liquid nodes and the node itself if it is
liquid (=water node was added) to transform queue.
*/
v3s16 dirs[7] = {
v3s16(0,0,0), // self
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
};
for(u16 i=0; i<7; i++)
{
try
{
v3s16 p2 = p + dirs[i];
Perttu Ahola
committed
MapNode n2 = getNode(p2);
if(ndef->get(n2).isLiquid() || n2.getContent() == CONTENT_AIR)
{
m_transforming_liquid.push_back(p2);
}
Perttu Ahola
committed
}catch(InvalidPositionException &e)
{
}
}
}
/*
*/
void Map::removeNodeAndUpdate(v3s16 p,
std::map<v3s16, MapBlock*> &modified_blocks)
INodeDefManager *ndef = m_gamedef->ndef();
/*PrintInfo(m_dout);
m_dout<<DTIME<<"Map::removeNodeAndUpdate(): p=("
<<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
Perttu Ahola
committed
Perttu Ahola
committed
Perttu Ahola
committed
/*
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 will be no sunlight going down.
*/
try{
MapNode topnode = getNode(toppos);
if(topnode.getLight(LIGHTBANK_DAY, ndef) != LIGHT_SUN)
node_under_sunlight = false;
}
catch(InvalidPositionException &e)
{
}
enum LightBank banks[] =
{
LIGHTBANK_DAY,
LIGHTBANK_NIGHT
};
for(s32 i=0; i<2; i++)
{
enum LightBank bank = banks[i];
Perttu Ahola
committed
/*
Unlight neighbors (in case the node is a light source)
*/
unLightNeighbors(bank, p,
getNode(p).getLight(bank, ndef),
/*
Remove node metadata
*/
removeNodeMetadata(p);
Remove the node.
This also clears the lighting.
Perttu Ahola
committed
for(s32 i=0; i<2; i++)
{
enum LightBank bank = banks[i];
Perttu Ahola
committed
/*
Recalculate lighting
*/
spreadLight(bank, light_sources, modified_blocks);
}
// Add the block of the removed node to modified_blocks
v3s16 blockpos = getNodeBlockPos(p);
MapBlock * block = getBlockNoCreate(blockpos);
assert(block != NULL);
modified_blocks[blockpos] = block;
/*
If the removed node was under sunlight, propagate the
sunlight down from it and then light all neighbors
of the propagated blocks.
*/
if(node_under_sunlight)
{
s16 ybottom = propagateSunlight(p, modified_blocks);
/*m_dout<<DTIME<<"Node was under sunlight. "
"Propagating sunlight";
m_dout<<DTIME<<" -> ybottom="<<ybottom<<std::endl;*/
s16 y = p.Y;
for(; y >= ybottom; y--)
{
v3s16 p2(p.X, y, p.Z);
/*m_dout<<DTIME<<"lighting neighbors of node ("
<<p2.X<<","<<p2.Y<<","<<p2.Z<<")"
<<std::endl;*/
lightNeighbors(LIGHTBANK_DAY, p2, modified_blocks);
}
}
else
{
// Set the lighting of this node to 0
// TODO: Is this needed? Lighting is cleared up there already.
n.setLight(LIGHTBANK_DAY, 0, ndef);
setNode(p, n);
}
catch(InvalidPositionException &e)
{
for(s32 i=0; i<2; i++)
{
enum LightBank bank = banks[i];
Perttu Ahola
committed
// Get the brightest neighbour node and propagate light from it
v3s16 n2p = getBrightestNeighbour(bank, p);
try{
lightNeighbors(bank, n2p, modified_blocks);
}
catch(InvalidPositionException &e)
{
}
/*
Update information about whether day and night light differ
*/
for(std::map<v3s16, MapBlock*>::iterator
i = modified_blocks.begin();
i != modified_blocks.end(); ++i)
i->second->expireDayNightDiff();
/*
Report for rollback
*/
if(m_gamedef->rollback())
{
RollbackNode rollback_newnode(this, p, m_gamedef);
RollbackAction action;
action.setSetNode(p, rollback_oldnode, rollback_newnode);
m_gamedef->rollback()->reportAction(action);
}
Add neighboring liquid nodes and this node to transform queue.
(it's vital for the node itself to get updated last.)
v3s16 dirs[7] = {
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
v3s16(0,0,0), // self
for(u16 i=0; i<7; i++)
{
try
{
v3s16 p2 = p + dirs[i];
Perttu Ahola
committed
MapNode n2 = getNode(p2);
if(ndef->get(n2).isLiquid() || n2.getContent() == CONTENT_AIR)
{
m_transforming_liquid.push_back(p2);
}
Perttu Ahola
committed
}catch(InvalidPositionException &e)
{
}
}
bool Map::addNodeWithEvent(v3s16 p, MapNode n)
{
MapEditEvent event;
event.type = MEET_ADDNODE;
event.p = p;
event.n = n;
bool succeeded = true;
try{
std::map<v3s16, MapBlock*> modified_blocks;
addNodeAndUpdate(p, n, modified_blocks);
// Copy modified_blocks to event
for(std::map<v3s16, MapBlock*>::iterator
i = modified_blocks.begin();
i != modified_blocks.end(); ++i)
}
}
catch(InvalidPositionException &e){
succeeded = false;
}
dispatchEvent(&event);
return succeeded;
}
bool Map::removeNodeWithEvent(v3s16 p)
{
MapEditEvent event;
event.type = MEET_REMOVENODE;
event.p = p;
bool succeeded = true;
try{
std::map<v3s16, MapBlock*> modified_blocks;
removeNodeAndUpdate(p, modified_blocks);
// Copy modified_blocks to event
for(std::map<v3s16, MapBlock*>::iterator
i = modified_blocks.begin();
i != modified_blocks.end(); ++i)
}
}
catch(InvalidPositionException &e){
succeeded = false;
}
dispatchEvent(&event);
return succeeded;
}
Perttu Ahola
committed
bool Map::getDayNightDiff(v3s16 blockpos)
{
try{
v3s16 p = blockpos + v3s16(0,0,0);
MapBlock *b = getBlockNoCreate(p);
Perttu Ahola
committed
if(b->getDayNightDiff())
return true;
}
catch(InvalidPositionException &e){}
v3s16 p = blockpos + v3s16(-1,0,0);
Perttu Ahola
committed
if(b->getDayNightDiff())
return true;
}
catch(InvalidPositionException &e){}
try{
v3s16 p = blockpos + v3s16(0,-1,0);
Perttu Ahola
committed
if(b->getDayNightDiff())
return true;
}
catch(InvalidPositionException &e){}
try{
v3s16 p = blockpos + v3s16(0,0,-1);
Perttu Ahola
committed
if(b->getDayNightDiff())
// Trailing edges
try{
v3s16 p = blockpos + v3s16(1,0,0);
MapBlock *b = getBlockNoCreate(p);
Perttu Ahola
committed
if(b->getDayNightDiff())
return true;
}
catch(InvalidPositionException &e){}
try{
v3s16 p = blockpos + v3s16(0,1,0);
MapBlock *b = getBlockNoCreate(p);
Perttu Ahola
committed
if(b->getDayNightDiff())
return true;
}
catch(InvalidPositionException &e){}
try{
v3s16 p = blockpos + v3s16(0,0,1);
MapBlock *b = getBlockNoCreate(p);
Perttu Ahola
committed
if(b->getDayNightDiff())
return true;
}
catch(InvalidPositionException &e){}
void Map::timerUpdate(float dtime, float unload_timeout,
std::list<v3s16> *unloaded_blocks)
bool save_before_unloading = (mapType() == MAPTYPE_SERVER);
Perttu Ahola
committed
// Profile modified reasons
Profiler modprofiler;
std::list<v2s16> sector_deletion_queue;
u32 deleted_blocks_count = 0;
u32 saved_blocks_count = 0;
Perttu Ahola
committed
beginSave();
for(std::map<v2s16, MapSector*>::iterator si = m_sectors.begin();
si != m_sectors.end(); ++si)
Perttu Ahola
committed
bool all_blocks_deleted = true;
Perttu Ahola
committed
sector->getBlocks(blocks);
for(std::list<MapBlock*>::iterator i = blocks.begin();
i != blocks.end(); ++i)
Perttu Ahola
committed
{
block->incrementUsageTimer(dtime);
if(block->refGet() == 0 && block->getUsageTimer() > unload_timeout)
{
v3s16 p = block->getPos();
// Save if modified
if(block->getModified() != MOD_STATE_CLEAN
&& save_before_unloading)
{
Perttu Ahola
committed
modprofiler.add(block->getModifiedReason(), 1);
saveBlock(block);
saved_blocks_count++;
}
// Delete from memory
sector->deleteBlock(block);
if(unloaded_blocks)
unloaded_blocks->push_back(p);
deleted_blocks_count++;
}
else
{
all_blocks_deleted = false;
Perttu Ahola
committed
}
if(all_blocks_deleted)
{
sector_deletion_queue.push_back(si->first);
Perttu Ahola
committed
}
Perttu Ahola
committed
endSave();
// Finally delete the empty sectors
deleteSectors(sector_deletion_queue);
if(deleted_blocks_count != 0)
{
PrintInfo(infostream); // ServerMap/ClientMap:
infostream<<"Unloaded "<<deleted_blocks_count
<<" blocks from memory";
if(save_before_unloading)
infostream<<", of which "<<saved_blocks_count<<" were written";
infostream<<", "<<block_count_all<<" blocks in memory";
infostream<<"."<<std::endl;
Perttu Ahola
committed
if(saved_blocks_count != 0){
PrintInfo(infostream); // ServerMap/ClientMap:
infostream<<"Blocks modified by: "<<std::endl;
modprofiler.print(infostream);
}
void Map::unloadUnreferencedBlocks(std::list<v3s16> *unloaded_blocks)
{
timerUpdate(0.0, -1.0, unloaded_blocks);
}
void Map::deleteSectors(std::list<v2s16> &list)
for(std::list<v2s16>::iterator j = list.begin();
j != list.end(); ++j)
// If sector is in sector cache, remove it from there
if(m_sector_cache == sector)
m_sector_cache = NULL;
// Remove from map and delete
delete sector;
void Map::unloadUnusedData(float timeout,
core::list<v3s16> *deleted_blocks)
{
core::list<v2s16> sector_deletion_queue;
u32 deleted_blocks_count = 0;
u32 saved_blocks_count = 0;
Perttu Ahola
committed
core::map<v2s16, MapSector*>::Iterator si = m_sectors.getIterator();
for(; si.atEnd() == false; si++)
{
MapSector *sector = si.getNode()->getValue();
bool all_blocks_deleted = true;
core::list<MapBlock*> blocks;
sector->getBlocks(blocks);
for(core::list<MapBlock*>::Iterator i = blocks.begin();
i != blocks.end(); i++)
{
MapBlock *block = (*i);
Perttu Ahola
committed
if(block->getUsageTimer() > timeout)
{
// Save if modified
if(block->getModified() != MOD_STATE_CLEAN)
Perttu Ahola
committed
saveBlock(block);
saved_blocks_count++;
}
// Delete from memory
sector->deleteBlock(block);
deleted_blocks_count++;
Perttu Ahola
committed
}
else
{
all_blocks_deleted = false;
}
}
if(all_blocks_deleted)
{
sector_deletion_queue.push_back(si.getNode()->getKey());
}
}
deleteSectors(sector_deletion_queue);
Perttu Ahola
committed
infostream<<"Map: Unloaded "<<deleted_blocks_count<<" blocks from memory"
<<", of which "<<saved_blocks_count<<" were wr."
<<std::endl;
Perttu Ahola
committed
//return sector_deletion_queue.getSize();
//return deleted_blocks_count;
void Map::PrintInfo(std::ostream &out)
{
out<<"Map: ";
}
#define WATER_DROP_BOOST 4
enum NeighborType {
NEIGHBOR_UPPER,
NEIGHBOR_SAME_LEVEL,
NEIGHBOR_LOWER
};
struct NodeNeighbor {
MapNode n;
NeighborType t;
v3s16 p;
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
void Map::transforming_liquid_add(v3s16 p) {
m_transforming_liquid.push_back(p);
}
s32 Map::transforming_liquid_size() {
return m_transforming_liquid.size();
}
const v3s16 g_7dirs[7] =
{
// +right, +top, +back
v3s16( 0,-1, 0), // bottom
v3s16( 0, 0, 0), // self
v3s16( 0, 0, 1), // back
v3s16( 0, 0,-1), // front
v3s16( 1, 0, 0), // right
v3s16(-1, 0, 0), // left
v3s16( 0, 1, 0) // top
};
#define D_BOTTOM 0
#define D_TOP 6
#define D_SELF 1
void Map::transformLiquidsFinite(std::map<v3s16, MapBlock*> & modified_blocks)
{
INodeDefManager *nodemgr = m_gamedef->ndef();
DSTACK(__FUNCTION_NAME);
//TimeTaker timer("transformLiquids()");
u32 loopcount = 0;
u32 initial_size = m_transforming_liquid.size();
u8 relax = g_settings->getS16("liquid_relax");
bool fast_flood = g_settings->getS16("liquid_fast_flood");
int water_level = g_settings->getS16("water_level");
// list of nodes that due to viscosity have not reached their max level height
UniqueQueue<v3s16> must_reflow, must_reflow_second;
// List of MapBlocks that will require a lighting update (due to lava)
std::map<v3s16, MapBlock*> lighting_modified_blocks;
u16 loop_max = g_settings->getU16("liquid_loop_max");
//if (m_transforming_liquid.size() > 0) errorstream << "Liquid queue size="<<m_transforming_liquid.size()<<std::endl;
{
// This should be done here so that it is done when continue is used
if (loopcount >= initial_size || loopcount >= loop_max)
break;
loopcount++;
/*
Get a queued transforming liquid node
*/
v3s16 p0 = m_transforming_liquid.pop_front();
u16 total_level = 0;
// surrounding flowing liquid nodes
NodeNeighbor neighbors[7];
// current level of every block
s8 liquid_levels[7] = {-1, -1, -1, -1, -1, -1, -1};
// target levels
s8 liquid_levels_want[7] = {-1, -1, -1, -1, -1, -1, -1};
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
s8 can_liquid_same_level = 0;
content_t liquid_kind = CONTENT_IGNORE;
content_t liquid_kind_flowing = CONTENT_IGNORE;
/*
Collect information about the environment
*/
const v3s16 *dirs = g_7dirs;
for (u16 i = 0; i < 7; i++) {
NeighborType nt = NEIGHBOR_SAME_LEVEL;
switch (i) {
case D_TOP:
nt = NEIGHBOR_UPPER;
break;
case D_BOTTOM:
nt = NEIGHBOR_LOWER;
break;
}
v3s16 npos = p0 + dirs[i];
neighbors[i].n = getNodeNoEx(npos);
neighbors[i].t = nt;
neighbors[i].p = npos;
neighbors[i].l = 0;
neighbors[i].i = 0;
NodeNeighbor & nb = neighbors[i];
switch (nodemgr->get(nb.n.getContent()).liquid_type) {
case LIQUID_NONE:
if (nb.n.getContent() == CONTENT_AIR) {
liquid_levels[i] = 0;
nb.l = 1;
}
break;
case LIQUID_SOURCE:
// if this node is not (yet) of a liquid type,
// choose the first liquid type we encounter
liquid_kind_flowing = nodemgr->getId(
nodemgr->get(nb.n).liquid_alternative_flowing);
if (liquid_kind == CONTENT_IGNORE)
liquid_kind = nb.n.getContent();
if (nb.n.getContent() == liquid_kind) {
liquid_levels[i] = nb.n.getLevel(nodemgr); //LIQUID_LEVEL_SOURCE;
nb.l = 1;
nb.i = (nb.n.param2 & LIQUID_INFINITY_MASK);
}
break;
case LIQUID_FLOWING:
// if this node is not (yet) of a liquid type,
// choose the first liquid type we encounter
if (liquid_kind_flowing == CONTENT_IGNORE)
liquid_kind_flowing = nb.n.getContent();
if (liquid_kind == CONTENT_IGNORE)
liquid_kind = nodemgr->getId(
nodemgr->get(nb.n).liquid_alternative_source);
liquid_levels[i] = nb.n.getLevel(nodemgr); //(nb.n.param2 & LIQUID_LEVEL_MASK);
if (nb.l && nb.t == NEIGHBOR_SAME_LEVEL)
++can_liquid_same_level;
if (liquid_levels[i] > 0)
total_level += liquid_levels[i];
infostream << "get node i=" <<(int)i<<" " << PP(npos) << " c="
<< nb.n.getContent() <<" p0="<< (int)nb.n.param0 <<" p1="
<< (int)nb.n.param1 <<" p2="<< (int)nb.n.param2 << " lt="
<< nodemgr->get(nb.n.getContent()).liquid_type
//<< " lk=" << liquid_kind << " lkf=" << liquid_kind_flowing
<< " l="<< nb.l << " inf="<< nb.i << " nlevel=" << (int)liquid_levels[i]
<< " tlevel=" << (int)total_level << " cansame="
<< (int)can_liquid_same_level << std::endl;
if (liquid_kind == CONTENT_IGNORE ||
!neighbors[D_SELF].l ||
total_level <= 0)
continue;
// fill bottom block
if (neighbors[D_BOTTOM].l) {
liquid_levels_want[D_BOTTOM] = total_level > LIQUID_LEVEL_SOURCE ?
LIQUID_LEVEL_SOURCE : total_level;
total_level -= liquid_levels_want[D_BOTTOM];
}
if (relax && ((p0.Y == water_level) || (fast_flood && p0.Y <= water_level)) && liquid_levels[D_TOP] == 0 &&
liquid_levels[D_BOTTOM] == LIQUID_LEVEL_SOURCE &&
total_level >= LIQUID_LEVEL_SOURCE * can_liquid_same_level-
(can_liquid_same_level - relax) &&
can_liquid_same_level >= relax + 1) {
total_level = LIQUID_LEVEL_SOURCE * can_liquid_same_level;
}
// prevent lakes in air above unloaded blocks
if (liquid_levels[D_TOP] == 0 && (p0.Y > water_level) && neighbors[D_BOTTOM].n.getContent() == CONTENT_IGNORE && !(loopcount % 3)) {
// calculate self level 5 blocks
u8 want_level =
total_level >= LIQUID_LEVEL_SOURCE * can_liquid_same_level
? LIQUID_LEVEL_SOURCE
: total_level / can_liquid_same_level;
total_level -= want_level * can_liquid_same_level;
//relax down
if (relax && p0.Y == water_level + 1 && liquid_levels[D_TOP] == 0 &&
liquid_levels[D_BOTTOM] == LIQUID_LEVEL_SOURCE && want_level == 0 &&
total_level <= (can_liquid_same_level - relax) &&
can_liquid_same_level >= relax + 1) {
total_level = 0;
}
for (u16 ii = D_SELF; ii < D_TOP; ++ii) { // fill only same level
if (!neighbors[ii].l)
continue;
liquid_levels_want[ii] = want_level;
if (liquid_levels_want[ii] < LIQUID_LEVEL_SOURCE && total_level > 0) {
if (loopcount % 3 || liquid_levels[ii] <= 0){
if (liquid_levels[ii] > liquid_levels_want[ii]) {
++liquid_levels_want[ii];
--total_level;
}
} else if (neighbors[ii].l > 0){
++liquid_levels_want[ii];
--total_level;
}
}
}
for (u16 ii = 0; ii < 7; ++ii) {
if (total_level < 1) break;
if (liquid_levels_want[ii] >= 0 &&
liquid_levels_want[ii] < LIQUID_LEVEL_SOURCE) {
++liquid_levels_want[ii];
--total_level;
}
}
// fill top block if can
if (neighbors[D_TOP].l) {
liquid_levels_want[D_TOP] = total_level > LIQUID_LEVEL_SOURCE ?
LIQUID_LEVEL_SOURCE : total_level;
total_level -= liquid_levels_want[D_TOP];
}
for (u16 ii = 0; ii < 7; ii++) // infinity and cave flood optimization
if ( neighbors[ii].i ||
(liquid_levels_want[ii] >= 0 &&
(fast_flood && p0.Y < water_level &&
(initial_size >= 1000
&& ii != D_TOP
&& want_level >= LIQUID_LEVEL_SOURCE/4
&& can_liquid_same_level >= 5
&& liquid_levels[D_TOP] >= LIQUID_LEVEL_SOURCE))))
liquid_levels_want[ii] = LIQUID_LEVEL_SOURCE;
/*
if (total_level > 0) //|| flowed != volume)
infostream <<" AFTER level=" << (int)total_level
//<< " flowed="<<flowed<< " volume=" << volume
<< " wantsame="<<(int)want_level<< " top="
<< (int)liquid_levels_want[D_TOP]<< " topwas="
<< (int)liquid_levels[D_TOP]<< " bot="
<< (int)liquid_levels_want[D_BOTTOM]<<std::endl;
*/
//u8 changed = 0;
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
for (u16 i = 0; i < 7; i++) {
if (liquid_levels_want[i] < 0 || !neighbors[i].l)
continue;
MapNode & n0 = neighbors[i].n;
p0 = neighbors[i].p;
/*
decide on the type (and possibly level) of the current node
*/
content_t new_node_content;
s8 new_node_level = -1;
u8 viscosity = nodemgr->get(liquid_kind).liquid_viscosity;
if (viscosity > 1 && liquid_levels_want[i] != liquid_levels[i]) {
// amount to gain, limited by viscosity
// must be at least 1 in absolute value
s8 level_inc = liquid_levels_want[i] - liquid_levels[i];
if (level_inc < -viscosity || level_inc > viscosity)
new_node_level = liquid_levels[i] + level_inc/viscosity;
else if (level_inc < 0)
new_node_level = liquid_levels[i] - 1;
else if (level_inc > 0)
new_node_level = liquid_levels[i] + 1;
if (new_node_level >= LIQUID_LEVEL_SOURCE)
new_node_content = liquid_kind;
else if (new_node_level > 0)
new_node_content = liquid_kind_flowing;
else
new_node_content = CONTENT_AIR;
if (liquid_levels_want[i] != liquid_levels[i] &&
liquid_levels[D_TOP] <= 0 && !neighbors[D_BOTTOM].l &&
new_node_level >= 1 && new_node_level <= 2) {
for (u16 ii = D_SELF + 1; ii < D_TOP; ++ii) { // only same level
if (neighbors[ii].l)
must_reflow_second.push_back(p0 + dirs[ii]);
}
check if anything has changed.
if not, just continue with the next node.
if (
new_node_content == n0.getContent()
&& (nodemgr->get(n0.getContent()).liquid_type != LIQUID_FLOWING ||
(n0.getLevel(nodemgr) == (u8)new_node_level
//&& ((n0.param2 & LIQUID_FLOW_DOWN_MASK) ==
//LIQUID_FLOW_DOWN_MASK) == flowing_down
))
&&
(nodemgr->get(n0.getContent()).liquid_type != LIQUID_SOURCE ||
(((n0.param2 & LIQUID_INFINITY_MASK) ==
LIQUID_INFINITY_MASK) == neighbors[i].i
)*/
if (liquid_levels[i] == new_node_level)
{
//++changed;
if (nodemgr->get(new_node_content).liquid_type == LIQUID_FLOWING) {
// set level to last 3 bits, flowing down bit to 4th bit
n0.param2 = (new_node_level & LIQUID_LEVEL_MASK);
} else if (nodemgr->get(new_node_content).liquid_type == LIQUID_SOURCE) {
//n0.param2 = ~(LIQUID_LEVEL_MASK | LIQUID_FLOW_DOWN_MASK);
n0.param2 = (neighbors[i].i ? LIQUID_INFINITY_MASK : 0x00);
}
/*
infostream << "set node i=" <<(int)i<<" "<< PP(p0)<< " nc="
<<new_node_content<< " p2="<<(int)n0.param2<< " nl="
<<(int)new_node_level<<std::endl;
*/
n0.setContent(liquid_kind_flowing);
n0.setLevel(nodemgr, new_node_level);
// Find out whether there is a suspect for this action
std::string suspect;
if(m_gamedef->rollback()){
suspect = m_gamedef->rollback()->getSuspect(p0, 83, 1);
}
if(!suspect.empty()){
// Blame suspect
RollbackScopeActor rollback_scope(m_gamedef->rollback(), suspect, true);
// Get old node for rollback
RollbackNode rollback_oldnode(this, p0, m_gamedef);
// Set node
setNode(p0, n0);
// Report
RollbackNode rollback_newnode(this, p0, m_gamedef);
RollbackAction action;
action.setSetNode(p0, rollback_oldnode, rollback_newnode);
m_gamedef->rollback()->reportAction(action);
} else {
// Set node
setNode(p0, n0);
}
v3s16 blockpos = getNodeBlockPos(p0);
MapBlock *block = getBlockNoCreateNoEx(blockpos);
if(block != NULL) {
modified_blocks[blockpos] = block;
// If node emits light, MapBlock requires lighting update
if(nodemgr->get(n0).light_source != 0)
lighting_modified_blocks[block->getPos()] = block;
}
must_reflow.push_back(neighbors[i].p);
}
/* //for better relax only same level
if (changed) for (u16 ii = D_SELF + 1; ii < D_TOP; ++ii) {
if (!neighbors[ii].l) continue;
must_reflow.push_back(p0 + dirs[ii]);
}*/
}
/*
if (loopcount)
infostream<<"Map::transformLiquids(): loopcount="<<loopcount
<<" reflow="<<must_reflow.size()
<<" queue="<< m_transforming_liquid.size()<<std::endl;
*/
while (must_reflow.size() > 0)
m_transforming_liquid.push_back(must_reflow.pop_front());
while (must_reflow_second.size() > 0)
m_transforming_liquid.push_back(must_reflow_second.pop_front());
updateLighting(lighting_modified_blocks, modified_blocks);
}
void Map::transformLiquids(std::map<v3s16, MapBlock*> & modified_blocks)
if (g_settings->getBool("liquid_finite"))
return Map::transformLiquidsFinite(modified_blocks);
DSTACK(__FUNCTION_NAME);
u32 loopcount = 0;
u32 initial_size = m_transforming_liquid.size();
Perttu Ahola
committed
infostream<<"transformLiquids(): initial_size="<<initial_size<<std::endl;*/
Perttu Ahola
committed
// list of nodes that due to viscosity have not reached their max level height
UniqueQueue<v3s16> must_reflow;
// List of MapBlocks that will require a lighting update (due to lava)
std::map<v3s16, MapBlock*> lighting_modified_blocks;
u16 loop_max = g_settings->getU16("liquid_loop_max");
while(m_transforming_liquid.size() != 0)
{
// This should be done here so that it is done when continue is used
if(loopcount >= initial_size || loopcount >= loop_max)
v3s16 p0 = m_transforming_liquid.pop_front();
Collect information about current node
*/
s8 liquid_level = -1;
content_t liquid_kind = CONTENT_IGNORE;
switch (liquid_type) {
case LIQUID_SOURCE:
liquid_kind = nodemgr->getId(nodemgr->get(n0).liquid_alternative_flowing);
break;
case LIQUID_FLOWING:
liquid_level = (n0.param2 & LIQUID_LEVEL_MASK);
liquid_kind = n0.getContent();
break;
case LIQUID_NONE:
// if this is an air node, it *could* be transformed into a liquid. otherwise,
// continue with the next node.
if (n0.getContent() != CONTENT_AIR)
continue;
liquid_kind = CONTENT_AIR;
break;
}
/*
Collect information about the environment
*/
const v3s16 *dirs = g_6dirs;
NodeNeighbor sources[6]; // surrounding sources
int num_sources = 0;
NodeNeighbor flows[6]; // surrounding flowing liquid nodes
int num_flows = 0;
NodeNeighbor airs[6]; // surrounding air
int num_airs = 0;
NodeNeighbor neutrals[6]; // nodes that are solid or another kind of liquid
int num_neutrals = 0;
bool flowing_down = false;
for (u16 i = 0; i < 6; i++) {
NeighborType nt = NEIGHBOR_SAME_LEVEL;
switch (i) {
nt = NEIGHBOR_UPPER;
break;
nt = NEIGHBOR_LOWER;
break;
}
v3s16 npos = p0 + dirs[i];
NodeNeighbor nb = {getNodeNoEx(npos), nt, npos};
case LIQUID_NONE:
if (nb.n.getContent() == CONTENT_AIR) {
airs[num_airs++] = nb;
Giuseppe Bilotta
committed
// if the current node is a water source the neighbor
// should be enqueded for transformation regardless of whether the
// current node changes or not.
if (nb.t != NEIGHBOR_UPPER && liquid_type != LIQUID_NONE)
m_transforming_liquid.push_back(npos);
// if the current node happens to be a flowing node, it will start to flow down here.
Giuseppe Bilotta
committed
if (nb.t == NEIGHBOR_LOWER) {
flowing_down = true;
Giuseppe Bilotta
committed
}
} else {
neutrals[num_neutrals++] = nb;
break;
case LIQUID_SOURCE:
// if this node is not (yet) of a liquid type, choose the first liquid type we encounter
if (liquid_kind == CONTENT_AIR)
liquid_kind = nodemgr->getId(nodemgr->get(nb.n).liquid_alternative_flowing);
if (nodemgr->getId(nodemgr->get(nb.n).liquid_alternative_flowing) != liquid_kind) {
neutrals[num_neutrals++] = nb;
} else {
Perttu Ahola
committed
// Do not count bottom source, it will screw things up
if(dirs[i].Y != -1)
sources[num_sources++] = nb;
break;
case LIQUID_FLOWING:
// if this node is not (yet) of a liquid type, choose the first liquid type we encounter
liquid_kind = nodemgr->getId(nodemgr->get(nb.n).liquid_alternative_flowing);
if (nodemgr->getId(nodemgr->get(nb.n).liquid_alternative_flowing) != liquid_kind) {
neutrals[num_neutrals++] = nb;
} else {
flows[num_flows++] = nb;
if (nb.t == NEIGHBOR_LOWER)
flowing_down = true;
decide on the type (and possibly level) of the current node
*/
content_t new_node_content;
s8 new_node_level = -1;
u8 range = rangelim(nodemgr->get(liquid_kind).liquid_range, 0, LIQUID_LEVEL_MAX+1);
if ((num_sources >= 2 && nodemgr->get(liquid_kind).liquid_renewable) || liquid_type == LIQUID_SOURCE) {
// liquid_kind will be set to either the flowing alternative of the node (if it's a liquid)
// or the flowing alternative of the first of the surrounding sources (if it's air), so
// it's perfectly safe to use liquid_kind here to determine the new node content.
new_node_content = nodemgr->getId(nodemgr->get(liquid_kind).liquid_alternative_source);
} else if (num_sources >= 1 && sources[0].t != NEIGHBOR_LOWER) {
// liquid_kind is set properly, see above
new_node_content = liquid_kind;
if (new_node_level < (LIQUID_LEVEL_MAX+1-range))
new_node_content = CONTENT_AIR;
} else {
// no surrounding sources, so get the maximum level that can flow into this node
for (u16 i = 0; i < num_flows; i++) {
u8 nb_liquid_level = (flows[i].n.param2 & LIQUID_LEVEL_MASK);
switch (flows[i].t) {
case NEIGHBOR_UPPER:
if (nb_liquid_level + WATER_DROP_BOOST > max_node_level) {
max_node_level = LIQUID_LEVEL_MAX;
if (nb_liquid_level + WATER_DROP_BOOST < LIQUID_LEVEL_MAX)
} else if (nb_liquid_level > max_node_level)
max_node_level = nb_liquid_level;
break;
case NEIGHBOR_LOWER:
break;
case NEIGHBOR_SAME_LEVEL:
if ((flows[i].n.param2 & LIQUID_FLOW_DOWN_MASK) != LIQUID_FLOW_DOWN_MASK &&
nb_liquid_level > 0 && nb_liquid_level - 1 > max_node_level) {
max_node_level = nb_liquid_level - 1;
}
break;
}
u8 viscosity = nodemgr->get(liquid_kind).liquid_viscosity;
if (viscosity > 1 && max_node_level != liquid_level) {
// amount to gain, limited by viscosity
// must be at least 1 in absolute value
s8 level_inc = max_node_level - liquid_level;
if (level_inc < -viscosity || level_inc > viscosity)
new_node_level = liquid_level + level_inc/viscosity;
else if (level_inc < 0)
new_node_level = liquid_level - 1;
else if (level_inc > 0)
new_node_level = liquid_level + 1;
if (new_node_level != max_node_level)
must_reflow.push_back(p0);
} else
new_node_level = max_node_level;
new_node_content = liquid_kind;
new_node_content = CONTENT_AIR;
/*
check if anything has changed. if not, just continue with the next node.
*/
Loading
Loading full blame...