aboutsummaryrefslogtreecommitdiff
path: root/builtin/common/serialize.lua
blob: 1deee43a463d31fe67656abe1ade370178898e13 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
-- Minetest: builtin/serialize.lua

-- https://github.com/fab13n/metalua/blob/no-dll/src/lib/serialize.lua
-- Copyright (c) 2006-2997 Fabien Fleutot <metalua@gmail.com>
-- License: MIT
--------------------------------------------------------------------------------
-- Serialize an object into a source code string. This string, when passed as
-- an argument to deserialize(), returns an object structurally identical
-- to the original one. The following are currently supported:
-- * strings, numbers, booleans, nil
-- * tables thereof. Tables can have shared part, but can't be recursive yet.
-- Caveat: metatables and environments aren't saved.
--------------------------------------------------------------------------------

local no_identity = { number=1, boolean=1, string=1, ['nil']=1 }

function core.serialize(x)

	local gensym_max   =  0  -- index of the gensym() symbol generator
	local seen_once    = { } -- element->true set of elements seen exactly once in the table
	local multiple     = { } -- element->varname set of elements seen more than once
	local nested       = { } -- transient, set of elements currently being traversed
	local nest_points  = { }
	local nest_patches = { }
	
	local function gensym()
		gensym_max = gensym_max + 1 ;  return gensym_max
	end

	-----------------------------------------------------------------------------
	-- nest_points are places where a table appears within itself, directly or not.
	-- for instance, all of these chunks create nest points in table x:
	-- "x = { }; x[x] = 1", "x = { }; x[1] = x", "x = { }; x[1] = { y = { x } }".
	-- To handle those, two tables are created by mark_nest_point:
	-- * nest_points [parent] associates all keys and values in table parent which
	--   create a nest_point with boolean `true'
	-- * nest_patches contain a list of { parent, key, value } tuples creating
	--   a nest point. They're all dumped after all the other table operations
	--   have been performed.
	--
	-- mark_nest_point (p, k, v) fills tables nest_points and nest_patches with
	-- informations required to remember that key/value (k,v) create a nest point
	-- in table parent. It also marks `parent' as occuring multiple times, since
	-- several references to it will be required in order to patch the nest
	-- points.
	-----------------------------------------------------------------------------
	local function mark_nest_point (parent, k, v)
		local nk, nv = nested[k], nested[v]
		assert (not nk or seen_once[k] or multiple[k])
		assert (not nv or seen_once[v] or multiple[v])
		local mode = (nk and nv and "kv") or (nk and "k") or ("v")
		local parent_np = nest_points [parent]
		local pair = { k, v }
		if not parent_np then parent_np = { }; nest_points [parent] = parent_np end
		parent_np [k], parent_np [v] = nk, nv
		table.insert (nest_patches, { parent, k, v })
		seen_once [parent], multiple [parent]  = nil, true
	end

	-----------------------------------------------------------------------------
	-- First pass, list the tables and functions which appear more than once in x
	-----------------------------------------------------------------------------
	local function mark_multiple_occurences (x)
		if no_identity [type(x)] then return end
		if     seen_once [x]     then seen_once [x], multiple [x] = nil, true
		elseif multiple  [x]     then -- pass
		else   seen_once [x] = true end
		
		if type (x) == 'table' then
			nested [x] = true
			for k, v in pairs (x) do
				if nested[k] or nested[v] then mark_nest_point (x, k, v) else
					mark_multiple_occurences (k)
					mark_multiple_occurences (v)
				end
			end
			nested [x] = nil
		end
	end

	local dumped    = { } -- multiply occuring values already dumped in localdefs
	local localdefs = { } -- already dumped local definitions as source code lines

	-- mutually recursive functions:
	local dump_val, dump_or_ref_val

	--------------------------------------------------------------------
	-- if x occurs multiple times, dump the local var rather than the
	-- value. If it's the first time it's dumped, also dump the content
	-- in localdefs.
	--------------------------------------------------------------------
	function dump_or_ref_val (x)
		if nested[x] then return 'false' end -- placeholder for recursive reference
		if not multiple[x] then return dump_val (x) end
		local var = dumped [x]
		if var then return "_[" .. var .. "]" end -- already referenced
		local val = dump_val(x) -- first occurence, create and register reference
		var = gensym()
		table.insert(localdefs, "_["..var.."]="..val)
		dumped [x] = var
		return "_[" .. var .. "]"
	end

	-----------------------------------------------------------------------------
	-- Second pass, dump the object; subparts occuring multiple times are dumped
	-- in local variables which can be referenced multiple times;
	-- care is taken to dump locla vars in asensible order.
	-----------------------------------------------------------------------------
	function dump_val(x)
		local  t = type(x)
		if     x==nil        then return 'nil'
		elseif t=="number"   then return tostring(x)
		elseif t=="string"   then return string.format("%q", x)
		elseif t=="boolean"  then return x and "true" or "false"
		elseif t=="function" then
			return string.format("loadstring(%q)", string.dump(x))
		elseif t=="table" then
			local acc        = { }
			local idx_dumped = { }
			local np         = nest_points [x]
			for i, v in ipairs(x) do
				if np and np[v] then
					table.insert (acc, 'false') -- placeholder
				else
					table.insert (acc, dump_or_ref_val(v))
				end
				idx_dumped[i] = true
			end
			for k, v in pairs(x) do
				if np and (np[k] or np[v]) then
					--check_multiple(k); check_multiple(v) -- force dumps in localdefs
				elseif not idx_dumped[k] then
					table.insert (acc, "[" .. dump_or_ref_val(k) .. "] = " .. dump_or_ref_val(v))
				end
			end
			return "{ "..table.concat(acc,", ").." }"
		else
			error ("Can't serialize data of type "..t)
		end
	end
	
	local function dump_nest_patches()
		for _, entry in ipairs(nest_patches) do
			local p, k, v = unpack (entry)
			assert (multiple[p])
			local set = dump_or_ref_val (p) .. "[" .. dump_or_ref_val (k) .. "] = " .. 
				dump_or_ref_val (v) .. " -- rec "
			table.insert (localdefs, set)
		end
	end

	mark_multiple_occurences (x)
	local toplevel = dump_or_ref_val (x)
	dump_nest_patches()

	if next (localdefs) then
		return "local _={ }\n" ..
			table.concat (localdefs, "\n") .. 
			"\nreturn " .. toplevel
	else
		return "return " .. toplevel
	end
end

-- Deserialization.
-- http://stackoverflow.com/questions/5958818/loading-serialized-data-into-a-table
--

local env = {
	loadstring = loadstring,
}

local function noop() end

local safe_env = {
	loadstring = noop,
}

local function stringtotable(sdata, safe)
	if sdata:byte(1) == 27 then return nil, "binary bytecode prohibited" end
	local f, message = assert(loadstring(sdata))
	if not f then return nil, message end
	if safe then
		setfenv(f, safe_env)
	else
		setfenv(f, env)
	end
	return f()
end

function core.deserialize(sdata, safe)
	local table = {}
	local okay, results = pcall(stringtotable, sdata, safe)
	if okay then
		return results
	end
	core.log('error', 'core.deserialize(): '.. results)
	return nil
end

-- Run some unit tests
local function unit_test()
	function unitTest(name, success)
		if not success then
			error(name .. ': failed')
		end
	end

	unittest_input = {cat={sound="nyan", speed=400}, dog={sound="woof"}}
	unittest_output = core.deserialize(core.serialize(unittest_input))

	unitTest("test 1a", unittest_input.cat.sound == unittest_output.cat.sound)
	unitTest("test 1b", unittest_input.cat.speed == unittest_output.cat.speed)
	unitTest("test 1c", unittest_input.dog.sound == unittest_output.dog.sound)

	unittest_input = {escapechars="\n\r\t\v\\\"\'", noneuropean="θשׁ٩∂"}
	unittest_output = core.deserialize(core.serialize(unittest_input))
	unitTest("test 3a", unittest_input.escapechars == unittest_output.escapechars)
	unitTest("test 3b", unittest_input.noneuropean == unittest_output.noneuropean)
end
unit_test() -- Run it
unit_test = nil -- Hide it

id='n1107' href='#n1107'>1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466
/*
Minetest-c55
Copyright (C) 2010-2011 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 General Public License as published by
the Free Software Foundation; either version 2 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 General Public License for more details.

You should have received a copy of the GNU 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 "inventory.h"
#include "serialization.h"
#include "utility.h"
#include "debug.h"
#include <sstream>
#include "main.h" // For tsrc, g_toolmanager
#include "serverobject.h"
#include "content_mapnode.h"
#include "content_sao.h"
#include "environment.h"
#include "mapblock.h"
#include "player.h"
#include "log.h"
#include "nodedef.h"
#include "tooldef.h"
#include "craftitemdef.h"
#include "gamedef.h"
#include "scriptapi.h"
#include "strfnd.h"
#include "nameidmapping.h" // For loading legacy MaterialItems
#include "serverremoteplayer.h"

/*
	InventoryItem
*/

InventoryItem::InventoryItem(IGameDef *gamedef, u16 count):
	m_gamedef(gamedef),
	m_count(count)
{
	assert(m_gamedef);
}

InventoryItem::~InventoryItem()
{
}

content_t content_translate_from_19_to_internal(content_t c_from)
{
	for(u32 i=0; i<sizeof(trans_table_19)/sizeof(trans_table_19[0]); i++)
	{
		if(trans_table_19[i][1] == c_from)
		{
			return trans_table_19[i][0];
		}
	}
	return c_from;
}

InventoryItem* InventoryItem::deSerialize(std::istream &is, IGameDef *gamedef)
{
	DSTACK(__FUNCTION_NAME);

	//is.imbue(std::locale("C"));
	// Read name
	std::string name;
	std::getline(is, name, ' ');
	
	if(name == "MaterialItem")
	{
		// u16 reads directly as a number (u8 doesn't)
		u16 material;
		is>>material;
		u16 count;
		is>>count;
		// Convert old materials
		if(material <= 0xff)
			material = content_translate_from_19_to_internal(material);
		if(material > MAX_CONTENT)
			throw SerializationError("Too large material number");
		return new MaterialItem(gamedef, material, count);
	}
	else if(name == "MaterialItem2")
	{
		u16 material;
		is>>material;
		u16 count;
		is>>count;
		if(material > MAX_CONTENT)
			throw SerializationError("Too large material number");
		return new MaterialItem(gamedef, material, count);
	}
	else if(name == "node" || name == "NodeItem" || name == "MaterialItem3")
	{
		std::string all;
		std::getline(is, all, '\n');
		std::string nodename;
		// First attempt to read inside ""
		Strfnd fnd(all);
		fnd.next("\"");
		// If didn't skip to end, we have ""s
		if(!fnd.atend()){
			nodename = fnd.next("\"");
		} else { // No luck, just read a word then
			fnd.start(all);
			nodename = fnd.next(" ");
		}
		fnd.skip_over(" ");
		u16 count = stoi(trim(fnd.next("")));
		if(count == 0)
			count = 1;
		return new MaterialItem(gamedef, nodename, count);
	}
	else if(name == "MBOItem")
	{
		std::string inventorystring;
		std::getline(is, inventorystring, '|');
		throw SerializationError("MBOItem not supported anymore");
	}
	else if(name == "craft" || name == "CraftItem")
	{
		std::string all;
		std::getline(is, all, '\n');
		std::string subname;
		// First attempt to read inside ""
		Strfnd fnd(all);
		fnd.next("\"");
		// If didn't skip to end, we have ""s
		if(!fnd.atend()){
			subname = fnd.next("\"");
		} else { // No luck, just read a word then
			fnd.start(all);
			subname = fnd.next(" ");
		}
		// Then read count
		fnd.skip_over(" ");
		u16 count = stoi(trim(fnd.next("")));
		if(count == 0)
			count = 1;
		return new CraftItem(gamedef, subname, count);
	}
	else if(name == "tool" || name == "ToolItem")
	{
		std::string all;
		std::getline(is, all, '\n');
		std::string toolname;
		// First attempt to read inside ""
		Strfnd fnd(all);
		fnd.next("\"");
		// If didn't skip to end, we have ""s
		if(!fnd.atend()){
			toolname = fnd.next("\"");
		} else { // No luck, just read a word then
			fnd.start(all);
			toolname = fnd.next(" ");
		}
		// Then read wear
		fnd.skip_over(" ");
		u16 wear = stoi(trim(fnd.next("")));
		return new ToolItem(gamedef, toolname, wear);
	}
	else
	{
		infostream<<"Unknown InventoryItem name=\""<<name<<"\""<<std::endl;
		throw SerializationError("Unknown InventoryItem name");
	}
}

InventoryItem* InventoryItem::deSerialize(const std::string &str,
		IGameDef *gamedef)
{
	std::istringstream is(str, std::ios_base::binary);
	return deSerialize(is, gamedef);
}

std::string InventoryItem::getItemString() {
	// Get item string
	std::ostringstream os(std::ios_base::binary);
	serialize(os);
	return os.str();
}

bool InventoryItem::dropOrPlace(ServerEnvironment *env,
		ServerActiveObject *dropper,
		v3f pos, bool place, s16 count)
{
	/*
		Ensure that the block is loaded so that the item
		can properly be added to the static list too
	*/
	v3s16 blockpos = getNodeBlockPos(floatToInt(pos, BS));
	MapBlock *block = env->getMap().emergeBlock(blockpos, false);
	if(block==NULL)
	{
		infostream<<"InventoryItem::dropOrPlace(): FAIL: block not found: "
				<<blockpos.X<<","<<blockpos.Y<<","<<blockpos.Z
				<<std::endl;
		return false;
	}

	/*
		Take specified number of items,
		but limit to getDropCount().
	*/
	s16 dropcount = getDropCount();
	if(count < 0 || count > dropcount)
		count = dropcount;
	if(count < 0 || count > getCount())
		count = getCount();
	if(count > 0)
	{
		/*
			Create an ItemSAO
		*/
		pos.Y -= BS*0.25; // let it drop a bit
		// Randomize a bit
		//pos.X += BS*0.2*(float)myrand_range(-1000,1000)/1000.0;
		//pos.Z += BS*0.2*(float)myrand_range(-1000,1000)/1000.0;
		// Create object
		ServerActiveObject *obj = new ItemSAO(env, pos, getItemString());
		// Add the object to the environment
		env->addActiveObject(obj);
		infostream<<"Dropped item"<<std::endl;

		setCount(getCount() - count);
	}

	return getCount() < 1; // delete the item?
}

/*
	MaterialItem
*/

MaterialItem::MaterialItem(IGameDef *gamedef, std::string nodename, u16 count):
	InventoryItem(gamedef, count)
{
	if(nodename == "")
		nodename = "unknown_block";
	m_nodename = nodename;
}
// Legacy constructor
MaterialItem::MaterialItem(IGameDef *gamedef, content_t content, u16 count):
	InventoryItem(gamedef, count)
{
	NameIdMapping legacy_nimap;
	content_mapnode_get_name_id_mapping(&legacy_nimap);
	std::string nodename;
	legacy_nimap.getName(content, nodename);
	if(nodename == "")
		nodename = "unknown_block";
	m_nodename = nodename;
}

#ifndef SERVER
video::ITexture * MaterialItem::getImage() const
{
	return m_gamedef->getNodeDefManager()->get(m_nodename).inventory_texture;
}
#endif

bool MaterialItem::isCookable() const
{
	INodeDefManager *ndef = m_gamedef->ndef();
	const ContentFeatures &f = ndef->get(m_nodename);
	return (f.cookresult_item != "");
}

InventoryItem *MaterialItem::createCookResult() const
{
	INodeDefManager *ndef = m_gamedef->ndef();
	const ContentFeatures &f = ndef->get(m_nodename);
	std::istringstream is(f.cookresult_item, std::ios::binary);
	return InventoryItem::deSerialize(is, m_gamedef);
}

float MaterialItem::getCookTime() const
{
	INodeDefManager *ndef = m_gamedef->ndef();
	const ContentFeatures &f = ndef->get(m_nodename);
	return f.furnace_cooktime;
}

float MaterialItem::getBurnTime() const
{
	INodeDefManager *ndef = m_gamedef->ndef();
	const ContentFeatures &f = ndef->get(m_nodename);
	return f.furnace_burntime;
}

content_t MaterialItem::getMaterial() const
{
	INodeDefManager *ndef = m_gamedef->ndef();
	content_t id = CONTENT_IGNORE;
	ndef->getId(m_nodename, id);
	return id;
}

/*
	ToolItem
*/

std::string ToolItem::getImageBasename() const
{
	return m_gamedef->getToolDefManager()->getImagename(m_toolname);
}

#ifndef SERVER
video::ITexture * ToolItem::getImage() const
{
	ITextureSource *tsrc = m_gamedef->tsrc();

	std::string basename = getImageBasename();
	
	/*
		Calculate a progress value with sane amount of
		maximum states
	*/
	u32 maxprogress = 30;
	u32 toolprogress = (65535-m_wear)/(65535/maxprogress);
	
	float value_f = (float)toolprogress / (float)maxprogress;
	std::ostringstream os;
	os<<basename<<"^[progressbar"<<value_f;

	return tsrc->getTextureRaw(os.str());
}

video::ITexture * ToolItem::getImageRaw() const
{
	ITextureSource *tsrc = m_gamedef->tsrc();
	
	return tsrc->getTextureRaw(getImageBasename());
}
#endif

/*
	CraftItem
*/

#ifndef SERVER
video::ITexture * CraftItem::getImage() const
{
	ICraftItemDefManager *cidef = m_gamedef->cidef();
	ITextureSource *tsrc = m_gamedef->tsrc();
	std::string imagename = cidef->getImagename(m_subname);
	return tsrc->getTextureRaw(imagename);
}
#endif

u16 CraftItem::getStackMax() const
{
	ICraftItemDefManager *cidef = m_gamedef->cidef();
	const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
	if(def == NULL)
		return InventoryItem::getStackMax();
	return def->stack_max;
}

bool CraftItem::isUsable() const
{
	ICraftItemDefManager *cidef = m_gamedef->cidef();
	const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
	return def != NULL && def->usable;
}

bool CraftItem::isCookable() const
{
	ICraftItemDefManager *cidef = m_gamedef->cidef();
	const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
	return def != NULL && def->cookresult_item != "";
}

InventoryItem *CraftItem::createCookResult() const
{
	ICraftItemDefManager *cidef = m_gamedef->cidef();
	const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
	if(def == NULL)
		return InventoryItem::createCookResult();
	std::istringstream is(def->cookresult_item, std::ios::binary);
	return InventoryItem::deSerialize(is, m_gamedef);
}

float CraftItem::getCookTime() const
{
	ICraftItemDefManager *cidef = m_gamedef->cidef();
	const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
	if (def == NULL)
		return InventoryItem::getCookTime();
	return def->furnace_cooktime;
}

float CraftItem::getBurnTime() const
{
	ICraftItemDefManager *cidef = m_gamedef->cidef();
	const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
	if (def == NULL)
		return InventoryItem::getBurnTime();
	return def->furnace_burntime;
}

s16 CraftItem::getDropCount() const
{
	// Special cases
	ICraftItemDefManager *cidef = m_gamedef->cidef();
	const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
	if(def != NULL && def->dropcount >= 0)
		return def->dropcount;
	// Default
	return InventoryItem::getDropCount();
}

bool CraftItem::areLiquidsPointable() const
{
	ICraftItemDefManager *cidef = m_gamedef->cidef();
	const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
	return def != NULL && def->liquids_pointable;
}

bool CraftItem::dropOrPlace(ServerEnvironment *env,
		ServerActiveObject *dropper,
		v3f pos, bool place, s16 count)
{
	if(count == 0)
		return false;

	bool callback_exists = false;
	bool result = false;

	if(place)
	{
		result = scriptapi_craftitem_on_place_on_ground(
				env->getLua(),
				m_subname.c_str(), dropper, pos,
				callback_exists);
	}

	// note: on_drop is fallback for on_place_on_ground

	if(!callback_exists)
	{
		result = scriptapi_craftitem_on_drop(
				env->getLua(),
				m_subname.c_str(), dropper, pos,
				callback_exists);
	}

	if(callback_exists)
	{
		// If the callback returned true, drop one item
		if(result)
			setCount(getCount() - 1);
		return getCount() < 1;
	}
	else
	{
		// If neither on_place_on_ground (if place==true)
		// nor on_drop exists, call the base implementation
		return InventoryItem::dropOrPlace(env, dropper, pos, place, count);
	}
}

bool CraftItem::use(ServerEnvironment *env,
		ServerActiveObject *user,
		const PointedThing& pointed)
{
	bool callback_exists = false;
	bool result = false;

	result = scriptapi_craftitem_on_use(
			env->getLua(),
			m_subname.c_str(), user, pointed,
			callback_exists);

	if(callback_exists)
	{
		// If the callback returned true, drop one item
		if(result)
			setCount(getCount() - 1);
		return getCount() < 1;
	}
	else
	{
		// If neither on_place_on_ground (if place==true)
		// nor on_drop exists, call the base implementation
		return InventoryItem::use(env, user, pointed);
	}
}

/*
	Inventory
*/

InventoryList::InventoryList(std::string name, u32 size)
{
	m_name = name;
	m_size = size;
	clearItems();
	//m_dirty = false;
}

InventoryList::~InventoryList()
{
	for(u32 i=0; i<m_items.size(); i++)
	{
		if(m_items[i])
			delete m_items[i];
	}
}

void InventoryList::clearItems()
{
	for(u32 i=0; i<m_items.size(); i++)
	{
		if(m_items[i])
			delete m_items[i];
	}

	m_items.clear();

	for(u32 i=0; i<m_size; i++)
	{
		m_items.push_back(NULL);
	}

	//setDirty(true);
}

void InventoryList::serialize(std::ostream &os) const
{
	//os.imbue(std::locale("C"));
	
	for(u32 i=0; i<m_items.size(); i++)
	{
		InventoryItem *item = m_items[i];
		if(item != NULL)
		{
			os<<"Item ";
			item->serialize(os);
		}
		else
		{
			os<<"Empty";
		}
		os<<"\n";
	}

	os<<"EndInventoryList\n";
}

void InventoryList::deSerialize(std::istream &is, IGameDef *gamedef)
{
	//is.imbue(std::locale("C"));

	clearItems();
	u32 item_i = 0;

	for(;;)
	{
		std::string line;
		std::getline(is, line, '\n');

		std::istringstream iss(line);
		//iss.imbue(std::locale("C"));

		std::string name;
		std::getline(iss, name, ' ');

		if(name == "EndInventoryList")
		{
			break;
		}
		// This is a temporary backwards compatibility fix
		else if(name == "end")
		{
			break;
		}
		else if(name == "Item")
		{
			if(item_i > getSize() - 1)
				throw SerializationError("too many items");
			InventoryItem *item = InventoryItem::deSerialize(iss, gamedef);
			m_items[item_i++] = item;
		}
		else if(name == "Empty")
		{
			if(item_i > getSize() - 1)
				throw SerializationError("too many items");
			m_items[item_i++] = NULL;
		}
		else
		{
			throw SerializationError("Unknown inventory identifier");
		}
	}
}

InventoryList::InventoryList(const InventoryList &other)
{
	/*
		Do this so that the items get cloned. Otherwise the pointers
		in the array will just get copied.
	*/
	*this = other;
}

InventoryList & InventoryList::operator = (const InventoryList &other)
{
	m_name = other.m_name;
	m_size = other.m_size;
	clearItems();
	for(u32 i=0; i<other.m_items.size(); i++)
	{
		InventoryItem *item = other.m_items[i];
		if(item != NULL)
		{
			m_items[i] = item->clone();
		}
	}
	//setDirty(true);

	return *this;
}

const std::string &InventoryList::getName() const
{
	return m_name;
}

u32 InventoryList::getSize()
{
	return m_items.size();
}

u32 InventoryList::getUsedSlots()
{
	u32 num = 0;
	for(u32 i=0; i<m_items.size(); i++)
	{
		InventoryItem *item = m_items[i];
		if(item != NULL)
			num++;
	}
	return num;
}

u32 InventoryList::getFreeSlots()
{
	return getSize() - getUsedSlots();
}

const InventoryItem * InventoryList::getItem(u32 i) const
{
	if(i >= m_items.size())
		return NULL;
	return m_items[i];
}

InventoryItem * InventoryList::getItem(u32 i)
{
	if(i >= m_items.size())
		return NULL;
	return m_items[i];
}

InventoryItem * InventoryList::changeItem(u32 i, InventoryItem *newitem)
{
	if(i >= m_items.size())
		return newitem;

	InventoryItem *olditem = m_items[i];
	m_items[i] = newitem;
	//setDirty(true);
	return olditem;
}

void InventoryList::deleteItem(u32 i)
{
	assert(i < m_items.size());
	InventoryItem *item = changeItem(i, NULL);
	if(item)
		delete item;
}

InventoryItem * InventoryList::addItem(InventoryItem *newitem)
{
	if(newitem == NULL)
		return NULL;
	
	/*
		First try to find if it could be added to some existing items
	*/
	for(u32 i=0; i<m_items.size(); i++)
	{
		// Ignore empty slots
		if(m_items[i] == NULL)
			continue;
		// Try adding
		newitem = addItem(i, newitem);
		if(newitem == NULL)
			return NULL; // All was eaten
	}

	/*
		Then try to add it to empty slots
	*/
	for(u32 i=0; i<m_items.size(); i++)
	{
		// Ignore unempty slots
		if(m_items[i] != NULL)
			continue;
		// Try adding
		newitem = addItem(i, newitem);
		if(newitem == NULL)
			return NULL; // All was eaten
	}

	// Return leftover
	return newitem;
}

InventoryItem * InventoryList::addItem(u32 i, InventoryItem *newitem)
{
	if(newitem == NULL)
		return NULL;
	if(i >= m_items.size())
		return newitem;
	
	//setDirty(true);
	
	// If it is an empty position, it's an easy job.
	InventoryItem *to_item = getItem(i);
	if(to_item == NULL)
	{
		m_items[i] = newitem;
		return NULL;
	}
	
	// If not addable, return the item
	if(newitem->addableTo(to_item) == false)
		return newitem;
	
	// If the item fits fully in the slot, add counter and delete it
	if(newitem->getCount() <= to_item->freeSpace())
	{
		to_item->add(newitem->getCount());
		delete newitem;
		return NULL;
	}
	// Else the item does not fit fully. Add all that fits and return
	// the rest.
	else
	{
		u16 freespace = to_item->freeSpace();
		to_item->add(freespace);
		newitem->remove(freespace);
		return newitem;
	}
}

bool InventoryList::itemFits(const u32 i, const InventoryItem *newitem)
{
	// If it is an empty position, it's an easy job.
	const InventoryItem *to_item = getItem(i);
	if(to_item == NULL)
	{
		return true;
	}
	
	// If not addable, fail
	if(newitem->addableTo(to_item) == false)
		return false;
	
	// If the item fits fully in the slot, pass
	if(newitem->getCount() <= to_item->freeSpace())
	{
		return true;
	}

	return false;
}

bool InventoryList::roomForItem(const InventoryItem *item)
{
	for(u32 i=0; i<m_items.size(); i++)
		if(itemFits(i, item))
			return true;
	return false;
}

bool InventoryList::roomForCookedItem(const InventoryItem *item)
{
	if(!item)
		return false;
	const InventoryItem *cook = item->createCookResult();
	if(!cook)
		return false;
	bool room = roomForItem(cook);
	delete cook;
	return room;
}

InventoryItem * InventoryList::takeItem(u32 i, u32 count)
{
	if(count == 0)
		return NULL;
	
	//setDirty(true);

	InventoryItem *item = getItem(i);
	// If it is an empty position, return NULL
	if(item == NULL)
		return NULL;
	
	if(count >= item->getCount())
	{
		// Get the item by swapping NULL to its place
		return changeItem(i, NULL);
	}
	else
	{
		InventoryItem *item2 = item->clone();
		item->remove(count);
		item2->setCount(count);
		return item2;
	}
	
	return false;
}

void InventoryList::decrementMaterials(u16 count)
{
	for(u32 i=0; i<m_items.size(); i++)
	{
		InventoryItem *item = takeItem(i, count);
		if(item)
			delete item;
	}
}

void InventoryList::print(std::ostream &o)
{
	o<<"InventoryList:"<<std::endl;
	for(u32 i=0; i<m_items.size(); i++)
	{
		InventoryItem *item = m_items[i];
		if(item != NULL)
		{
			o<<i<<": ";
			item->serialize(o);
			o<<"\n";
		}
	}
}

/*
	Inventory
*/

Inventory::~Inventory()
{
	clear();
}

void Inventory::clear()
{
	for(u32 i=0; i<m_lists.size(); i++)
	{
		delete m_lists[i];
	}
	m_lists.clear();
}

Inventory::Inventory()
{
}

Inventory::Inventory(const Inventory &other)
{
	*this = other;
}

Inventory & Inventory::operator = (const Inventory &other)
{
	clear();
	for(u32 i=0; i<other.m_lists.size(); i++)
	{
		m_lists.push_back(new InventoryList(*other.m_lists[i]));
	}
	return *this;
}

void Inventory::serialize(std::ostream &os) const
{
	for(u32 i=0; i<m_lists.size(); i++)
	{
		InventoryList *list = m_lists[i];
		os<<"List "<<list->getName()<<" "<<list->getSize()<<"\n";
		list->serialize(os);
	}

	os<<"EndInventory\n";
}

void Inventory::deSerialize(std::istream &is, IGameDef *gamedef)
{
	clear();

	for(;;)
	{
		std::string line;
		std::getline(is, line, '\n');

		std::istringstream iss(line);

		std::string name;
		std::getline(iss, name, ' ');

		if(name == "EndInventory")
		{
			break;
		}
		// This is a temporary backwards compatibility fix
		else if(name == "end")
		{
			break;
		}
		else if(name == "List")
		{
			std::string listname;
			u32 listsize;

			std::getline(iss, listname, ' ');
			iss>>listsize;

			InventoryList *list = new InventoryList(listname, listsize);
			list->deSerialize(is, gamedef);

			m_lists.push_back(list);
		}
		else
		{
			throw SerializationError("Unknown inventory identifier");
		}
	}
}

InventoryList * Inventory::addList(const std::string &name, u32 size)
{
	s32 i = getListIndex(name);
	if(i != -1)
	{
		if(m_lists[i]->getSize() != size)
		{
			delete m_lists[i];
			m_lists[i] = new InventoryList(name, size);
		}
		return m_lists[i];
	}
	else
	{
		m_lists.push_back(new InventoryList(name, size));
		return m_lists.getLast();
	}
}

InventoryList * Inventory::getList(const std::string &name)
{
	s32 i = getListIndex(name);
	if(i == -1)
		return NULL;
	return m_lists[i];
}

bool Inventory::deleteList(const std::string &name)
{
	s32 i = getListIndex(name);
	if(i == -1)
		return false;
	delete m_lists[i];
	m_lists.erase(i);
	return true;
}

const InventoryList * Inventory::getList(const std::string &name) const
{
	s32 i = getListIndex(name);
	if(i == -1)
		return NULL;
	return m_lists[i];
}

const s32 Inventory::getListIndex(const std::string &name) const
{
	for(u32 i=0; i<m_lists.size(); i++)
	{
		if(m_lists[i]->getName() == name)
			return i;
	}
	return -1;
}

/*
	InventoryAction
*/

InventoryAction * InventoryAction::deSerialize(std::istream &is)
{
	std::string type;
	std::getline(is, type, ' ');

	InventoryAction *a = NULL;

	if(type == "Move")
	{
		a = new IMoveAction(is);
	}
	else if(type == "Drop")
	{
		a = new IDropAction(is);
	}

	return a;
}

static std::string describeC(const struct InventoryContext *c)
{
	if(c->current_player == NULL)
		return "current_player=NULL";
	else
		return std::string("current_player=") + c->current_player->getName();
}

IMoveAction::IMoveAction(std::istream &is)
{
	std::string ts;

	std::getline(is, ts, ' ');
	count = stoi(ts);

	std::getline(is, from_inv, ' ');

	std::getline(is, from_list, ' ');

	std::getline(is, ts, ' ');
	from_i = stoi(ts);

	std::getline(is, to_inv, ' ');

	std::getline(is, to_list, ' ');

	std::getline(is, ts, ' ');
	to_i = stoi(ts);
}

void IMoveAction::apply(InventoryContext *c, InventoryManager *mgr,
		ServerEnvironment *env)
{
	Inventory *inv_from = mgr->getInventory(c, from_inv);
	Inventory *inv_to = mgr->getInventory(c, to_inv);
	
	if(!inv_from){
		infostream<<"IMoveAction::apply(): FAIL: source inventory not found: "
				<<"context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
				<<", to_inv=\""<<to_inv<<"\""<<std::endl;
		return;
	}
	if(!inv_to){
		infostream<<"IMoveAction::apply(): FAIL: destination inventory not found: "
				"context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
				<<", to_inv=\""<<to_inv<<"\""<<std::endl;
		return;
	}

	InventoryList *list_from = inv_from->getList(from_list);
	InventoryList *list_to = inv_to->getList(to_list);

	/*
		If a list doesn't exist or the source item doesn't exist
	*/
	if(!list_from){
		infostream<<"IMoveAction::apply(): FAIL: source list not found: "
				<<"context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
				<<", from_list=\""<<from_list<<"\""<<std::endl;
		return;
	}
	if(!list_to){
		infostream<<"IMoveAction::apply(): FAIL: destination list not found: "
				<<"context=["<<describeC(c)<<"], to_inv=\""<<to_inv<<"\""
				<<", to_list=\""<<to_list<<"\""<<std::endl;
		return;
	}
	if(list_from->getItem(from_i) == NULL)
	{
		infostream<<"IMoveAction::apply(): FAIL: source item not found: "
				<<"context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
				<<", from_list=\""<<from_list<<"\""
				<<" from_i="<<from_i<<std::endl;
		return;
	}
	/*
		If the source and the destination slots are the same
	*/
	if(inv_from == inv_to && list_from == list_to && from_i == to_i)
	{
		infostream<<"IMoveAction::apply(): FAIL: source and destination slots "
				<<"are the same: inv=\""<<from_inv<<"\" list=\""<<from_list
				<<"\" i="<<from_i<<std::endl;
		return;
	}
	
	// Take item from source list
	InventoryItem *item1 = NULL;
	if(count == 0)
		item1 = list_from->changeItem(from_i, NULL);
	else
		item1 = list_from->takeItem(from_i, count);

	// Try to add the item to destination list
	InventoryItem *olditem = item1;
	item1 = list_to->addItem(to_i, item1);

	// If something is returned, the item was not fully added
	if(item1 != NULL)
	{
		// If olditem is returned, nothing was added.
		bool nothing_added = (item1 == olditem);
		
		// If something else is returned, part of the item was left unadded.
		// Add the other part back to the source item
		list_from->addItem(from_i, item1);

		// If olditem is returned, nothing was added.
		// Swap the items
		if(nothing_added)
		{
			// Take item from source list
			item1 = list_from->changeItem(from_i, NULL);
			// Adding was not possible, swap the items.
			InventoryItem *item2 = list_to->changeItem(to_i, item1);
			// Put item from destination list to the source list
			list_from->changeItem(from_i, item2);
		}
	}

	mgr->inventoryModified(c, from_inv);
	if(from_inv != to_inv)
		mgr->inventoryModified(c, to_inv);
	
	infostream<<"IMoveAction::apply(): moved at "
			<<"["<<describeC(c)<<"]"
			<<" from inv=\""<<from_inv<<"\""
			<<" list=\""<<from_list<<"\""
			<<" i="<<from_i
			<<" to inv=\""<<to_inv<<"\""
			<<" list=\""<<to_list<<"\""
			<<" i="<<to_i
			<<std::endl;
}

IDropAction::IDropAction(std::istream &is)
{
	std::string ts;

	std::getline(is, ts, ' ');
	count = stoi(ts);

	std::getline(is, from_inv, ' ');

	std::getline(is, from_list, ' ');

	std::getline(is, ts, ' ');
	from_i = stoi(ts);
}

void IDropAction::apply(InventoryContext *c, InventoryManager *mgr,
		ServerEnvironment *env)
{
	if(c->current_player == NULL){
		infostream<<"IDropAction::apply(): FAIL: current_player is NULL"<<std::endl;
		return;
	}

	// Do NOT cast directly to ServerActiveObject*, it breaks
	// because of multiple inheritance.
	ServerActiveObject *dropper =
		static_cast<ServerActiveObject*>(
		static_cast<ServerRemotePlayer*>(
			c->current_player
		));

	Inventory *inv_from = mgr->getInventory(c, from_inv);
	
	if(!inv_from){
		infostream<<"IDropAction::apply(): FAIL: source inventory not found: "
				<<"context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""<<std::endl;
		return;
	}

	InventoryList *list_from = inv_from->getList(from_list);

	/*
		If a list doesn't exist or the source item doesn't exist
	*/
	if(!list_from){
		infostream<<"IDropAction::apply(): FAIL: source list not found: "
				<<"context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
				<<", from_list=\""<<from_list<<"\""<<std::endl;
		return;
	}
	InventoryItem *item = list_from->getItem(from_i);
	if(item == NULL)
	{
		infostream<<"IDropAction::apply(): FAIL: source item not found: "
				<<"context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
				<<", from_list=\""<<from_list<<"\""
				<<" from_i="<<from_i<<std::endl;
		return;
	}

	v3f pos = dropper->getBasePosition();
	pos.Y += 0.5*BS;

	s16 count2 = count;
	if(count2 == 0)
		count2 = -1;

	/*
		Drop the item
	*/
	bool remove = item->dropOrPlace(env, dropper, pos, false, count2);
	if(remove)
		list_from->deleteItem(from_i);

	mgr->inventoryModified(c, from_inv);

	infostream<<"IDropAction::apply(): dropped "
			<<"["<<describeC(c)<<"]"
			<<" from inv=\""<<from_inv<<"\""
			<<" list=\""<<from_list<<"\""
			<<" i="<<from_i
			<<std::endl;
}

/*
	Craft checking system
*/

bool ItemSpec::checkItem(const InventoryItem *item) const
{
	if(type == ITEM_NONE)
	{
		// Has to be no item
		if(item != NULL)
			return false;
		return true;
	}
	
	// There should be an item
	if(item == NULL)
		return false;

	std::string itemname = item->getName();

	if(type == ITEM_MATERIAL)
	{
		if(itemname != "MaterialItem")
			return false;
		MaterialItem *mitem = (MaterialItem*)item;
		if(num != 65535){
			if(mitem->getMaterial() != num)
				return false;
		} else {
			if(mitem->getNodeName() != name)
				return false;
		}
	}
	else if(type == ITEM_CRAFT)
	{
		if(itemname != "CraftItem")
			return false;
		CraftItem *mitem = (CraftItem*)item;
		if(mitem->getSubName() != name)
			return false;
	}
	else if(type == ITEM_TOOL)
	{
		// Not supported yet
		assert(0);
	}
	else if(type == ITEM_MBO)
	{
		// Not supported yet
		assert(0);
	}
	else
	{
		// Not supported yet
		assert(0);
	}
	return true;
}

bool checkItemCombination(InventoryItem const * const *items, const ItemSpec *specs)
{
	u16 items_min_x = 100;
	u16 items_max_x = 100;
	u16 items_min_y = 100;
	u16 items_max_y = 100;
	for(u16 y=0; y<3; y++)
	for(u16 x=0; x<3; x++)
	{
		if(items[y*3 + x] == NULL)
			continue;
		if(items_min_x == 100 || x < items_min_x)
			items_min_x = x;
		if(items_min_y == 100 || y < items_min_y)
			items_min_y = y;
		if(items_max_x == 100 || x > items_max_x)
			items_max_x = x;
		if(items_max_y == 100 || y > items_max_y)
			items_max_y = y;
	}
	// No items at all, just return false
	if(items_min_x == 100)
		return false;
	
	u16 items_w = items_max_x - items_min_x + 1;
	u16 items_h = items_max_y - items_min_y + 1;

	u16 specs_min_x = 100;
	u16 specs_max_x = 100;
	u16 specs_min_y = 100;
	u16 specs_max_y = 100;
	for(u16 y=0; y<3; y++)
	for(u16 x=0; x<3; x++)
	{
		if(specs[y*3 + x].type == ITEM_NONE)
			continue;
		if(specs_min_x == 100 || x < specs_min_x)
			specs_min_x = x;
		if(specs_min_y == 100 || y < specs_min_y)
			specs_min_y = y;
		if(specs_max_x == 100 || x > specs_max_x)
			specs_max_x = x;
		if(specs_max_y == 100 || y > specs_max_y)
			specs_max_y = y;
	}
	// No specs at all, just return false
	if(specs_min_x == 100)
		return false;

	u16 specs_w = specs_max_x - specs_min_x + 1;
	u16 specs_h = specs_max_y - specs_min_y + 1;

	// Different sizes
	if(items_w != specs_w || items_h != specs_h)
		return false;

	for(u16 y=0; y<specs_h; y++)
	for(u16 x=0; x<specs_w; x++)
	{
		u16 items_x = items_min_x + x;
		u16 items_y = items_min_y + y;
		u16 specs_x = specs_min_x + x;
		u16 specs_y = specs_min_y + y;
		const InventoryItem *item = items[items_y * 3 + items_x];
		const ItemSpec &spec = specs[specs_y * 3 + specs_x];

		if(spec.checkItem(item) == false)
			return false;
	}

	return true;
}

bool checkItemCombination(const InventoryItem * const * items,
		const InventoryItem * const * specs)
{
	u16 items_min_x = 100;
	u16 items_max_x = 100;
	u16 items_min_y = 100;
	u16 items_max_y = 100;
	for(u16 y=0; y<3; y++)
	for(u16 x=0; x<3; x++)
	{
		if(items[y*3 + x] == NULL)
			continue;
		if(items_min_x == 100 || x < items_min_x)
			items_min_x = x;
		if(items_min_y == 100 || y < items_min_y)
			items_min_y = y;
		if(items_max_x == 100 || x > items_max_x)
			items_max_x = x;
		if(items_max_y == 100 || y > items_max_y)
			items_max_y = y;
	}
	// No items at all, just return false
	if(items_min_x == 100)
		return false;
	
	u16 items_w = items_max_x - items_min_x + 1;
	u16 items_h = items_max_y - items_min_y + 1;

	u16 specs_min_x = 100;
	u16 specs_max_x = 100;
	u16 specs_min_y = 100;
	u16 specs_max_y = 100;
	for(u16 y=0; y<3; y++)
	for(u16 x=0; x<3; x++)
	{
		if(specs[y*3 + x] == NULL)
			continue;
		if(specs_min_x == 100 || x < specs_min_x)
			specs_min_x = x;
		if(specs_min_y == 100 || y < specs_min_y)
			specs_min_y = y;
		if(specs_max_x == 100 || x > specs_max_x)
			specs_max_x = x;
		if(specs_max_y == 100 || y > specs_max_y)
			specs_max_y = y;
	}
	// No specs at all, just return false
	if(specs_min_x == 100)
		return false;

	u16 specs_w = specs_max_x - specs_min_x + 1;
	u16 specs_h = specs_max_y - specs_min_y + 1;

	// Different sizes
	if(items_w != specs_w || items_h != specs_h)
		return false;

	for(u16 y=0; y<specs_h; y++)
	for(u16 x=0; x<specs_w; x++)
	{
		u16 items_x = items_min_x + x;
		u16 items_y = items_min_y + y;
		u16 specs_x = specs_min_x + x;
		u16 specs_y = specs_min_y + y;
		const InventoryItem *item = items[items_y * 3 + items_x];
		const InventoryItem *spec = specs[specs_y * 3 + specs_x];
		
		if(item == NULL && spec == NULL)
			continue;
		if(item == NULL && spec != NULL)
			return false;
		if(item != NULL && spec == NULL)
			return false;
		if(!spec->isSubsetOf(item))
			return false;
	}

	return true;
}

//END