1 /*
2 script/lua_api/l_craft.cpp
3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4 */
5 
6 /*
7 This file is part of Freeminer.
8 
9 Freeminer is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13 
14 Freeminer  is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with Freeminer.  If not, see <http://www.gnu.org/licenses/>.
21 */
22 
23 
24 #include "lua_api/l_craft.h"
25 #include "lua_api/l_internal.h"
26 #include "lua_api/l_item.h"
27 #include "common/c_converter.h"
28 #include "common/c_content.h"
29 #include "server.h"
30 #include "craftdef.h"
31 
32 struct EnumString ModApiCraft::es_CraftMethod[] =
33 {
34 	{CRAFT_METHOD_NORMAL, "normal"},
35 	{CRAFT_METHOD_COOKING, "cooking"},
36 	{CRAFT_METHOD_FUEL, "fuel"},
37 	{0, NULL},
38 };
39 
40 
41 // helper for register_craft
readCraftRecipeShaped(lua_State * L,int index,int & width,std::vector<std::string> & recipe)42 bool ModApiCraft::readCraftRecipeShaped(lua_State *L, int index,
43 		int &width, std::vector<std::string> &recipe)
44 {
45 	if(index < 0)
46 		index = lua_gettop(L) + 1 + index;
47 
48 	if(!lua_istable(L, index))
49 		return false;
50 
51 	lua_pushnil(L);
52 	int rowcount = 0;
53 	while(lua_next(L, index) != 0){
54 		int colcount = 0;
55 		// key at index -2 and value at index -1
56 		if(!lua_istable(L, -1))
57 			return false;
58 		int table2 = lua_gettop(L);
59 		lua_pushnil(L);
60 		while(lua_next(L, table2) != 0){
61 			// key at index -2 and value at index -1
62 			if(!lua_isstring(L, -1))
63 				return false;
64 			recipe.push_back(lua_tostring(L, -1));
65 			// removes value, keeps key for next iteration
66 			lua_pop(L, 1);
67 			colcount++;
68 		}
69 		if(rowcount == 0){
70 			width = colcount;
71 		} else {
72 			if(colcount != width)
73 				return false;
74 		}
75 		// removes value, keeps key for next iteration
76 		lua_pop(L, 1);
77 		rowcount++;
78 	}
79 	return width != 0;
80 }
81 
82 // helper for register_craft
readCraftRecipeShapeless(lua_State * L,int index,std::vector<std::string> & recipe)83 bool ModApiCraft::readCraftRecipeShapeless(lua_State *L, int index,
84 		std::vector<std::string> &recipe)
85 {
86 	if(index < 0)
87 		index = lua_gettop(L) + 1 + index;
88 
89 	if(!lua_istable(L, index))
90 		return false;
91 
92 	lua_pushnil(L);
93 	while(lua_next(L, index) != 0){
94 		// key at index -2 and value at index -1
95 		if(!lua_isstring(L, -1))
96 			return false;
97 		recipe.push_back(lua_tostring(L, -1));
98 		// removes value, keeps key for next iteration
99 		lua_pop(L, 1);
100 	}
101 	return true;
102 }
103 
104 // helper for register_craft
readCraftReplacements(lua_State * L,int index,CraftReplacements & replacements)105 bool ModApiCraft::readCraftReplacements(lua_State *L, int index,
106 		CraftReplacements &replacements)
107 {
108 	if(index < 0)
109 		index = lua_gettop(L) + 1 + index;
110 
111 	if(!lua_istable(L, index))
112 		return false;
113 
114 	lua_pushnil(L);
115 	while(lua_next(L, index) != 0){
116 		// key at index -2 and value at index -1
117 		if(!lua_istable(L, -1))
118 			return false;
119 		lua_rawgeti(L, -1, 1);
120 		if(!lua_isstring(L, -1))
121 			return false;
122 		std::string replace_from = lua_tostring(L, -1);
123 		lua_pop(L, 1);
124 		lua_rawgeti(L, -1, 2);
125 		if(!lua_isstring(L, -1))
126 			return false;
127 		std::string replace_to = lua_tostring(L, -1);
128 		lua_pop(L, 1);
129 		replacements.pairs.push_back(
130 				std::make_pair(replace_from, replace_to));
131 		// removes value, keeps key for next iteration
132 		lua_pop(L, 1);
133 	}
134 	return true;
135 }
136 // register_craft({output=item, recipe={{item00,item10},{item01,item11}})
l_register_craft(lua_State * L)137 int ModApiCraft::l_register_craft(lua_State *L)
138 {
139 	NO_MAP_LOCK_REQUIRED;
140 	//infostream<<"register_craft"<<std::endl;
141 	luaL_checktype(L, 1, LUA_TTABLE);
142 	int table = 1;
143 
144 	// Get the writable craft definition manager from the server
145 	IWritableCraftDefManager *craftdef =
146 			getServer(L)->getWritableCraftDefManager();
147 
148 	std::string type = getstringfield_default(L, table, "type", "shaped");
149 
150 	/*
151 		CraftDefinitionShaped
152 	*/
153 	if(type == "shaped"){
154 		std::string output = getstringfield_default(L, table, "output", "");
155 		if(output == "")
156 			throw LuaError("Crafting definition is missing an output");
157 
158 		int width = 0;
159 		std::vector<std::string> recipe;
160 		lua_getfield(L, table, "recipe");
161 		if(lua_isnil(L, -1))
162 			throw LuaError("Crafting definition is missing a recipe"
163 					" (output=\"" + output + "\")");
164 		if(!readCraftRecipeShaped(L, -1, width, recipe))
165 			throw LuaError("Invalid crafting recipe"
166 					" (output=\"" + output + "\")");
167 
168 		CraftReplacements replacements;
169 		lua_getfield(L, table, "replacements");
170 		if(!lua_isnil(L, -1))
171 		{
172 			if(!readCraftReplacements(L, -1, replacements))
173 				throw LuaError("Invalid replacements"
174 						" (output=\"" + output + "\")");
175 		}
176 
177 		CraftDefinition *def = new CraftDefinitionShaped(
178 				output, width, recipe, replacements);
179 		craftdef->registerCraft(def);
180 	}
181 	/*
182 		CraftDefinitionShapeless
183 	*/
184 	else if(type == "shapeless"){
185 		std::string output = getstringfield_default(L, table, "output", "");
186 		if(output == "")
187 			throw LuaError("Crafting definition (shapeless)"
188 					" is missing an output");
189 
190 		std::vector<std::string> recipe;
191 		lua_getfield(L, table, "recipe");
192 		if(lua_isnil(L, -1))
193 			throw LuaError("Crafting definition (shapeless)"
194 					" is missing a recipe"
195 					" (output=\"" + output + "\")");
196 		if(!readCraftRecipeShapeless(L, -1, recipe))
197 			throw LuaError("Invalid crafting recipe"
198 					" (output=\"" + output + "\")");
199 
200 		CraftReplacements replacements;
201 		lua_getfield(L, table, "replacements");
202 		if(!lua_isnil(L, -1))
203 		{
204 			if(!readCraftReplacements(L, -1, replacements))
205 				throw LuaError("Invalid replacements"
206 						" (output=\"" + output + "\")");
207 		}
208 
209 		CraftDefinition *def = new CraftDefinitionShapeless(
210 				output, recipe, replacements);
211 		craftdef->registerCraft(def);
212 	}
213 	/*
214 		CraftDefinitionToolRepair
215 	*/
216 	else if(type == "toolrepair"){
217 		float additional_wear = getfloatfield_default(L, table,
218 				"additional_wear", 0.0);
219 
220 		CraftDefinition *def = new CraftDefinitionToolRepair(
221 				additional_wear);
222 		craftdef->registerCraft(def);
223 	}
224 	/*
225 		CraftDefinitionCooking
226 	*/
227 	else if(type == "cooking"){
228 		std::string output = getstringfield_default(L, table, "output", "");
229 		if(output == "")
230 			throw LuaError("Crafting definition (cooking)"
231 					" is missing an output");
232 
233 		std::string recipe = getstringfield_default(L, table, "recipe", "");
234 		if(recipe == "")
235 			throw LuaError("Crafting definition (cooking)"
236 					" is missing a recipe"
237 					" (output=\"" + output + "\")");
238 
239 		float cooktime = getfloatfield_default(L, table, "cooktime", 3.0);
240 
241 		CraftReplacements replacements;
242 		lua_getfield(L, table, "replacements");
243 		if(!lua_isnil(L, -1))
244 		{
245 			if(!readCraftReplacements(L, -1, replacements))
246 				throw LuaError("Invalid replacements"
247 						" (cooking output=\"" + output + "\")");
248 		}
249 
250 		CraftDefinition *def = new CraftDefinitionCooking(
251 				output, recipe, cooktime, replacements);
252 		craftdef->registerCraft(def);
253 	}
254 	/*
255 		CraftDefinitionFuel
256 	*/
257 	else if(type == "fuel"){
258 		std::string recipe = getstringfield_default(L, table, "recipe", "");
259 		if(recipe == "")
260 			throw LuaError("Crafting definition (fuel)"
261 					" is missing a recipe");
262 
263 		float burntime = getfloatfield_default(L, table, "burntime", 1.0);
264 
265 		CraftReplacements replacements;
266 		lua_getfield(L, table, "replacements");
267 		if(!lua_isnil(L, -1))
268 		{
269 			if(!readCraftReplacements(L, -1, replacements))
270 				throw LuaError("Invalid replacements"
271 						" (fuel recipe=\"" + recipe + "\")");
272 		}
273 
274 		CraftDefinition *def = new CraftDefinitionFuel(
275 				recipe, burntime, replacements);
276 		craftdef->registerCraft(def);
277 	}
278 	else
279 	{
280 		throw LuaError("Unknown crafting definition type: \"" + type + "\"");
281 	}
282 
283 	lua_pop(L, 1);
284 	return 0; /* number of results */
285 }
286 
287 // get_craft_result(input)
l_get_craft_result(lua_State * L)288 int ModApiCraft::l_get_craft_result(lua_State *L)
289 {
290 	NO_MAP_LOCK_REQUIRED;
291 
292 	int input_i = 1;
293 	std::string method_s = getstringfield_default(L, input_i, "method", "normal");
294 	enum CraftMethod method = (CraftMethod)getenumfield(L, input_i, "method",
295 				es_CraftMethod, CRAFT_METHOD_NORMAL);
296 	int width = 1;
297 	lua_getfield(L, input_i, "width");
298 	if(lua_isnumber(L, -1))
299 		width = luaL_checkinteger(L, -1);
300 	lua_pop(L, 1);
301 	lua_getfield(L, input_i, "items");
302 	std::vector<ItemStack> items = read_items(L, -1,getServer(L));
303 	lua_pop(L, 1); // items
304 
305 	IGameDef *gdef = getServer(L);
306 	ICraftDefManager *cdef = gdef->cdef();
307 	CraftInput input(method, width, items);
308 	CraftOutput output;
309 	bool got = cdef->getCraftResult(input, output, true, gdef);
310 	lua_newtable(L); // output table
311 	if(got){
312 		ItemStack item;
313 		item.deSerialize(output.item, gdef->idef());
314 		LuaItemStack::create(L, item);
315 		lua_setfield(L, -2, "item");
316 		setintfield(L, -1, "time", output.time);
317 	} else {
318 		LuaItemStack::create(L, ItemStack());
319 		lua_setfield(L, -2, "item");
320 		setintfield(L, -1, "time", 0);
321 	}
322 	lua_newtable(L); // decremented input table
323 	lua_pushstring(L, method_s.c_str());
324 	lua_setfield(L, -2, "method");
325 	lua_pushinteger(L, width);
326 	lua_setfield(L, -2, "width");
327 	push_items(L, input.items);
328 	lua_setfield(L, -2, "items");
329 	return 2;
330 }
331 
332 // get_craft_recipe(result item)
l_get_craft_recipe(lua_State * L)333 int ModApiCraft::l_get_craft_recipe(lua_State *L)
334 {
335 	NO_MAP_LOCK_REQUIRED;
336 
337 	int k = 1;
338 	int input_i = 1;
339 	std::string o_item = luaL_checkstring(L,input_i);
340 
341 	IGameDef *gdef = getServer(L);
342 	ICraftDefManager *cdef = gdef->cdef();
343 	CraftInput input;
344 	CraftOutput output(o_item,0);
345 	bool got = cdef->getCraftRecipe(input, output, gdef);
346 	lua_newtable(L); // output table
347 	if(got){
348 		lua_newtable(L);
349 		for(std::vector<ItemStack>::const_iterator
350 			i = input.items.begin();
351 			i != input.items.end(); i++, k++)
352 		{
353 			if (i->empty())
354 			{
355 				continue;
356 			}
357 			lua_pushinteger(L,k);
358 			lua_pushstring(L,i->name.c_str());
359 			lua_settable(L, -3);
360 		}
361 		lua_setfield(L, -2, "items");
362 		setintfield(L, -1, "width", input.width);
363 		switch (input.method) {
364 		case CRAFT_METHOD_NORMAL:
365 			lua_pushstring(L,"normal");
366 			break;
367 		case CRAFT_METHOD_COOKING:
368 			lua_pushstring(L,"cooking");
369 			break;
370 		case CRAFT_METHOD_FUEL:
371 			lua_pushstring(L,"fuel");
372 			break;
373 		default:
374 			lua_pushstring(L,"unknown");
375 		}
376 		lua_setfield(L, -2, "type");
377 	} else {
378 		lua_pushnil(L);
379 		lua_setfield(L, -2, "items");
380 		setintfield(L, -1, "width", 0);
381 	}
382 	return 1;
383 }
384 
385 // get_all_craft_recipes(result item)
l_get_all_craft_recipes(lua_State * L)386 int ModApiCraft::l_get_all_craft_recipes(lua_State *L)
387 {
388 	NO_MAP_LOCK_REQUIRED;
389 
390 	std::string o_item = luaL_checkstring(L,1);
391 	IGameDef *gdef = getServer(L);
392 	ICraftDefManager *cdef = gdef->cdef();
393 	CraftInput input;
394 	CraftOutput output(o_item,0);
395 	std::vector<CraftDefinition*> recipes_list;
396 	recipes_list = cdef->getCraftRecipes(output, gdef);
397 	if (recipes_list.empty()) {
398 		lua_pushnil(L);
399 		return 1;
400 	}
401 
402 	lua_createtable(L, recipes_list.size(), 0);
403 	std::vector<CraftDefinition*>::const_iterator iter = recipes_list.begin();
404 	for (u16 i = 0; iter != recipes_list.end(); iter++) {
405 		CraftOutput tmpout;
406 		tmpout.item = "";
407 		tmpout.time = 0;
408 		tmpout = (*iter)->getOutput(input, gdef);
409 		std::string query = tmpout.item;
410 		char *fmtpos, *fmt = &query[0];
411 		if (strtok_r(fmt, " ", &fmtpos) == output.item) {
412 			input = (*iter)->getInput(output, gdef);
413 			lua_newtable(L);
414 			lua_newtable(L); // items
415 			std::vector<ItemStack>::const_iterator iter = input.items.begin();
416 			for (u16 j = 1; iter != input.items.end(); iter++, j++) {
417 				if (iter->empty())
418 					continue;
419 				lua_pushstring(L, iter->name.c_str());
420 				lua_rawseti(L, -2, j);
421 			}
422 			lua_setfield(L, -2, "items");
423 			setintfield(L, -1, "width", input.width);
424 			switch (input.method) {
425 				case CRAFT_METHOD_NORMAL:
426 					lua_pushstring(L, "normal");
427 					break;
428 				case CRAFT_METHOD_COOKING:
429 					lua_pushstring(L, "cooking");
430 					break;
431 				case CRAFT_METHOD_FUEL:
432 					lua_pushstring(L, "fuel");
433 					break;
434 				default:
435 					lua_pushstring(L, "unknown");
436 			}
437 			lua_setfield(L, -2, "type");
438 			lua_pushstring(L, &tmpout.item[0]);
439 			lua_setfield(L, -2, "output");
440 			lua_rawseti(L, -2, ++i);
441 		}
442 	}
443 	return 1;
444 }
445 
Initialize(lua_State * L,int top)446 void ModApiCraft::Initialize(lua_State *L, int top)
447 {
448 	API_FCT(get_all_craft_recipes);
449 	API_FCT(get_craft_recipe);
450 	API_FCT(get_craft_result);
451 	API_FCT(register_craft);
452 }
453