Skip to content
Snippets Groups Projects
map.cpp 101 KiB
Newer Older
Perttu Ahola's avatar
Perttu Ahola committed
		// 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);

		n.setLight(bank, 0, ndef);
	/*
		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);
	/*
		Remove node metadata
	*/

	removeNodeMetadata(p);

Perttu Ahola's avatar
Perttu Ahola committed
	setNode(p, n);
Perttu Ahola's avatar
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.
Perttu Ahola's avatar
Perttu Ahola committed
		TODO: This could be optimized by mass-unlighting instead
Perttu Ahola's avatar
Perttu Ahola committed
	*/
	if(node_under_sunlight && !ndef->get(n).sunlight_propagates)
Perttu Ahola's avatar
Perttu Ahola committed
	{
		s16 y = p.Y - 1;
		for(;; y--){
			//m_dout<<DTIME<<"y="<<y<<std::endl;
			v3s16 n2pos(p.X, y, p.Z);
Perttu Ahola's avatar
Perttu Ahola committed
			MapNode n2;
			try{
				n2 = getNode(n2pos);
			}
			catch(InvalidPositionException &e)
			{
				break;
			}

			if(n2.getLight(LIGHTBANK_DAY, ndef) == LIGHT_SUN)
Perttu Ahola's avatar
Perttu Ahola committed
			{
Perttu Ahola's avatar
Perttu Ahola committed
				unLightNeighbors(LIGHTBANK_DAY,
						n2pos, n2.getLight(LIGHTBANK_DAY, ndef),
Perttu Ahola's avatar
Perttu Ahola committed
						light_sources, modified_blocks);
				n2.setLight(LIGHTBANK_DAY, 0, ndef);
Perttu Ahola's avatar
Perttu Ahola committed
				setNode(n2pos, n2);
			}
			else
				break;
		}
	}
Perttu Ahola's avatar
Perttu Ahola committed
	for(s32 i=0; i<2; i++)
	{
		enum LightBank bank = banks[i];
Perttu Ahola's avatar
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.
proller's avatar
proller committed
		note: todo: for liquid_finite enough to add only self node
	*/
	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];
		if(ndef->get(n2).isLiquid() || n2.getContent() == CONTENT_AIR)
		{
			m_transforming_liquid.push_back(p2);
		}
Perttu Ahola's avatar
Perttu Ahola committed
}

/*
*/
void Map::removeNodeAndUpdate(v3s16 p,
		std::map<v3s16, MapBlock*> &modified_blocks)
Perttu Ahola's avatar
Perttu Ahola committed
{
	INodeDefManager *ndef = m_gamedef->ndef();
Perttu Ahola's avatar
Perttu Ahola committed

Perttu Ahola's avatar
Perttu Ahola committed
	/*PrintInfo(m_dout);
	m_dout<<DTIME<<"Map::removeNodeAndUpdate(): p=("
			<<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
Perttu Ahola's avatar
Perttu Ahola committed
	bool node_under_sunlight = true;
Perttu Ahola's avatar
Perttu Ahola committed
	v3s16 toppos = p + v3s16(0,1,0);
Perttu Ahola's avatar
Perttu Ahola committed

	// Node will be replaced with this
	content_t replace_material = CONTENT_AIR;
	/*
		Collect old node for rollback
	*/
	RollbackNode rollback_oldnode(this, p, m_gamedef);

Perttu Ahola's avatar
Perttu Ahola committed
	/*
		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)
Perttu Ahola's avatar
Perttu Ahola committed
			node_under_sunlight = false;
	}
	catch(InvalidPositionException &e)
	{
	}

	std::set<v3s16> light_sources;
Perttu Ahola's avatar
Perttu Ahola committed

	enum LightBank banks[] =
	{
		LIGHTBANK_DAY,
		LIGHTBANK_NIGHT
	};
	for(s32 i=0; i<2; i++)
	{
		enum LightBank bank = banks[i];
Perttu Ahola's avatar
Perttu Ahola committed
		/*
			Unlight neighbors (in case the node is a light source)
		*/
		unLightNeighbors(bank, p,
				getNode(p).getLight(bank, ndef),
Perttu Ahola's avatar
Perttu Ahola committed
				light_sources, modified_blocks);
	}
Perttu Ahola's avatar
Perttu Ahola committed

Perttu Ahola's avatar
Perttu Ahola committed
	/*
		Remove node metadata
	*/

	removeNodeMetadata(p);

Perttu Ahola's avatar
Perttu Ahola committed
	/*
Perttu Ahola's avatar
Perttu Ahola committed
		Remove the node.
		This also clears the lighting.
Perttu Ahola's avatar
Perttu Ahola committed
	*/
Perttu Ahola's avatar
Perttu Ahola committed
	MapNode n;
	n.setContent(replace_material);
Perttu Ahola's avatar
Perttu Ahola committed
	setNode(p, n);
Perttu Ahola's avatar
Perttu Ahola committed
	for(s32 i=0; i<2; i++)
	{
		enum LightBank bank = banks[i];
Perttu Ahola's avatar
Perttu Ahola committed
		/*
			Recalculate lighting
		*/
		spreadLight(bank, light_sources, modified_blocks);
	}
Perttu Ahola's avatar
Perttu Ahola committed

	// 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;
Perttu Ahola's avatar
Perttu Ahola committed

	/*
		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;*/
Perttu Ahola's avatar
Perttu Ahola committed
			lightNeighbors(LIGHTBANK_DAY, p2, modified_blocks);
Perttu Ahola's avatar
Perttu Ahola committed
		}
	}
	else
	{
		// Set the lighting of this node to 0
Perttu Ahola's avatar
Perttu Ahola committed
		// TODO: Is this needed? Lighting is cleared up there already.
Perttu Ahola's avatar
Perttu Ahola committed
		try{
			MapNode n = getNode(p);
			n.setLight(LIGHTBANK_DAY, 0, ndef);
Perttu Ahola's avatar
Perttu Ahola committed
			setNode(p, n);
		}
		catch(InvalidPositionException &e)
		{
			assert(0);
Perttu Ahola's avatar
Perttu Ahola committed
	for(s32 i=0; i<2; i++)
	{
		enum LightBank bank = banks[i];
Perttu Ahola's avatar
Perttu Ahola committed
		// Get the brightest neighbour node and propagate light from it
		v3s16 n2p = getBrightestNeighbour(bank, p);
		try{
kwolekr's avatar
kwolekr committed
			//MapNode n2 = getNode(n2p);
Perttu Ahola's avatar
Perttu Ahola committed
			lightNeighbors(bank, n2p, modified_blocks);
		}
		catch(InvalidPositionException &e)
		{
		}
Perttu Ahola's avatar
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)
		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.)
proller's avatar
proller committed
		note: todo: for liquid_finite enough to add only self node
		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(ndef->get(n2).isLiquid() || n2.getContent() == CONTENT_AIR)
		{
			m_transforming_liquid.push_back(p2);
		}
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)
			event.modified_blocks.insert(i->first);
		}
	}
	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)
			event.modified_blocks.insert(i->first);
		}
	}
	catch(InvalidPositionException &e){
		succeeded = false;
	}

	dispatchEvent(&event);

	return succeeded;
}

bool Map::getDayNightDiff(v3s16 blockpos)
{
	try{
		v3s16 p = blockpos + v3s16(0,0,0);
		MapBlock *b = getBlockNoCreate(p);
			return true;
	}
	catch(InvalidPositionException &e){}
Perttu Ahola's avatar
Perttu Ahola committed
	// Leading edges
		v3s16 p = blockpos + v3s16(-1,0,0);
		MapBlock *b = getBlockNoCreate(p);
			return true;
	}
	catch(InvalidPositionException &e){}
	try{
		v3s16 p = blockpos + v3s16(0,-1,0);
		MapBlock *b = getBlockNoCreate(p);
			return true;
	}
	catch(InvalidPositionException &e){}
	try{
		v3s16 p = blockpos + v3s16(0,0,-1);
		MapBlock *b = getBlockNoCreate(p);
			return true;
Perttu Ahola's avatar
Perttu Ahola committed
	}
	catch(InvalidPositionException &e){}
Perttu Ahola's avatar
Perttu Ahola committed
	// Trailing edges
	try{
		v3s16 p = blockpos + v3s16(1,0,0);
		MapBlock *b = getBlockNoCreate(p);
Perttu Ahola's avatar
Perttu Ahola committed
			return true;
	}
	catch(InvalidPositionException &e){}
	try{
		v3s16 p = blockpos + v3s16(0,1,0);
		MapBlock *b = getBlockNoCreate(p);
Perttu Ahola's avatar
Perttu Ahola committed
			return true;
	}
	catch(InvalidPositionException &e){}
	try{
		v3s16 p = blockpos + v3s16(0,0,1);
		MapBlock *b = getBlockNoCreate(p);
Perttu Ahola's avatar
Perttu Ahola committed
			return true;
	}
	catch(InvalidPositionException &e){}

	return false;
Perttu Ahola's avatar
Perttu Ahola committed
}

/*
	Updates usage timers
*/
void Map::timerUpdate(float dtime, float unload_timeout,
		std::list<v3s16> *unloaded_blocks)
Perttu Ahola's avatar
Perttu Ahola committed
{
	bool save_before_unloading = (mapType() == MAPTYPE_SERVER);
	std::list<v2s16> sector_deletion_queue;
	u32 deleted_blocks_count = 0;
	u32 saved_blocks_count = 0;
	u32 block_count_all = 0;
Perttu Ahola's avatar
Perttu Ahola committed

	for(std::map<v2s16, MapSector*>::iterator si = m_sectors.begin();
		si != m_sectors.end(); ++si)
Perttu Ahola's avatar
Perttu Ahola committed
	{
		MapSector *sector = si->second;
		bool all_blocks_deleted = true;

		std::list<MapBlock*> blocks;
		for(std::list<MapBlock*>::iterator i = blocks.begin();
				i != blocks.end(); ++i)
			MapBlock *block = (*i);
			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)
				{
					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;
				block_count_all++;

		if(all_blocks_deleted)
		{
			sector_deletion_queue.push_back(si->first);
Perttu Ahola's avatar
Perttu Ahola committed
	}
	// 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";
		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)
Perttu Ahola's avatar
Perttu Ahola committed
{
	for(std::list<v2s16>::iterator j = list.begin();
		j != list.end(); ++j)
Perttu Ahola's avatar
Perttu Ahola committed
	{
		MapSector *sector = m_sectors[*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
		m_sectors.erase(*j);
void Map::unloadUnusedData(float timeout,
Perttu Ahola's avatar
Perttu Ahola committed
		core::list<v3s16> *deleted_blocks)
{
	core::list<v2s16> sector_deletion_queue;
	u32 deleted_blocks_count = 0;
	u32 saved_blocks_count = 0;

	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);
			if(block->getUsageTimer() > timeout)
			{
				// Save if modified
				if(block->getModified() != MOD_STATE_CLEAN)
Perttu Ahola's avatar
Perttu Ahola committed
				// Delete from memory
				sector->deleteBlock(block);
			}
			else
			{
				all_blocks_deleted = false;
			}
		}

		if(all_blocks_deleted)
		{
			sector_deletion_queue.push_back(si.getNode()->getKey());
		}
	}

	deleteSectors(sector_deletion_queue);
	infostream<<"Map: Unloaded "<<deleted_blocks_count<<" blocks from memory"
			<<", of which "<<saved_blocks_count<<" were wr."
			<<std::endl;
	//return sector_deletion_queue.getSize();
	//return deleted_blocks_count;
Perttu Ahola's avatar
Perttu Ahola committed
}
Perttu Ahola's avatar
Perttu Ahola committed

void Map::PrintInfo(std::ostream &out)
{
	out<<"Map: ";
}

enum NeighborType {
	NEIGHBOR_UPPER,
	NEIGHBOR_SAME_LEVEL,
	NEIGHBOR_LOWER
};
struct NodeNeighbor {
	MapNode n;
	NeighborType t;
	v3s16 p;
proller's avatar
proller committed
	bool l; //can liquid
	bool i; //infinity
proller's avatar
proller committed
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)
proller's avatar
proller committed
{
	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;

proller's avatar
proller committed
	while (m_transforming_liquid.size() > 0)
proller's avatar
proller committed
	{
		// This should be done here so that it is done when continue is used
		if (loopcount >= initial_size || loopcount >= loop_max)
proller's avatar
proller committed
			break;
		loopcount++;
		/*
			Get a queued transforming liquid node
		*/
		v3s16 p0 = m_transforming_liquid.pop_front();
		u16 total_level = 0;
proller's avatar
proller committed
		// 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};
proller's avatar
proller committed
		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:
proller's avatar
proller committed
					// if this node is not (yet) of a liquid type,
					// choose the first liquid type we encounter
proller's avatar
proller committed
					if (liquid_kind_flowing == CONTENT_IGNORE)
proller's avatar
proller committed
						liquid_kind_flowing = nodemgr->getId(
							nodemgr->get(nb.n).liquid_alternative_flowing);
proller's avatar
proller committed
					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;
proller's avatar
proller committed
						nb.l = 1;
						nb.i = (nb.n.param2 & LIQUID_INFINITY_MASK);
					}
					break;
				case LIQUID_FLOWING:
proller's avatar
proller committed
					// if this node is not (yet) of a liquid type,
					// choose the first liquid type we encounter
proller's avatar
proller committed
					if (liquid_kind_flowing == CONTENT_IGNORE)
						liquid_kind_flowing = nb.n.getContent();
					if (liquid_kind == CONTENT_IGNORE)
proller's avatar
proller committed
						liquid_kind = nodemgr->getId(
							nodemgr->get(nb.n).liquid_alternative_source);
proller's avatar
proller committed
					if (nb.n.getContent() == liquid_kind_flowing) {
						liquid_levels[i] = nb.n.getLevel(nodemgr); //(nb.n.param2 & LIQUID_LEVEL_MASK);
proller's avatar
proller committed
						nb.l = 1;
					}
					break;
			}
proller's avatar
proller committed
			
			if (nb.l && nb.t == NEIGHBOR_SAME_LEVEL)
				++can_liquid_same_level;
			if (liquid_levels[i] > 0)
				total_level += liquid_levels[i];
proller's avatar
proller committed
			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
proller's avatar
proller committed
			//<< " lk=" << liquid_kind << " lkf=" << liquid_kind_flowing
proller's avatar
proller committed
			<< " l="<< nb.l	<< " inf="<< nb.i << " nlevel=" << (int)liquid_levels[i]
			<< " tlevel=" << (int)total_level << " cansame="
			<< (int)can_liquid_same_level << std::endl;
proller's avatar
proller committed
		if (liquid_kind == CONTENT_IGNORE ||
			!neighbors[D_SELF].l ||
			total_level <= 0)
proller's avatar
proller committed
			continue;

		// fill bottom block
		if (neighbors[D_BOTTOM].l) {
proller's avatar
proller committed
			liquid_levels_want[D_BOTTOM] = total_level > LIQUID_LEVEL_SOURCE ?
				LIQUID_LEVEL_SOURCE : total_level;
proller's avatar
proller committed
			total_level -= liquid_levels_want[D_BOTTOM];
		}

proller's avatar
proller committed
		//relax up
		if (relax && ((p0.Y == water_level) || (fast_flood && p0.Y <= water_level)) && liquid_levels[D_TOP] == 0 &&
proller's avatar
proller committed
			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) { 
proller's avatar
proller committed
			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)) {
			--total_level;
proller's avatar
proller committed
		// 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;

proller's avatar
proller committed
		//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) {
proller's avatar
proller committed
			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;
				}
proller's avatar
proller committed
			}
		}

		for (u16 ii = 0; ii < 7; ++ii) {
			if (total_level < 1) break;
proller's avatar
proller committed
			if (liquid_levels_want[ii] >= 0 &&
				liquid_levels_want[ii] < LIQUID_LEVEL_SOURCE) {
proller's avatar
proller committed
				++liquid_levels_want[ii];
				--total_level;
			}
		}

		// fill top block if can
		if (neighbors[D_TOP].l) {
proller's avatar
proller committed
			liquid_levels_want[D_TOP] = total_level > LIQUID_LEVEL_SOURCE ?
				LIQUID_LEVEL_SOURCE : total_level;
proller's avatar
proller committed
			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 &&
proller's avatar
proller committed
				 (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;

proller's avatar
proller committed
		/*
		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;
		*/
proller's avatar
proller committed
		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;
proller's avatar
proller committed
			} else {
proller's avatar
proller committed
				new_node_level = liquid_levels_want[i];
proller's avatar
proller committed
			}
			
proller's avatar
proller committed
			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;
proller's avatar
proller committed
			// last level must flow down on stairs
proller's avatar
proller committed
			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) {
proller's avatar
proller committed
				for (u16 ii = D_SELF + 1; ii < D_TOP; ++ii) { // only same level
proller's avatar
proller committed
					if (neighbors[ii].l)
						must_reflow_second.push_back(p0 + dirs[ii]);
				}
proller's avatar
proller committed
				check if anything has changed.
				if not, just continue with the next node.
proller's avatar
proller committed
			 */
proller's avatar
proller committed
			if (
				 new_node_content == n0.getContent() 
				&& (nodemgr->get(n0.getContent()).liquid_type != LIQUID_FLOWING ||
				 (n0.getLevel(nodemgr) == (u8)new_node_level
proller's avatar
proller committed
				 //&& ((n0.param2 & LIQUID_FLOW_DOWN_MASK) ==
				 //LIQUID_FLOW_DOWN_MASK) == flowing_down
proller's avatar
proller committed
				 ))
				&&
				 (nodemgr->get(n0.getContent()).liquid_type != LIQUID_SOURCE ||
proller's avatar
proller committed
				 (((n0.param2 & LIQUID_INFINITY_MASK) ==
					LIQUID_INFINITY_MASK) == neighbors[i].i
proller's avatar
proller committed
				 ))
			   )*/
			if (liquid_levels[i] == new_node_level)
			{
proller's avatar
proller committed
				continue;
			}
proller's avatar
proller committed

			/*
				update the current node
			 */
proller's avatar
proller committed
			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);
			}
proller's avatar
proller committed
			/*
			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);
proller's avatar
proller committed
			// 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);
proller's avatar
proller committed
				// 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;
proller's avatar
proller committed
				// 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);
		}
proller's avatar
proller committed
		/* //for better relax  only same level
		if (changed)  for (u16 ii = D_SELF + 1; ii < D_TOP; ++ii) {
proller's avatar
proller committed
			if (!neighbors[ii].l) continue;
			must_reflow.push_back(p0 + dirs[ii]);
		}*/
	}
proller's avatar
proller committed
	/*
	if (loopcount)
		infostream<<"Map::transformLiquids(): loopcount="<<loopcount
		<<" reflow="<<must_reflow.size()
		<<" queue="<< m_transforming_liquid.size()<<std::endl;
	*/
proller's avatar
proller committed
	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);
}