Repository navigation
Expand file tree
/
Copy pathlegacy.lua
More file actions
378 lines (352 loc) · 14.1 KB
/
Copy pathlegacy.lua
File metadata and controls
378 lines (352 loc) · 14.1 KB
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
local log_removals = minetest.settings:get_bool("simplecrafting_lib_log_invalid_recipe_removal")
local register_heat = function()
if minetest.registered_craftitems["simplecrafting_lib:heat"] == nil then
minetest.register_craftitem(":simplecrafting_lib:heat", {
description = "Heat",
groups = {simplecrafting_lib_intermediate=1, not_in_creative_inventory=1,},
inventory_image = "simplecrafting_lib_heat.png",
stack_max = 9999,
})
end
end
local function create_recipe(legacy)
local items = legacy.items
local has_items = false
for _, item in pairs(items) do
has_items = true
break
end
if not has_items then return end
local recipe = {}
recipe.output = ItemStack(legacy.output)
recipe.input = {}
recipe.returns = legacy.returns
for _, item in pairs(items) do
if item ~= "" then
recipe.input[item] = (recipe.input[item] or 0) + 1
end
end
return recipe
end
-- It's possible to have a recipe with a replacements pair that gives back more than what's being replaced,
-- eg in cottages the straw mat recipe replaces 1 default:stone with 3 farming:seed_wheats.
-- This parses out that possibility
local function get_item_and_quantity(item_string)
local string_split_list = {}
for v in string.gmatch(item_string, "%S+") do
table.insert(string_split_list, v)
end
if #string_split_list == 1 then
return item_string, 1 -- no number provided
else
return string_split_list[1], tonumber(string_split_list[#string_split_list])
end
end
local function get_replacement_group_haver(item_name, item_counts, recipe)
if item_counts[item_name] == nil then
-- If the item to be replaced is a group identifier, and there's an item that matches it in the recipe, substitute that.
if string.sub(item_name, 1, 6) == "group:" then
local groupname = string.sub(item_name, 7)
for k, v in pairs(item_counts) do
if minetest.get_item_group(k, groupname) > 0 then
return k
end
end
return false, "[simplecrafting_lib] recipe has a group replacement " .. item_name
.. " but an input belonging to that group can't be found."
end
-- If the item to be replaced doesn't exist in the recipe but there are groups in the recipe, see if the item has one of those groups
for input, _ in pairs(item_counts) do
if string.sub(input, 1, 6) == "group:" and
minetest.get_item_group(item_name, string.sub(input, 7)) > 0 then
return input
end
end
return false, "[simplecrafting_lib] recipe has a replacement target " .. item_name ..
" but an input matching it can't be found."
end
return item_name
end
local function process_replacements(recipe, count, legacy_returns)
for _,pair in pairs(recipe.replacements) do
local item_name, item_quantity = get_item_and_quantity(pair[2])
local replace_input, err = get_replacement_group_haver(pair[1], count, recipe)
if replace_input == false then
return false, err
end
local input_count = count[replace_input]
if input_count == nil then
return false, "Replacement input not found: " .. replace_input
end
legacy_returns[item_name] = input_count * item_quantity
end
return true
end
local function process_shaped_recipe(recipe)
local legacy = {items={},output=recipe.output}
local count = {}
for _,row in pairs(recipe.recipe) do
for _, item in pairs(row) do
legacy.items[#legacy.items+1] = item
count[item] = (count[item] or 0) + 1
end
end
if recipe.replacements then
legacy.returns = {}
local success, err = process_replacements(recipe, count, legacy.returns)
if success == false then
return false, err
end
end
return create_recipe(legacy)
end
local function process_shapeless_recipe(recipe)
local legacy = {items={},output=recipe.output}
if recipe.replacements then
legacy.returns = {}
local count = {}
for _, item in pairs(recipe.recipe) do
count[item] = (count[item] or 0) + 1
end
local success, err = process_replacements(recipe, count, legacy.returns)
if success == false then
return false, err
end
end
legacy.items = recipe.recipe
return create_recipe(legacy)
end
local function process_cooking_recipe(recipe)
local legacy = {input={}}
legacy.output = recipe.output
legacy.input[recipe.recipe] = 1
legacy.input["simplecrafting_lib:heat"] = recipe.cooktime or 3
if recipe.replacements then
legacy.returns = {}
local count = {}
count[recipe.recipe] = 1
local success, err = process_replacements(recipe, count, legacy.returns)
if success == false then
return false, err
end
end
return legacy
end
local function process_fuel_recipe(recipe)
local legacy = {input={}}
legacy.input[recipe.recipe] = 1
legacy.output = ItemStack({name="simplecrafting_lib:heat", count=recipe.burntime})
if recipe.replacements then
legacy.returns = {}
for _,pair in pairs(recipe.replacements) do
local item_name, item_quantity = get_item_and_quantity(pair[2])
legacy.returns[item_name] = item_quantity
end
end
return legacy
end
local already_cleared_processed = {} -- contains recipes suitable for re-registering
-- once we're done initializing, throw these tables away. They're not needed after that.
minetest.after(10, function()
already_cleared_processed = nil
end)
-- This is necessary because the format of recipes returned by
-- get_all_crafts is completely different from the format required by clear_craft.
-- https://github.com/minetest/minetest/issues/5962
-- https://github.com/minetest/minetest/issues/5790
-- https://github.com/minetest/minetest/issues/7429
local function safe_clear_craft(recipe_to_clear)
local parameter_recipe = {}
if recipe_to_clear.method == nil or recipe_to_clear.method == "normal" then
if recipe_to_clear.width == 0 then
parameter_recipe.type="shapeless"
parameter_recipe.recipe = recipe_to_clear.items
elseif recipe_to_clear.width == 1 then
parameter_recipe.width = 1
parameter_recipe.recipe = {
{recipe_to_clear.items[1] or ""},
{recipe_to_clear.items[2] or ""},
{recipe_to_clear.items[3] or ""},
}
elseif recipe_to_clear.width == 2 then
parameter_recipe.width = 2
parameter_recipe.recipe = {
{recipe_to_clear.items[1] or "", recipe_to_clear.items[2] or ""},
{recipe_to_clear.items[3] or "", recipe_to_clear.items[4] or ""},
{recipe_to_clear.items[5] or "", recipe_to_clear.items[6] or ""},
}
elseif recipe_to_clear.width == 3 then
parameter_recipe.width = 3
parameter_recipe.recipe = {
{recipe_to_clear.items[1] or "", recipe_to_clear.items[2] or "", recipe_to_clear.items[3] or ""},
{recipe_to_clear.items[4] or "", recipe_to_clear.items[5] or "", recipe_to_clear.items[6] or ""},
{recipe_to_clear.items[7] or "", recipe_to_clear.items[8] or "", recipe_to_clear.items[9] or ""},
}
end
elseif recipe_to_clear.type == "cooking" then
parameter_recipe.type = "cooking"
parameter_recipe.recipe = recipe_to_clear.items[1]
else
minetest.log("error", "[simplecrafting_lib] safe_clear_craft was unable to parse recipe "..dump(recipe_to_clear))
return false
end
-- https://github.com/minetest/minetest/issues/6513
local success, err = pcall(function()
if not minetest.clear_craft(parameter_recipe) then
minetest.log("warning", "[simplecrafting_lib] failed to clear recipe " .. dump(recipe_to_clear) .. "\nas parameter\n" .. dump(parameter_recipe))
end
end)
if not success and err ~= "No crafting specified for input" then
minetest.log("error", "[simplecrafting_lib] minetest.clear_craft failed with error \"" ..err.. "\" while attempting to clear craft " ..dump(recipe_to_clear))
return false
elseif success == true and err == false then
minetest.log("warning", "[simplecrafting_lib] minetest.clear_craft wasn't able to find inputs for " .. dump(recipe_to_clear))
return false
end
return true
end
local function register_legacy_recipe(legacy_recipe)
local clear_recipe = false
for _, filter in ipairs(simplecrafting_lib.import_filters) do
local working_recipe = simplecrafting_lib.deep_copy(legacy_recipe)
local craft_type, clear_this = filter(working_recipe)
if craft_type then
if (working_recipe.input["simplecrafting_lib:heat"]) or
(working_recipe.output and ItemStack(working_recipe.output):get_name() == "simplecrafting_lib:heat") then
register_heat()
end
simplecrafting_lib.register(craft_type, working_recipe)
end
clear_recipe = clear_this or clear_recipe
end
return clear_recipe
end
-- import_legacy_recipes overrides minetest.register_craft so that subsequently registered
-- crafting recipes will be put into this system. If you wish to register a craft
-- the old way without it being put into this system, use this method.
simplecrafting_lib.minetest_register_craft = minetest.register_craft
local function log_failure(err, recipe)
if log_removals then
if err ~= nil then
minetest.log("error", err .. "\n" .. dump(recipe))
else
minetest.log("error", "[simplecrafting_lib] Shapeless recipe could not be processed, likely due to nonexistent replacement items:\n" .. dump(recipe))
end
end
end
-- This replaces the core register_craft method so that any crafts
-- registered after this one will be added to the new system.
minetest.register_craft = function(recipe)
local clear = false
local new_recipe
if not recipe.type then
local err
new_recipe, err = process_shaped_recipe(recipe)
if new_recipe == false then
log_failure(err, recipe)
return
end
clear = register_legacy_recipe(new_recipe)
elseif recipe.type == "shapeless" then
local err
new_recipe, err = process_shapeless_recipe(recipe)
if new_recipe == false then
log_failure(err, recipe)
return
end
clear = register_legacy_recipe(new_recipe)
elseif recipe.type == "cooking" then
new_recipe = process_cooking_recipe(recipe)
clear = register_legacy_recipe(new_recipe)
elseif recipe.type == "fuel" then
new_recipe = process_fuel_recipe(recipe)
clear = register_legacy_recipe(new_recipe)
end
if not clear then
return simplecrafting_lib.minetest_register_craft(recipe)
else
table.insert(already_cleared_processed, new_recipe)
end
end
local function import_legacy_recipes()
-- if any recipes have been cleared by previous runs of import_legacy_recipes, let this run have the opportunity to look at them.
for _, recipe in pairs(already_cleared_processed) do
register_legacy_recipe(recipe)
end
-- This loop goes through all recipes that have already been registered and
-- converts them
for item,_ in pairs(minetest.registered_items) do
local crafts = minetest.get_all_craft_recipes(item)
if crafts and item ~= "" then
for _,legacy_recipe in pairs(crafts) do
if legacy_recipe.method == nil or legacy_recipe.method == "normal" then
-- get_all_craft_recipes output recipes omit replacements, need to find those experimentally
-- https://github.com/minetest/minetest/issues/4901
local output, decremented_input = minetest.get_craft_result(legacy_recipe)
-- until https://github.com/minetest/minetest_game/commit/ae7206c0064cbb5c0e5434c19893d4bf3fa2b388
-- the dye:red + dye:green -> dye:brown recipe was broken here - there were two
-- red+green recipes, one producing dark grey and one producing brown dye, and when one gets
-- cleared from the crafting system by safe_clear_craft the other goes too and this craft attempt
-- fails.
-- This brokenness manifests by returning their input items and no output, so check if an output
-- was actually made before counting the returns as actual returns.
-- This is not an ideal solution since it may result in recipes losing their replacements,
-- but at this point I'm solving edge cases for edge cases and I need to sleep.
if output.item:get_count() > 0 then
for _, returned_item in pairs(decremented_input.items) do
if returned_item:get_count() > 0 then
legacy_recipe.returns = legacy_recipe.returns or {}
legacy_recipe.returns[returned_item:get_name()] = (legacy_recipe.returns[returned_item:get_name()] or 0) + returned_item:get_count()
end
end
end
local new_recipe = create_recipe(legacy_recipe)
if register_legacy_recipe(new_recipe) then
if not safe_clear_craft(legacy_recipe) then
minetest.log("warning", "[simplecrafting_lib] minetest.clear_craft wasn't able to find inputs for " .. dump(legacy_recipe) .. " for output " .. item)
end
table.insert(already_cleared_processed, new_recipe)
end
elseif legacy_recipe.method == "cooking" then
local new_recipe = {input={}}
new_recipe.output = legacy_recipe.output
new_recipe.input[legacy_recipe.items[1]] = 1
local cooked = minetest.get_craft_result({method = "cooking", width = 1, items = {legacy_recipe.items[1]}})
new_recipe.input["simplecrafting_lib:heat"] = cooked.time
if register_legacy_recipe(new_recipe) then
if not safe_clear_craft(legacy_recipe) then
minetest.log("warning", "[simplecrafting_lib] minetest.clear_craft wasn't able to find inputs for " .. dump(legacy_recipe) .. " for cooking output " .. item)
end
table.insert(already_cleared_processed, new_recipe)
end
else
minetest.log("error", "[simplecrafting_lib] Unrecognized crafting method for legacy recipe " .. dump(legacy_recipe))
end
end
end
-- Fuel recipes aren't returned by get_all_craft_recipes, need to find those experimentally
-- https://github.com/minetest/minetest/issues/5745
local fuel, afterfuel = minetest.get_craft_result({method="fuel",width=1,items={item}})
if fuel.time ~= 0 then
local new_recipe = {}
new_recipe.input = {}
new_recipe.input[item] = 1
new_recipe.output = ItemStack({name="simplecrafting_lib:heat", count = fuel.time})
for _, afteritem in pairs(afterfuel.items) do
if afteritem:get_count() > 0 then
new_recipe.returns = new_recipe.returns or {}
new_recipe.returns[afteritem:get_name()] = (new_recipe.returns[afteritem:get_name()] or 0) + afteritem:get_count()
end
end
if register_legacy_recipe(new_recipe) then
minetest.clear_craft({type="fuel", recipe=item})
table.insert(already_cleared_processed, new_recipe)
end
end
end
end
simplecrafting_lib.import_filters = {}
simplecrafting_lib.register_recipe_import_filter = function(filter_function)
table.insert(simplecrafting_lib.import_filters, filter_function)
import_legacy_recipes()
end