aboutsummaryrefslogtreecommitdiff
path: root/src/environment.cpp
Commit message (Collapse)AuthorAge
* Weather: Clean up getHeat/getHumidity somewhatkwolekr2013-09-16
|
* Omnicleanup: header cleanup, add ModApiUtil shared between game and mainmenuKahrl2013-08-14
|
* Dont write directly to files but rather write and copy a tmp filePilzAdam2013-08-13
|
* Decoration: Fix schematic probability mess with new MTS file versionkwolekr2013-08-11
|
* Add support for different drowning damage and allow drowning in other nodetypesBlockMen2013-08-06
|
* Change large amount of objects to be configurable via a setting in ↵Novatux2013-08-04
| | | | minetest.conf (max_objects_per_block).
* Fix anticheatPerttu Ahola2013-08-03
|
* Add set_breath and get_breath to lua API.RealBadAngel2013-07-20
|
* Fix many formspec menu bugssapier2013-07-07
|
* Dont write player files all the timePilzAdam2013-06-28
|
* Print playername when failing to read playerfile and ignore files starting ↵PilzAdam2013-06-22
| | | | with .
* Add drowningPilzAdam2013-06-19
|
* Show number of objects in client environment in profiler (F6)Kahrl2013-06-18
|
* Play player_damage.ogg when recieving damage and additionally play ↵PilzAdam2013-06-12
| | | | player_falling_damage.ogg when recieving falling damage
* Add and implement setting max_clearobjects_extra_loaded_blocks.Kahrl2013-06-03
| | | | | Now Environment::clearAllObjects() unloads unused blocks in an interval defined by max_clearobjects_extra_loaded_blocks (default 4096).
* Move scriptapi to separate folder (by sapier)sapier2013-05-25
| | | | | | | | | | | On the lua side, notably minetest.env:<function>(<args>) should now be replaced by minetest.<function>(<args>). The old way is and will stay supported for a long time. Also: Update and clean up lua_api.txt (by celeron55) Move EnvRef to lua and remove add_rat and add_firefly (by kahrl) Add separate src/util/CMakeLists.txt, other minor fixes (by kahrl)
* Fix lava damage on player's upper bodykwolekr2013-05-19
|
* Fix nearly all warningskwolekr2013-05-19
|
* fix static data not beeing stored correctly on deactivationsapier2013-05-06
|
* Improve getFree*ActiveObjectId to reduce common case cpu usage drasticalysapier2013-04-24
|
* Add Dijkstra A* and A* without prefetching pathfind algorithmssapier2013-04-06
|
* Allow modifying movement speed, jump height and gravity per-player via the ↵MirceaKitsune2013-04-05
| | | | Lua API.
* Closed add object <-> object collision handlingsapier2013-03-28
|
* Migrate to STL containers/algorithms.Ilya Zhuravlev2013-03-11
|
* Update Copyright YearsSfan52013-02-24
|
* Change Minetest-c55 to MinetestPilzAdam2013-02-24
|
* Improved Player PhysicsMirceaKitsune2013-02-14
|
* The new mapgen, noise functions, et al.kwolekr2013-01-21
|
* fixes node timer bug (fixes #407).Jürgen Doser2013-01-11
| | | | | | Previously, when a block was activated, on_timer callbacks where called with the relative position of the node inside the block, instead of the absolute position of the node.
* Add ServerEnvironment::setNode()/removeNode() to allow setting nodes from ↵Perttu Ahola2013-01-02
| | | | the C++ side with proper script-defined initialization/destruction
* Smooth day-night transitionsPerttu Ahola2012-12-02
|
* Default server step to 0.1s and sync object/player update intervals to itPerttu Ahola2012-11-26
|
* Move util/serialize.h out from staticobject.h for smaller header dependenciesPerttu Ahola2012-11-26
|
* Proper versioning of new network-serialized stuffPerttu Ahola2012-11-26
|
* Send animations, bone overrides and attachments in entity initialization. ↵MirceaKitsune2012-11-25
| | | | | | | | | | | | | | | | | | | | Clients no longer have to be near an object when an animation or attachment is set to see the changes, and newly connected clients (or a client that simply renders the object for the first time) will get all of those settings. Therefore, the lua script no longer needs to run every X seconds either, just once per entity. Finish fixing the material color code. But it won't work until MineTest has dynamic lighting... another day another feature. Extra checks for the bone positioning / rotation code Many checks and consistency improvements to the client attachment code Make a separate function for checking if a client object is attached. A more in-depth change will be needed here to fix reading of invalid pointers Use a different method of fetching the parent. Fixes the mass segmentation faults when rendering an attachment (some still happen though) Major change to how attachments are handled. Fix the last segmentaton fault, which was due to the parent becoming invalid while being refreshed / removed which would bause the child to remain attached to nothing. Parents remove their children when being deleted themselves and add them back when re-added. Attachments are stored inside a 2D a vector which easily allows both a child to find their parent and a parent to find its children. Remove attachment list entry when an object is being permanently removed. Also avoid duplicate entries in this list when re-attaching the same object The "big code comments" can now go away. Client attachments almost work properly, and I know what else needs to be done
* Add dtime_s to entity activationPerttu Ahola2012-09-09
|
* Fix ServerActiveObject stuffPerttu Ahola2012-09-09
|
* Fix wielditem entity drawtype brightness controlPerttu Ahola2012-09-09
|
* Fix the bouncy node related stuff a bitPerttu Ahola2012-09-01
|
* Add bouncy node groupPerttu Ahola2012-09-01
|
* Add disable_jump and fall_damage_add_percent node groupsPerttu Ahola2012-09-01
|
* fix node timers so on_timer gets the correct positiondarkrose2012-08-12
|
* ABM and liquid overload skipPerttu Ahola2012-07-27
|
* Fix server build (a missing header)Perttu Ahola2012-07-27
|
* Experimental-ish rollback functionalityPerttu Ahola2012-07-27
|
* Implement node timersdarkrose2012-07-23
|
* Switch the license to be LGPLv2/later, with small parts still remaining as ↵Perttu Ahola2012-06-05
| | | | GPLv2/later, by agreement of major contributors
* WIP node metadata, node timersKahrl2012-06-03
|
* Add a hack to get rid of the rare "ERROR: StaticObjectList::insert(): id ↵Perttu Ahola2012-04-15
| | | | already exists" failure
* Fix ABM handling on block activationPerttu Ahola2012-04-09
|
> 847 848 849 850 851 852 853 854 855 856 857 858
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "irrlichttypes_extrabloated.h"
#include "mapnode.h"
#include "porting.h"
#include "nodedef.h"
#include "map.h"
#include "content_mapnode.h" // For mapnode_translate_*_internal
#include "serialization.h" // For ser_ver_supported
#include "util/serialize.h"
#include "log.h"
#include "util/directiontables.h"
#include "util/numeric.h"
#include <string>
#include <sstream>

static const Rotation wallmounted_to_rot[] = {
	ROTATE_0, ROTATE_180, ROTATE_90, ROTATE_270
};

static const u8 rot_to_wallmounted[] = {
	2, 4, 3, 5
};


/*
	MapNode
*/

void MapNode::getColor(const ContentFeatures &f, video::SColor *color) const
{
	if (f.palette) {
		*color = (*f.palette)[param2];
		return;
	}
	*color = f.color;
}

void MapNode::setLight(LightBank bank, u8 a_light, const ContentFeatures &f) noexcept
{
	// If node doesn't contain light data, ignore this
	if(f.param_type != CPT_LIGHT)
		return;
	if(bank == LIGHTBANK_DAY)
	{
		param1 &= 0xf0;
		param1 |= a_light & 0x0f;
	}
	else if(bank == LIGHTBANK_NIGHT)
	{
		param1 &= 0x0f;
		param1 |= (a_light & 0x0f)<<4;
	}
	else
		assert("Invalid light bank" == NULL);
}

void MapNode::setLight(LightBank bank, u8 a_light, const NodeDefManager *nodemgr)
{
	setLight(bank, a_light, nodemgr->get(*this));
}

bool MapNode::isLightDayNightEq(const NodeDefManager *nodemgr) const
{
	const ContentFeatures &f = nodemgr->get(*this);
	bool isEqual;

	if (f.param_type == CPT_LIGHT) {
		u8 day   = MYMAX(f.light_source, param1 & 0x0f);
		u8 night = MYMAX(f.light_source, (param1 >> 4) & 0x0f);
		isEqual = day == night;
	} else {
		isEqual = true;
	}

	return isEqual;
}

u8 MapNode::getLight(LightBank bank, const NodeDefManager *nodemgr) const
{
	// Select the brightest of [light source, propagated light]
	const ContentFeatures &f = nodemgr->get(*this);

	u8 light;
	if(f.param_type == CPT_LIGHT)
		light = bank == LIGHTBANK_DAY ? param1 & 0x0f : (param1 >> 4) & 0x0f;
	else
		light = 0;

	return MYMAX(f.light_source, light);
}

u8 MapNode::getLightRaw(LightBank bank, const ContentFeatures &f) const noexcept
{
	if(f.param_type == CPT_LIGHT)
		return bank == LIGHTBANK_DAY ? param1 & 0x0f : (param1 >> 4) & 0x0f;
	return 0;
}

u8 MapNode::getLightNoChecks(LightBank bank, const ContentFeatures *f) const noexcept
{
	return MYMAX(f->light_source,
	             bank == LIGHTBANK_DAY ? param1 & 0x0f : (param1 >> 4) & 0x0f);
}

bool MapNode::getLightBanks(u8 &lightday, u8 &lightnight,
	const NodeDefManager *nodemgr) const
{
	// Select the brightest of [light source, propagated light]
	const ContentFeatures &f = nodemgr->get(*this);
	if(f.param_type == CPT_LIGHT)
	{
		lightday = param1 & 0x0f;
		lightnight = (param1>>4)&0x0f;
	}
	else
	{
		lightday = 0;
		lightnight = 0;
	}
	if(f.light_source > lightday)
		lightday = f.light_source;
	if(f.light_source > lightnight)
		lightnight = f.light_source;
	return f.param_type == CPT_LIGHT || f.light_source != 0;
}

u8 MapNode::getFaceDir(const NodeDefManager *nodemgr,
	bool allow_wallmounted) const
{
	const ContentFeatures &f = nodemgr->get(*this);
	if (f.param_type_2 == CPT2_FACEDIR ||
			f.param_type_2 == CPT2_COLORED_FACEDIR)
		return (getParam2() & 0x1F) % 24;
	if (allow_wallmounted && (f.param_type_2 == CPT2_WALLMOUNTED ||
			f.param_type_2 == CPT2_COLORED_WALLMOUNTED))
		return wallmounted_to_facedir[getParam2() & 0x07];
	return 0;
}

u8 MapNode::getWallMounted(const NodeDefManager *nodemgr) const
{
	const ContentFeatures &f = nodemgr->get(*this);
	if (f.param_type_2 == CPT2_WALLMOUNTED ||
			f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
		return getParam2() & 0x07;
	return 0;
}

v3s16 MapNode::getWallMountedDir(const NodeDefManager *nodemgr) const
{
	switch(getWallMounted(nodemgr))
	{
	case 0: default: return v3s16(0,1,0);
	case 1: return v3s16(0,-1,0);
	case 2: return v3s16(1,0,0);
	case 3: return v3s16(-1,0,0);
	case 4: return v3s16(0,0,1);
	case 5: return v3s16(0,0,-1);
	}
}

void MapNode::rotateAlongYAxis(const NodeDefManager *nodemgr, Rotation rot)
{
	ContentParamType2 cpt2 = nodemgr->get(*this).param_type_2;

	if (cpt2 == CPT2_FACEDIR || cpt2 == CPT2_COLORED_FACEDIR) {
		static const u8 rotate_facedir[24 * 4] = {
			// Table value = rotated facedir
			// Columns: 0, 90, 180, 270 degrees rotation around vertical axis
			// Rotation is anticlockwise as seen from above (+Y)

			0, 1, 2, 3,  // Initial facedir 0 to 3
			1, 2, 3, 0,
			2, 3, 0, 1,
			3, 0, 1, 2,

			4, 13, 10, 19,  // 4 to 7
			5, 14, 11, 16,
			6, 15, 8, 17,
			7, 12, 9, 18,

			8, 17, 6, 15,  // 8 to 11
			9, 18, 7, 12,
			10, 19, 4, 13,
			11, 16, 5, 14,

			12, 9, 18, 7,  // 12 to 15
			13, 10, 19, 4,
			14, 11, 16, 5,
			15, 8, 17, 6,

			16, 5, 14, 11,  // 16 to 19
			17, 6, 15, 8,
			18, 7, 12, 9,
			19, 4, 13, 10,

			20, 23, 22, 21,  // 20 to 23
			21, 20, 23, 22,
			22, 21, 20, 23,
			23, 22, 21, 20
		};
		u8 facedir = (param2 & 31) % 24;
		u8 index = facedir * 4 + rot;
		param2 &= ~31;
		param2 |= rotate_facedir[index];
	} else if (cpt2 == CPT2_WALLMOUNTED ||
			cpt2 == CPT2_COLORED_WALLMOUNTED) {
		u8 wmountface = (param2 & 7);
		if (wmountface <= 1)
			return;

		Rotation oldrot = wallmounted_to_rot[wmountface - 2];
		param2 &= ~7;
		param2 |= rot_to_wallmounted[(oldrot - rot) & 3];
	}
}

void transformNodeBox(const MapNode &n, const NodeBox &nodebox,
	const NodeDefManager *nodemgr, std::vector<aabb3f> *p_boxes,
	u8 neighbors = 0)
{
	std::vector<aabb3f> &boxes = *p_boxes;

	if (nodebox.type == NODEBOX_FIXED || nodebox.type == NODEBOX_LEVELED) {
		const std::vector<aabb3f> &fixed = nodebox.fixed;
		int facedir = n.getFaceDir(nodemgr, true);
		u8 axisdir = facedir>>2;
		facedir&=0x03;
		for (aabb3f box : fixed) {
			if (nodebox.type == NODEBOX_LEVELED)
				box.MaxEdge.Y = (-0.5f + n.getLevel(nodemgr) / 64.0f) * BS;

			switch (axisdir) {
			case 0:
				if(facedir == 1)
				{
					box.MinEdge.rotateXZBy(-90);
					box.MaxEdge.rotateXZBy(-90);
				}
				else if(facedir == 2)
				{
					box.MinEdge.rotateXZBy(180);
					box.MaxEdge.rotateXZBy(180);
				}
				else if(facedir == 3)
				{
					box.MinEdge.rotateXZBy(90);
					box.MaxEdge.rotateXZBy(90);
				}
				break;
			case 1: // z+
				box.MinEdge.rotateYZBy(90);
				box.MaxEdge.rotateYZBy(90);
				if(facedir == 1)
				{
					box.MinEdge.rotateXYBy(90);
					box.MaxEdge.rotateXYBy(90);
				}
				else if(facedir == 2)
				{
					box.MinEdge.rotateXYBy(180);
					box.MaxEdge.rotateXYBy(180);
				}
				else if(facedir == 3)
				{
					box.MinEdge.rotateXYBy(-90);
					box.MaxEdge.rotateXYBy(-90);
				}
				break;
			case 2: //z-
				box.MinEdge.rotateYZBy(-90);
				box.MaxEdge.rotateYZBy(-90);
				if(facedir == 1)
				{
					box.MinEdge.rotateXYBy(-90);
					box.MaxEdge.rotateXYBy(-90);
				}
				else if(facedir == 2)
				{
					box.MinEdge.rotateXYBy(180);
					box.MaxEdge.rotateXYBy(180);
				}
				else if(facedir == 3)
				{
					box.MinEdge.rotateXYBy(90);
					box.MaxEdge.rotateXYBy(90);
				}
				break;
			case 3:  //x+
				box.MinEdge.rotateXYBy(-90);
				box.MaxEdge.rotateXYBy(-90);
				if(facedir == 1)
				{
					box.MinEdge.rotateYZBy(90);
					box.MaxEdge.rotateYZBy(90);
				}
				else if(facedir == 2)
				{
					box.MinEdge.rotateYZBy(180);
					box.MaxEdge.rotateYZBy(180);
				}
				else if(facedir == 3)
				{
					box.MinEdge.rotateYZBy(-90);
					box.MaxEdge.rotateYZBy(-90);
				}
				break;
			case 4:  //x-
				box.MinEdge.rotateXYBy(90);
				box.MaxEdge.rotateXYBy(90);
				if(facedir == 1)
				{
					box.MinEdge.rotateYZBy(-90);
					box.MaxEdge.rotateYZBy(-90);
				}
				else if(facedir == 2)
				{
					box.MinEdge.rotateYZBy(180);
					box.MaxEdge.rotateYZBy(180);
				}
				else if(facedir == 3)
				{
					box.MinEdge.rotateYZBy(90);
					box.MaxEdge.rotateYZBy(90);
				}
				break;
			case 5:
				box.MinEdge.rotateXYBy(-180);
				box.MaxEdge.rotateXYBy(-180);
				if(facedir == 1)
				{
					box.MinEdge.rotateXZBy(90);
					box.MaxEdge.rotateXZBy(90);
				}
				else if(facedir == 2)
				{
					box.MinEdge.rotateXZBy(180);
					box.MaxEdge.rotateXZBy(180);
				}
				else if(facedir == 3)
				{
					box.MinEdge.rotateXZBy(-90);
					box.MaxEdge.rotateXZBy(-90);
				}
				break;
			default:
				break;
			}
			box.repair();
			boxes.push_back(box);
		}
	}
	else if(nodebox.type == NODEBOX_WALLMOUNTED)
	{
		v3s16 dir = n.getWallMountedDir(nodemgr);

		// top
		if(dir == v3s16(0,1,0))
		{
			boxes.push_back(nodebox.wall_top);
		}
		// bottom
		else if(dir == v3s16(0,-1,0))
		{
			boxes.push_back(nodebox.wall_bottom);
		}
		// side
		else
		{
			v3f vertices[2] =
			{
				nodebox.wall_side.MinEdge,
				nodebox.wall_side.MaxEdge
			};

			for (v3f &vertex : vertices) {
				if(dir == v3s16(-1,0,0))
					vertex.rotateXZBy(0);
				if(dir == v3s16(1,0,0))
					vertex.rotateXZBy(180);
				if(dir == v3s16(0,0,-1))
					vertex.rotateXZBy(90);
				if(dir == v3s16(0,0,1))
					vertex.rotateXZBy(-90);
			}

			aabb3f box = aabb3f(vertices[0]);
			box.addInternalPoint(vertices[1]);
			boxes.push_back(box);
		}
	}
	else if (nodebox.type == NODEBOX_CONNECTED)
	{
		size_t boxes_size = boxes.size();
		boxes_size += nodebox.fixed.size();
		if (neighbors & 1)
			boxes_size += nodebox.connect_top.size();
		else
			boxes_size += nodebox.disconnected_top.size();

		if (neighbors & 2)
			boxes_size += nodebox.connect_bottom.size();
		else
			boxes_size += nodebox.disconnected_bottom.size();

		if (neighbors & 4)
			boxes_size += nodebox.connect_front.size();
		else
			boxes_size += nodebox.disconnected_front.size();

		if (neighbors & 8)
			boxes_size += nodebox.connect_left.size();
		else
			boxes_size += nodebox.disconnected_left.size();

		if (neighbors & 16)
			boxes_size += nodebox.connect_back.size();
		else
			boxes_size += nodebox.disconnected_back.size();

		if (neighbors & 32)
			boxes_size += nodebox.connect_right.size();
		else
			boxes_size += nodebox.disconnected_right.size();

		if (neighbors == 0)
			boxes_size += nodebox.disconnected.size();

		if (neighbors < 4)
			boxes_size += nodebox.disconnected_sides.size();

		boxes.reserve(boxes_size);

#define BOXESPUSHBACK(c) \
		for (std::vector<aabb3f>::const_iterator \
				it = (c).begin(); \
				it != (c).end(); ++it) \
			(boxes).push_back(*it);

		BOXESPUSHBACK(nodebox.fixed);

		if (neighbors & 1) {
			BOXESPUSHBACK(nodebox.connect_top);
		} else {
			BOXESPUSHBACK(nodebox.disconnected_top);
		}

		if (neighbors & 2) {
			BOXESPUSHBACK(nodebox.connect_bottom);
		} else {
			BOXESPUSHBACK(nodebox.disconnected_bottom);
		}

		if (neighbors & 4) {
			BOXESPUSHBACK(nodebox.connect_front);
		} else {
			BOXESPUSHBACK(nodebox.disconnected_front);
		}

		if (neighbors & 8) {
			BOXESPUSHBACK(nodebox.connect_left);
		} else {
			BOXESPUSHBACK(nodebox.disconnected_left);
		}

		if (neighbors & 16) {
			BOXESPUSHBACK(nodebox.connect_back);
		} else {
			BOXESPUSHBACK(nodebox.disconnected_back);
		}

		if (neighbors & 32) {
			BOXESPUSHBACK(nodebox.connect_right);
		} else {
			BOXESPUSHBACK(nodebox.disconnected_right);
		}

		if (neighbors == 0) {
			BOXESPUSHBACK(nodebox.disconnected);
		}

		if (neighbors < 4) {
			BOXESPUSHBACK(nodebox.disconnected_sides);
		}

	}
	else // NODEBOX_REGULAR
	{
		boxes.emplace_back(-BS/2,-BS/2,-BS/2,BS/2,BS/2,BS/2);
	}
}

static inline void getNeighborConnectingFace(
	const v3s16 &p, const NodeDefManager *nodedef,
	Map *map, MapNode n, u8 bitmask, u8 *neighbors)
{
	MapNode n2 = map->getNodeNoEx(p);
	if (nodedef->nodeboxConnects(n, n2, bitmask))
		*neighbors |= bitmask;
}

u8 MapNode::getNeighbors(v3s16 p, Map *map) const
{
	const NodeDefManager *nodedef = map->getNodeDefManager();
	u8 neighbors = 0;
	const ContentFeatures &f = nodedef->get(*this);
	// locate possible neighboring nodes to connect to
	if (f.drawtype == NDT_NODEBOX && f.node_box.type == NODEBOX_CONNECTED) {
		v3s16 p2 = p;

		p2.Y++;
		getNeighborConnectingFace(p2, nodedef, map, *this, 1, &neighbors);

		p2 = p;
		p2.Y--;
		getNeighborConnectingFace(p2, nodedef, map, *this, 2, &neighbors);

		p2 = p;
		p2.Z--;
		getNeighborConnectingFace(p2, nodedef, map, *this, 4, &neighbors);

		p2 = p;
		p2.X--;
		getNeighborConnectingFace(p2, nodedef, map, *this, 8, &neighbors);

		p2 = p;
		p2.Z++;
		getNeighborConnectingFace(p2, nodedef, map, *this, 16, &neighbors);

		p2 = p;
		p2.X++;
		getNeighborConnectingFace(p2, nodedef, map, *this, 32, &neighbors);
	}

	return neighbors;
}

void MapNode::getNodeBoxes(const NodeDefManager *nodemgr,
	std::vector<aabb3f> *boxes, u8 neighbors) const
{
	const ContentFeatures &f = nodemgr->get(*this);
	transformNodeBox(*this, f.node_box, nodemgr, boxes, neighbors);
}

void MapNode::getCollisionBoxes(const NodeDefManager *nodemgr,
	std::vector<aabb3f> *boxes, u8 neighbors) const
{
	const ContentFeatures &f = nodemgr->get(*this);
	if (f.collision_box.fixed.empty())
		transformNodeBox(*this, f.node_box, nodemgr, boxes, neighbors);
	else
		transformNodeBox(*this, f.collision_box, nodemgr, boxes, neighbors);
}

void MapNode::getSelectionBoxes(const NodeDefManager *nodemgr,
	std::vector<aabb3f> *boxes, u8 neighbors) const
{
	const ContentFeatures &f = nodemgr->get(*this);
	transformNodeBox(*this, f.selection_box, nodemgr, boxes, neighbors);
}

u8 MapNode::getMaxLevel(const NodeDefManager *nodemgr) const
{
	const ContentFeatures &f = nodemgr->get(*this);
	// todo: after update in all games leave only if (f.param_type_2 ==
	if( f.liquid_type == LIQUID_FLOWING || f.param_type_2 == CPT2_FLOWINGLIQUID)
		return LIQUID_LEVEL_MAX;
	if(f.leveled || f.param_type_2 == CPT2_LEVELED)
		return LEVELED_MAX;
	return 0;
}

u8 MapNode::getLevel(const NodeDefManager *nodemgr) const
{
	const ContentFeatures &f = nodemgr->get(*this);
	// todo: after update in all games leave only if (f.param_type_2 ==
	if(f.liquid_type == LIQUID_SOURCE)
		return LIQUID_LEVEL_SOURCE;
	if (f.param_type_2 == CPT2_FLOWINGLIQUID)
		return getParam2() & LIQUID_LEVEL_MASK;
	if(f.liquid_type == LIQUID_FLOWING) // can remove if all param_type_2 setted
		return getParam2() & LIQUID_LEVEL_MASK;
	if (f.param_type_2 == CPT2_LEVELED) {
		u8 level = getParam2() & LEVELED_MASK;
		if (level)
			return level;
	}
	if (f.leveled > LEVELED_MAX)
		return LEVELED_MAX;
	return f.leveled;
}

u8 MapNode::setLevel(const NodeDefManager *nodemgr, s8 level)
{
	u8 rest = 0;
	const ContentFeatures &f = nodemgr->get(*this);
	if (f.param_type_2 == CPT2_FLOWINGLIQUID
			|| f.liquid_type == LIQUID_FLOWING
			|| f.liquid_type == LIQUID_SOURCE) {
		if (level <= 0) { // liquid can’t exist with zero level
			setContent(CONTENT_AIR);
			return 0;
		}
		if (level >= LIQUID_LEVEL_SOURCE) {
			rest = level - LIQUID_LEVEL_SOURCE;
			setContent(nodemgr->getId(f.liquid_alternative_source));
			setParam2(0);
		} else {
			setContent(nodemgr->getId(f.liquid_alternative_flowing));
			setParam2((level & LIQUID_LEVEL_MASK) | (getParam2() & ~LIQUID_LEVEL_MASK));
		}
	} else if (f.param_type_2 == CPT2_LEVELED) {
		if (level < 0) { // zero means default for a leveled nodebox
			rest = level;
			level = 0;
		} else if (level > LEVELED_MAX) {
			rest = level - LEVELED_MAX;
			level = LEVELED_MAX;
		}
		setParam2((level & LEVELED_MASK) | (getParam2() & ~LEVELED_MASK));
	}
	return rest;
}

u8 MapNode::addLevel(const NodeDefManager *nodemgr, s8 add)
{
	s8 level = getLevel(nodemgr);
	level += add;
	return setLevel(nodemgr, level);
}

u32 MapNode::serializedLength(u8 version)
{
	if(!ser_ver_supported(version))
		throw VersionMismatchException("ERROR: MapNode format not supported");

	if (version == 0)
		return 1;

	if (version <= 9)
		return 2;

	if (version <= 23)
		return 3;

	return 4;
}
void MapNode::serialize(u8 *dest, u8 version) const
{
	if(!ser_ver_supported(version))
		throw VersionMismatchException("ERROR: MapNode format not supported");

	// Can't do this anymore; we have 16-bit dynamically allocated node IDs
	// in memory; conversion just won't work in this direction.
	if(version < 24)
		throw SerializationError("MapNode::serialize: serialization to "
				"version < 24 not possible");

	writeU16(dest+0, param0);
	writeU8(dest+2, param1);
	writeU8(dest+3, param2);
}
void MapNode::deSerialize(u8 *source, u8 version)
{
	if(!ser_ver_supported(version))
		throw VersionMismatchException("ERROR: MapNode format not supported");

	if(version <= 21)
	{
		deSerialize_pre22(source, version);
		return;
	}

	if(version >= 24){
		param0 = readU16(source+0);
		param1 = readU8(source+2);
		param2 = readU8(source+3);
	}else{
		param0 = readU8(source+0);
		param1 = readU8(source+1);
		param2 = readU8(source+2);
		if(param0 > 0x7F){
			param0 |= ((param2&0xF0)<<4);
			param2 &= 0x0F;
		}
	}
}
void MapNode::serializeBulk(std::ostream &os, int version,
		const MapNode *nodes, u32 nodecount,
		u8 content_width, u8 params_width, bool compressed)
{
	if (!ser_ver_supported(version))
		throw VersionMismatchException("ERROR: MapNode format not supported");

	sanity_check(content_width == 2);
	sanity_check(params_width == 2);

	// Can't do this anymore; we have 16-bit dynamically allocated node IDs
	// in memory; conversion just won't work in this direction.
	if (version < 24)
		throw SerializationError("MapNode::serializeBulk: serialization to "
				"version < 24 not possible");

	size_t databuf_size = nodecount * (content_width + params_width);
	u8 *databuf = new u8[databuf_size];

	u32 start1 = content_width * nodecount;
	u32 start2 = (content_width + 1) * nodecount;

	// Serialize content
	for (u32 i = 0; i < nodecount; i++) {
		writeU16(&databuf[i * 2], nodes[i].param0);
		writeU8(&databuf[start1 + i], nodes[i].param1);
		writeU8(&databuf[start2 + i], nodes[i].param2);
	}

	/*
		Compress data to output stream
	*/

	if (compressed)
		compressZlib(databuf, databuf_size, os);
	else
		os.write((const char*) &databuf[0], databuf_size);

	delete [] databuf;
}

// Deserialize bulk node data
void MapNode::deSerializeBulk(std::istream &is, int version,
		MapNode *nodes, u32 nodecount,
		u8 content_width, u8 params_width, bool compressed)
{
	if(!ser_ver_supported(version))
		throw VersionMismatchException("ERROR: MapNode format not supported");

	if (version < 22
			|| (content_width != 1 && content_width != 2)
			|| params_width != 2)
		FATAL_ERROR("Deserialize bulk node data error");

	// Uncompress or read data
	u32 len = nodecount * (content_width + params_width);
	SharedBuffer<u8> databuf(len);
	if(compressed)
	{
		std::ostringstream os(std::ios_base::binary);
		decompressZlib(is, os);
		std::string s = os.str();
		if(s.size() != len)
			throw SerializationError("deSerializeBulkNodes: "
					"decompress resulted in invalid size");
		memcpy(&databuf[0], s.c_str(), len);
	}
	else
	{
		is.read((char*) &databuf[0], len);
		if(is.eof() || is.fail())
			throw SerializationError("deSerializeBulkNodes: "
					"failed to read bulk node data");
	}

	// Deserialize content
	if(content_width == 1)
	{
		for(u32 i=0; i<nodecount; i++)
			nodes[i].param0 = readU8(&databuf[i]);
	}
	else if(content_width == 2)
	{
		for(u32 i=0; i<nodecount; i++)
			nodes[i].param0 = readU16(&databuf[i*2]);
	}

	// Deserialize param1
	u32 start1 = content_width * nodecount;
	for(u32 i=0; i<nodecount; i++)
		nodes[i].param1 = readU8(&databuf[start1 + i]);

	// Deserialize param2
	u32 start2 = (content_width + 1) * nodecount;
	if(content_width == 1)
	{
		for(u32 i=0; i<nodecount; i++) {
			nodes[i].param2 = readU8(&databuf[start2 + i]);
			if(nodes[i].param0 > 0x7F){
				nodes[i].param0 <<= 4;
				nodes[i].param0 |= (nodes[i].param2&0xF0)>>4;
				nodes[i].param2 &= 0x0F;
			}
		}
	}
	else if(content_width == 2)
	{
		for(u32 i=0; i<nodecount; i++)
			nodes[i].param2 = readU8(&databuf[start2 + i]);
	}
}

/*
	Legacy serialization
*/
void MapNode::deSerialize_pre22(const u8 *source, u8 version)
{
	if(version <= 1)
	{
		param0 = source[0];
	}
	else if(version <= 9)
	{
		param0 = source[0];
		param1 = source[1];
	}
	else
	{
		param0 = source[0];
		param1 = source[1];
		param2 = source[2];
		if(param0 > 0x7f){
			param0 <<= 4;
			param0 |= (param2&0xf0)>>4;
			param2 &= 0x0f;
		}
	}

	// Convert special values from old version to new
	if(version <= 19)
	{
		// In these versions, CONTENT_IGNORE and CONTENT_AIR
		// are 255 and 254
		// Version 19 is fucked up with sometimes the old values and sometimes not
		if(param0 == 255)
			param0 = CONTENT_IGNORE;
		else if(param0 == 254)
			param0 = CONTENT_AIR;
	}

	// Translate to our known version
	*this = mapnode_translate_to_internal(*this, version);
}