]> git.lizzy.rs Git - dragonfireclient.git/blob - src/script/lua_api/l_craft.cpp
Fix some issues with minetest.clear_craft (#8712)
[dragonfireclient.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->clearCraftsByOutput(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         std::vector<ItemStack> items;
355         items.reserve(recipe.size());
356         for (const auto &item : recipe)
357                 items.emplace_back(item, 1, 0, getServer(L)->idef());
358         CraftInput input(method, width, items);
359
360         if (!craftdef->clearCraftsByInput(input, getServer(L))) {
361                 warningstream << "No craft recipe matches input" << std::endl;
362                 lua_pushboolean(L, false);
363                 return 1;
364         }
365
366         lua_pushboolean(L, true);
367         return 1;
368 }
369
370 // get_craft_result(input)
371 int ModApiCraft::l_get_craft_result(lua_State *L)
372 {
373         NO_MAP_LOCK_REQUIRED;
374
375         int input_i = 1;
376         std::string method_s = getstringfield_default(L, input_i, "method", "normal");
377         enum CraftMethod method = (CraftMethod)getenumfield(L, input_i, "method",
378                                 es_CraftMethod, CRAFT_METHOD_NORMAL);
379         int width = 1;
380         lua_getfield(L, input_i, "width");
381         if(lua_isnumber(L, -1))
382                 width = luaL_checkinteger(L, -1);
383         lua_pop(L, 1);
384         lua_getfield(L, input_i, "items");
385         std::vector<ItemStack> items = read_items(L, -1,getServer(L));
386         lua_pop(L, 1); // items
387
388         IGameDef *gdef = getServer(L);
389         ICraftDefManager *cdef = gdef->cdef();
390         CraftInput input(method, width, items);
391         CraftOutput output;
392         std::vector<ItemStack> output_replacements;
393         bool got = cdef->getCraftResult(input, output, output_replacements, true, gdef);
394         lua_newtable(L); // output table
395         if (got) {
396                 ItemStack item;
397                 item.deSerialize(output.item, gdef->idef());
398                 LuaItemStack::create(L, item);
399                 lua_setfield(L, -2, "item");
400                 setintfield(L, -1, "time", output.time);
401                 push_items(L, output_replacements);
402                 lua_setfield(L, -2, "replacements");
403         } else {
404                 LuaItemStack::create(L, ItemStack());
405                 lua_setfield(L, -2, "item");
406                 setintfield(L, -1, "time", 0);
407                 lua_newtable(L);
408                 lua_setfield(L, -2, "replacements");
409         }
410         lua_newtable(L); // decremented input table
411         lua_pushstring(L, method_s.c_str());
412         lua_setfield(L, -2, "method");
413         lua_pushinteger(L, width);
414         lua_setfield(L, -2, "width");
415         push_items(L, input.items);
416         lua_setfield(L, -2, "items");
417         return 2;
418 }
419
420
421 static void push_craft_recipe(lua_State *L, IGameDef *gdef,
422                 const CraftDefinition *recipe,
423                 const CraftOutput &tmpout)
424 {
425         CraftInput input = recipe->getInput(tmpout, gdef);
426         CraftOutput output = recipe->getOutput(input, gdef);
427
428         lua_newtable(L); // items
429         std::vector<ItemStack>::const_iterator iter = input.items.begin();
430         for (u16 j = 1; iter != input.items.end(); ++iter, j++) {
431                 if (iter->empty())
432                         continue;
433                 lua_pushstring(L, iter->name.c_str());
434                 lua_rawseti(L, -2, j);
435         }
436         lua_setfield(L, -2, "items");
437         setintfield(L, -1, "width", input.width);
438
439         std::string method_s;
440         switch (input.method) {
441         case CRAFT_METHOD_NORMAL:
442                 method_s = "normal";
443                 break;
444         case CRAFT_METHOD_COOKING:
445                 method_s = "cooking";
446                 break;
447         case CRAFT_METHOD_FUEL:
448                 method_s = "fuel";
449                 break;
450         default:
451                 method_s = "unknown";
452         }
453         lua_pushstring(L, method_s.c_str());
454         lua_setfield(L, -2, "method");
455
456         // Deprecated, only for compatibility's sake
457         lua_pushstring(L, method_s.c_str());
458         lua_setfield(L, -2, "type");
459
460         lua_pushstring(L, output.item.c_str());
461         lua_setfield(L, -2, "output");
462 }
463
464 static void push_craft_recipes(lua_State *L, IGameDef *gdef,
465                 const std::vector<CraftDefinition*> &recipes,
466                 const CraftOutput &output)
467 {
468         lua_createtable(L, recipes.size(), 0);
469
470         if (recipes.empty()) {
471                 lua_pushnil(L);
472                 return;
473         }
474
475         std::vector<CraftDefinition*>::const_iterator it = recipes.begin();
476         for (unsigned i = 0; it != recipes.end(); ++it) {
477                 lua_newtable(L);
478                 push_craft_recipe(L, gdef, *it, output);
479                 lua_rawseti(L, -2, ++i);
480         }
481 }
482
483
484 // get_craft_recipe(result item)
485 int ModApiCraft::l_get_craft_recipe(lua_State *L)
486 {
487         NO_MAP_LOCK_REQUIRED;
488
489         std::string item = luaL_checkstring(L, 1);
490         Server *server = getServer(L);
491         CraftOutput output(item, 0);
492         std::vector<CraftDefinition*> recipes = server->cdef()
493                         ->getCraftRecipes(output, server, 1);
494
495         lua_createtable(L, 1, 0);
496
497         if (recipes.empty()) {
498                 lua_pushnil(L);
499                 lua_setfield(L, -2, "items");
500                 setintfield(L, -1, "width", 0);
501                 return 1;
502         }
503         push_craft_recipe(L, server, recipes[0], output);
504         return 1;
505 }
506
507 // get_all_craft_recipes(result item)
508 int ModApiCraft::l_get_all_craft_recipes(lua_State *L)
509 {
510         NO_MAP_LOCK_REQUIRED;
511
512         std::string item = luaL_checkstring(L, 1);
513         Server *server = getServer(L);
514         CraftOutput output(item, 0);
515         std::vector<CraftDefinition*> recipes = server->cdef()
516                         ->getCraftRecipes(output, server);
517
518         push_craft_recipes(L, server, recipes, output);
519         return 1;
520 }
521
522 void ModApiCraft::Initialize(lua_State *L, int top)
523 {
524         API_FCT(get_all_craft_recipes);
525         API_FCT(get_craft_recipe);
526         API_FCT(get_craft_result);
527         API_FCT(register_craft);
528         API_FCT(clear_craft);
529 }