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