9 local recipes_cache
= {}
10 local usages_cache
= {}
13 local progressive_mode
= M
.settings
:get_bool("mcl_craftguide_progressive_mode", true)
14 local sfinv_only
= false
16 local colorize
= M
.colorize
17 local reg_items
= M
.registered_items
18 local get_result
= M
.get_craft_result
19 local show_formspec
= M
.show_formspec
20 local get_player_by_name
= M
.get_player_by_name
21 local serialize
, deserialize
= M
.serialize
, M
.deserialize
23 local ESC
= M
.formspec_escape
24 local S
= M
.get_translator("mcl_craftguide")
26 local maxn
, sort, concat
, insert
, copy
=
27 table.maxn
, table.sort, table.concat
, table.insert
,
30 local fmt
, find
, gmatch
, match
, sub
, split
, lower
=
31 string.format, string.find
, string.gmatch
, string.match
,
32 string.sub
, string.split
, string.lower
34 local min, max, floor, ceil = math
.min, math
.max, math
.floor, math
.ceil
35 local pairs
, next, unpack
= pairs
, next, unpack
36 local vec_add
, vec_mul
= vector
.add
, vector
.multiply
38 local DEFAULT_SIZE
= 10
39 local MIN_LIMIT
, MAX_LIMIT
= 10, 12
40 DEFAULT_SIZE
= min(MAX_LIMIT
, max(MIN_LIMIT
, DEFAULT_SIZE
))
43 local POLL_FREQ
= 0.25
46 box
= "box[%f,%f;%f,%f;%s]",
47 label
= "label[%f,%f;%s]",
48 image
= "image[%f,%f;%f,%f;%s]",
49 button
= "button[%f,%f;%f,%f;%s;%s]",
50 tooltip
= "tooltip[%s;%s]",
51 item_image
= "item_image[%f,%f;%f,%f;%s]",
52 image_button
= "image_button[%f,%f;%f,%f;%s;%s;%s]",
53 item_image_button
= "item_image_button[%f,%f;%f,%f;%s;%s;%s]",
56 local group_stereotypes
= {
57 wood
= "mcl_core:wood",
58 stone
= "mcl_core:stone",
59 sand
= "mcl_core:sand",
60 wool
= "mcl_wool:white",
61 carpet
= "mcl_wool:white_carpet",
63 water_bucket
= "mcl_buckets:bucket_water",
64 flower
= "mcl_flowers:dandelion",
65 mushroom
= "mcl_mushrooms:mushroom_brown",
66 wood_slab
= "mcl_stairs:slab_wood",
67 wood_stairs
= "mcl_stairs:stairs_wood",
68 coal
= "mcl_core:coal_lump",
69 shulker_box
= "mcl_chests:violet_shulker_box",
70 quartz_block
= "mcl_nether:quartz_block",
71 banner
= "mcl_banners:banner_item_white",
72 mesecon_conductor_craftable
= "mesecons:wire_00000000_off",
73 purpur_block
= "mcl_end:purpur_block",
74 normal_sandstone
= "mcl_core:sandstone",
75 red_sandstone
= "mcl_core:redsandstone",
76 compass
= mcl_compass
.stereotype
,
77 clock = mcl_clock
.sterotype
,
81 shulker_box
= S("Any shulker box"),
83 wood
= S("Any wood planks"),
86 normal_sandstone
= S("Any normal sandstone"),
87 red_sandstone
= S("Any red sandstone"),
88 carpet
= S("Any carpet"),
90 water_bucket
= S("Any water bucket"),
91 flower
= S("Any flower"),
92 mushroom
= S("Any mushroom"),
93 wood_slab
= S("Any wooden slab"),
94 wood_stairs
= S("Any wooden stairs"),
96 quartz_block
= S("Any kind of quartz block"),
97 purpur_block
= S("Any kind of purpur block"),
98 stonebrick
= S("Any stone bricks"),
99 stick
= S("Any stick"),
110 local function table_merge(t
, t2
)
111 t
, t2
= t
or {}, t2
or {}
122 local function table_replace(t
, val
, new
)
123 for k
, v
in pairs(t
) do
130 local function table_diff(t
, t2
)
143 local diff
, c
= {}, 0
156 local custom_crafts
, craft_types
= {}, {}
158 function mcl_craftguide
.register_craft_type(name
, def
)
159 local func
= "mcl_craftguide.register_craft_guide(): "
160 assert(name
, func
.. "'name' field missing")
161 assert(def
.description
, func
.. "'description' field missing")
162 assert(def
.icon
, func
.. "'icon' field missing")
164 craft_types
[name
] = def
167 function mcl_craftguide
.register_craft(def
)
168 local func
= "mcl_craftguide.register_craft(): "
169 assert(def
.type, func
.. "'type' field missing")
170 assert(def
.width
, func
.. "'width' field missing")
171 assert(def
.output
, func
.. "'output' field missing")
172 assert(def
.items
, func
.. "'items' field missing")
174 custom_crafts
[#custom_crafts
+ 1] = def
177 local recipe_filters
= {}
179 function mcl_craftguide
.add_recipe_filter(name
, f
)
180 local func
= "mcl_craftguide.add_recipe_filter(): "
181 assert(name
, func
.. "filter name missing")
182 assert(f
and type(f
) == "function", func
.. "filter function missing")
184 recipe_filters
[name
] = f
187 function mcl_craftguide
.remove_recipe_filter(name
)
188 recipe_filters
[name
] = nil
191 function mcl_craftguide
.set_recipe_filter(name
, f
)
192 local func
= "mcl_craftguide.set_recipe_filter(): "
193 assert(name
, func
.. "filter name missing")
194 assert(f
and type(f
) == "function", func
.. "filter function missing")
196 recipe_filters
= {[name
] = f
}
199 function mcl_craftguide
.get_recipe_filters()
200 return recipe_filters
203 local function apply_recipe_filters(recipes
, player
)
204 for _
, filter
in pairs(recipe_filters
) do
205 recipes
= filter(recipes
, player
)
211 local search_filters
= {}
213 function mcl_craftguide
.add_search_filter(name
, f
)
214 local func
= "mcl_craftguide.add_search_filter(): "
215 assert(name
, func
.. "filter name missing")
216 assert(f
and type(f
) == "function", func
.. "filter function missing")
218 search_filters
[name
] = f
221 function mcl_craftguide
.remove_search_filter(name
)
222 search_filters
[name
] = nil
225 function mcl_craftguide
.get_search_filters()
226 return search_filters
229 local formspec_elements
= {}
231 function mcl_craftguide
.add_formspec_element(name
, def
)
232 local func
= "mcl_craftguide.add_formspec_element(): "
233 assert(def
.element
, func
.. "'element' field not defined")
234 assert(def
.type, func
.. "'type' field not defined")
235 assert(FMT
[def
.type], func
.. "'" .. def
.type .. "' type not supported by the API")
237 formspec_elements
[name
] = {
239 element
= def
.element
,
244 function mcl_craftguide
.remove_formspec_element(name
)
245 formspec_elements
[name
] = nil
248 function mcl_craftguide
.get_formspec_elements()
249 return formspec_elements
252 local function item_has_groups(item_groups
, groups
)
253 for i
= 1, #groups
do
254 local group
= groups
[i
]
255 if not item_groups
[group
] then
263 local function extract_groups(str
)
264 return split(sub(str
, 7), ",")
267 local function item_in_recipe(item
, recipe
)
268 for _
, recipe_item
in pairs(recipe
.items
) do
269 if recipe_item
== item
then
275 local function groups_item_in_recipe(item
, recipe
)
276 local item_groups
= reg_items
[item
].groups
277 for _
, recipe_item
in pairs(recipe
.items
) do
278 if sub(recipe_item
, 1, 6) == "group:" then
279 local groups
= extract_groups(recipe_item
)
280 if item_has_groups(item_groups
, groups
) then
281 local usage
= copy(recipe
)
282 table_replace(usage
.items
, recipe_item
, item
)
289 local function get_item_usages(item
)
290 local usages
, c
= {}, 0
292 for _
, recipes
in pairs(recipes_cache
) do
293 for i
= 1, #recipes
do
294 local recipe
= recipes
[i
]
295 if item_in_recipe(item
, recipe
) then
299 recipe
= groups_item_in_recipe(item
, recipe
)
308 if fuel_cache
[item
] then
309 usages
[#usages
+ 1] = {type = "fuel", width
= 1, items
= {item
}}
315 local function get_filtered_items(player
)
316 local items
, c
= {}, 0
318 for i
= 1, #init_items
do
319 local item
= init_items
[i
]
320 local recipes
= recipes_cache
[item
]
321 local usages
= usages_cache
[item
]
323 if recipes
and #apply_recipe_filters(recipes
, player
) > 0 or
324 usages
and #apply_recipe_filters(usages
, player
) > 0 then
333 local function cache_recipes(output
)
334 local recipes
= M
.get_all_craft_recipes(output
) or {}
337 for i
= 1, #custom_crafts
do
338 local custom_craft
= custom_crafts
[i
]
339 if match(custom_craft
.output
, "%S*") == output
then
341 recipes
[c
] = custom_craft
346 recipes_cache
[output
] = recipes
351 local function get_recipes(item
, data
, player
)
352 local recipes
= recipes_cache
[item
]
353 local usages
= usages_cache
[item
]
356 recipes
= apply_recipe_filters(recipes
, player
)
359 local no_recipes
= not recipes
or #recipes
== 0
360 if no_recipes
and not usages
then
362 elseif usages
and no_recipes
then
363 data
.show_usages
= true
366 if data
.show_usages
then
367 recipes
= apply_recipe_filters(usages_cache
[item
], player
)
368 if #recipes
== 0 then
376 local function get_burntime(item
)
377 return get_result({method
= "fuel", width
= 1, items
= {item
}}).time
380 local function cache_fuel(item
)
381 local burntime
= get_burntime(item
)
383 fuel_cache
[item
] = burntime
388 local function groups_to_item(groups
)
390 local group
= groups
[1]
391 local def_gr
= "mcl_core:" .. group
393 if group_stereotypes
[group
] then
394 return group_stereotypes
[group
]
395 elseif reg_items
[def_gr
] then
400 for name
, def
in pairs(reg_items
) do
401 if item_has_groups(def
.groups
, groups
) then
409 local function get_tooltip(item
, groups
, cooktime
, burntime
)
413 local gcol
= "#FFAAFF"
415 local g
= group_names
[groups
[1]]
417 -- Treat the groups “compass” and “clock” as fake groups
418 -- and just print the normal item name without special formatting
419 if groups
[1] == "compass" or groups
[1] == "clock" then
420 groupstr
= reg_items
[item
].description
421 elseif group_names
[groups
[1]]
then
422 -- Use the special group name string
423 groupstr
= minetest
.colorize(gcol
, group_names
[groups
[1]]
)
425 --[[ Fallback: Generic group explanation: This always
426 works, but the internally used group name (which
427 looks ugly) is exposed to the user. ]]
428 groupstr
= minetest
.colorize(gcol
, groups
[1])
429 groupstr
= S("Any item belonging to the @1 group", groupstr
)
434 local groupstr
, c
= {}, 0
435 for i
= 1, #groups
do
437 groupstr
[c
] = colorize(gcol
, groups
[i
])
440 groupstr
= concat(groupstr
, ", ")
441 tooltip
= S("Any item belonging to the groups: @1", groupstr
)
444 tooltip
= reg_items
[item
].description
447 if not groups
and cooktime
then
448 tooltip
= tooltip
.. "\n" ..
449 S("Cooking time: @1", colorize("yellow", cooktime
))
452 if not groups
and burntime
then
453 tooltip
= tooltip
.. "\n" ..
454 S("Burning time: @1", colorize("yellow", burntime
))
457 return fmt(FMT
.tooltip
, item
, ESC(tooltip
))
460 local function get_recipe_fs(data
, iY
)
462 local recipe
= data
.recipes
[data
.rnum
]
463 local width
= recipe
.width
464 local xoffset
= data
.iX
/ 2.15
465 local cooktime
, shapeless
467 if recipe
.type == "cooking" then
468 cooktime
, width
= width
, 1
469 elseif width
== 0 then
471 if #recipe
.items
<= 4 then
474 width
= min(3, #recipe
.items
)
478 local rows
= ceil(maxn(recipe
.items
) / width
)
479 local rightest
, btn_size
, s_btn_size
= 0, 1.1
481 local btn_lab
= data
.show_usages
and
482 ESC(S("Usage @1 of @2", data
.rnum
, #data
.recipes
)) or
483 ESC(S("Recipe @1 of @2", data
.rnum
, #data
.recipes
))
485 fs
[#fs
+ 1] = fmt(FMT
.button
,
486 sfinv_only
and 5.8 or data
.iX
- 2.6,
487 sfinv_only
and 7.9 or iY
+ 3.3,
493 if width
> GRID_LIMIT
or rows
> GRID_LIMIT
then
494 fs
[#fs
+ 1] = fmt(FMT
.label
,
497 ESC(S("Recipe is too big to be displayed (@1×@2)", width
, rows
)))
502 for i
, item
in pairs(recipe
.items
) do
503 local X
= ceil((i
- 1) % width
+ xoffset
- width
) -
504 (sfinv_only
and 0 or 0.2)
505 local Y
= ceil(i
/ width
+ (iY
+ 2) - min(2, rows
))
507 if width
> 3 or rows
> 3 then
508 btn_size
= width
> 3 and 3 / width
or 3 / rows
509 s_btn_size
= btn_size
510 X
= btn_size
* (i
% width
) + xoffset
- 2.65
511 Y
= btn_size
* floor((i
- 1) / width
) + (iY
+ 3) - min(2, rows
)
519 if sub(item
, 1, 6) == "group:" then
520 groups
= extract_groups(item
)
521 item
= groups_to_item(groups
)
525 if groups
and (#groups
>= 1 and groups
[1] ~= "compass" and groups
[1] ~= "clock") then
529 fs
[#fs
+ 1] = fmt(FMT
.item_image_button
,
531 Y
+ (sfinv_only
and 0.7 or 0.2),
538 local burntime
= fuel_cache
[item
]
540 if groups
or cooktime
or burntime
then
541 fs
[#fs
+ 1] = get_tooltip(item
, groups
, cooktime
, burntime
)
545 local custom_recipe
= craft_types
[recipe
.type]
547 if custom_recipe
or shapeless
or recipe
.type == "cooking" then
548 local icon
= custom_recipe
and custom_recipe
.icon
or
549 shapeless
and "shapeless" or "furnace"
551 if recipe
.type == "cooking" then
552 icon
= "default_furnace_front_active.png"
553 elseif not custom_recipe
then
554 icon
= fmt("craftguide_%s.png", icon
)
557 fs
[#fs
+ 1] = fmt(FMT
.image
,
559 sfinv_only
and 6.2 or iY
+ 1.7,
564 local tooltip
= custom_recipe
and custom_recipe
.description
or
565 shapeless
and S("Shapeless") or S("Cooking")
567 fs
[#fs
+ 1] = fmt("tooltip[%f,%f;%f,%f;%s]",
569 sfinv_only
and 6.2 or iY
+ 1.7,
575 local arrow_X
= rightest
+ (s_btn_size
or 1.1)
576 local output_X
= arrow_X
+ 0.9
578 fs
[#fs
+ 1] = fmt(FMT
.image
,
580 sfinv_only
and 6.85 or iY
+ 2.35,
583 "craftguide_arrow.png")
585 if recipe
.type == "fuel" then
586 fs
[#fs
+ 1] = fmt(FMT
.image
,
588 sfinv_only
and 6.68 or iY
+ 2.18,
591 "mcl_craftguide_fuel.png")
593 local output_name
= match(recipe
.output
, "%S+")
594 local burntime
= fuel_cache
[output_name
]
596 fs
[#fs
+ 1] = fmt(FMT
.item_image_button
,
598 sfinv_only
and 6.7 or iY
+ 2.2,
606 fs
[#fs
+ 1] = get_tooltip(output_name
, nil, nil, burntime
)
608 fs
[#fs
+ 1] = fmt(FMT
.image
,
610 sfinv_only
and 6.83 or iY
+ 2.33,
613 "craftguide_arrow.png")
615 fs
[#fs
+ 1] = fmt(FMT
.image
,
617 sfinv_only
and 6.68 or iY
+ 2.18,
620 "mcl_craftguide_fuel.png")
627 local function make_formspec(name
)
628 local data
= player_data
[name
]
629 local iY
= sfinv_only
and 4 or data
.iX
- 5
630 local ipp
= data
.iX
* iY
632 data
.pagemax
= max(1, ceil(#data
.items
/ ipp
))
636 if not sfinv_only
then
637 fs
[#fs
+ 1] = fmt("size[%f,%f;]", data
.iX
- 0.35, iY
+ 4)
639 fs
[#fs
+ 1] = "background9[1,1;1,1;mcl_base_textures_background9.png;true;7]"
641 fs
[#fs
+ 1] = fmt([[ tooltip[size_inc;%s]
642 tooltip[size_dec;%s] ]],
643 ESC(S("Increase window size")),
644 ESC(S("Decrease window size")))
647 image_button[%f,0.12;0.8,0.8;craftguide_zoomin_icon.png;size_inc;]
648 image_button[%f,0.12;0.8,0.8;craftguide_zoomout_icon.png;size_dec;] ]],
650 data
.iX
* 0.47 + 0.6)
654 image_button[2.4,0.12;0.8,0.8;craftguide_search_icon.png;search;]
655 image_button[3.05,0.12;0.8,0.8;craftguide_clear_icon.png;clear;]
656 field_close_on_enter[filter;false]
659 fs
[#fs
+ 1] = fmt([[ tooltip[search;%s]
665 ESC(S("Previous page")),
668 fs
[#fs
+ 1] = fmt("label[%f,%f;%s]",
669 sfinv_only
and 6.3 or data
.iX
- 2.2,
671 ESC(colorize("#383838", fmt("%s / %u", data
.pagenum
, data
.pagemax
))))
674 image_button[%f,0.12;0.8,0.8;craftguide_prev_icon.png;prev;]
675 image_button[%f,0.12;0.8,0.8;craftguide_next_icon.png;next;] ]],
676 sfinv_only
and 5.5 or data
.iX
- 3.1,
677 sfinv_only
and 7.3 or (data
.iX
- 1.2) - (data
.iX
>= 11 and 0.08 or 0))
679 fs
[#fs
+ 1] = fmt("field[0.3,0.32;2.5,1;filter;;%s]", ESC(data
.filter
))
681 if #data
.items
== 0 then
682 local no_item
= S("No item to show")
683 local pos
= (data
.iX
/ 2) - 1
685 if next(recipe_filters
) and #init_items
> 0 and data
.filter
== "" then
686 no_item
= S("Collect items to reveal more recipes")
690 fs
[#fs
+ 1] = fmt(FMT
.label
, pos
, 2, ESC(no_item
))
693 local first_item
= (data
.pagenum
- 1) * ipp
694 for i
= first_item
, first_item
+ ipp
- 1 do
695 local item
= data
.items
[i
+ 1]
700 local X
= i
% data
.iX
701 local Y
= (i
% ipp
- X
) / data
.iX
+ 1
703 fs
[#fs
+ 1] = fmt("item_image_button[%f,%f;%f,%f;%s;%s_inv;]",
704 X
- (sfinv_only
and 0 or (X
* 0.05)),
712 if data
.recipes
and #data
.recipes
> 0 then
713 fs
[#fs
+ 1] = get_recipe_fs(data
, iY
)
716 for elem_name
, def
in pairs(formspec_elements
) do
717 local element
= def
.element(data
)
719 if find(def
.type, "button") then
720 insert(element
, #element
, elem_name
)
723 fs
[#fs
+ 1] = fmt(FMT
[def
.type], unpack(element
))
730 local show_fs
= function(player
, name
)
732 sfinv
.set_player_inventory_formspec(player
)
734 show_formspec(name
, "mcl_craftguide", make_formspec(name
))
738 mcl_craftguide
.add_search_filter("groups", function(item
, groups
)
739 local itemdef
= reg_items
[item
]
740 local has_groups
= true
742 for i
= 1, #groups
do
743 local group
= groups
[i
]
744 if not itemdef
.groups
[group
] then
753 local function search(data
)
754 local filter
= data
.filter
756 if searches
[filter
] then
757 data
.items
= searches
[filter
]
761 local filtered_list
, c
= {}, 0
762 local extras
= "^(.-)%+([%w_]+)=([%w_,]+)"
763 local search_filter
= next(search_filters
) and match(filter
, extras
)
766 if search_filter
then
767 for filter_name
, values
in gmatch(filter
, sub(extras
, 6, -1)) do
768 if search_filters
[filter_name
] then
769 values
= split(values
, ",")
770 filters
[filter_name
] = values
775 for i
= 1, #data
.items_raw
do
776 local item
= data
.items_raw
[i
]
777 local def
= reg_items
[item
]
778 local desc
= lower(def
.description
)
779 local search_in
= item
.. desc
782 if search_filter
then
783 for filter_name
, values
in pairs(filters
) do
784 local func
= search_filters
[filter_name
]
785 to_add
= func(item
, values
) and (search_filter
== "" or
786 find(search_in
, search_filter
, 1, true))
789 to_add
= find(search_in
, filter
, 1, true)
794 filtered_list
[c
] = item
798 if not next(recipe_filters
) then
799 -- Cache the results only if searched 2 times
800 if searches
[filter
] == nil then
801 searches
[filter
] = false
803 searches
[filter
] = filtered_list
807 data
.items
= filtered_list
810 local function get_inv_items(player
)
811 local inv
= player
:get_inventory()
814 for i
= 1, #item_lists
do
815 local list
= inv
:get_list(item_lists
[i
])
816 table_merge(stacks
, list
)
819 local inv_items
, c
= {}, 0
821 for i
= 1, #stacks
do
822 local stack
= stacks
[i
]
823 if not stack
:is_empty() then
824 local name
= stack
:get_name()
825 if reg_items
[name
] then
835 local function init_data(name
)
836 player_data
[name
] = {
839 iX
= sfinv_only
and 8 or DEFAULT_SIZE
,
841 items_raw
= init_items
,
845 local function reset_data(data
)
849 data
.query_item
= nil
850 data
.show_usages
= nil
852 data
.items
= data
.items_raw
855 local function cache_usages()
856 for i
= 1, #init_items
do
857 local item
= init_items
[i
]
858 usages_cache
[item
] = get_item_usages(item
)
862 local function get_init_items()
864 for name
, def
in pairs(reg_items
) do
865 local is_fuel
= cache_fuel(name
)
866 if not (def
.groups
.not_in_craft_guide
== 1) and
867 def
.description
and def
.description
~= "" and
868 (cache_recipes(name
) or is_fuel
) then
878 local function on_receive_fields(player
, fields
)
879 local name
= player
:get_player_name()
880 local data
= player_data
[name
]
882 for elem_name
, def
in pairs(formspec_elements
) do
883 if fields
[elem_name
] and def
.action
then
884 return def
.action(player
, data
)
890 show_fs(player
, name
)
892 elseif fields
.alternate
then
893 if #data
.recipes
== 1 then
897 local num_next
= data
.rnum
+ 1
898 data
.rnum
= data
.recipes
[num_next
] and num_next
or 1
899 show_fs(player
, name
)
901 elseif (fields
.key_enter_field
== "filter" or fields
.search
) and
902 fields
.filter
~= "" then
903 local fltr
= lower(fields
.filter
)
904 if data
.filter
== fltr
then
911 show_fs(player
, name
)
913 elseif fields
.prev
or fields
.next then
914 if data
.pagemax
== 1 then
918 data
.pagenum
= data
.pagenum
- (fields
.prev
and 1 or -1)
920 if data
.pagenum
> data
.pagemax
then
922 elseif data
.pagenum
== 0 then
923 data
.pagenum
= data
.pagemax
926 show_fs(player
, name
)
928 elseif (fields
.size_inc
and data
.iX
< MAX_LIMIT
) or
929 (fields
.size_dec
and data
.iX
> MIN_LIMIT
) then
931 data
.iX
= data
.iX
+ (fields
.size_inc
and 1 or -1)
932 show_fs(player
, name
)
935 for field
in pairs(fields
) do
936 if find(field
, ":") then
944 elseif sub(item
, -4) == "_inv" then
945 item
= sub(item
, 1, -5)
948 if item
~= data
.query_item
then
949 data
.show_usages
= nil
951 data
.show_usages
= not data
.show_usages
954 local recipes
= get_recipes(item
, data
, player
)
959 data
.query_item
= item
960 data
.recipes
= recipes
963 show_fs(player
, name
)
967 M
.register_on_mods_loaded(get_init_items
)
969 -- TODO: Remove sfinv support
971 sfinv
.register_page("craftguide:craftguide", {
972 title
= "Craft Guide",
974 get
= function(self
, player
, context
)
975 local name
= player
:get_player_name()
976 local formspec
= make_formspec(name
)
978 return sfinv
.make_formspec(player
, context
, formspec
)
981 on_enter
= function(self
, player
, context
)
982 if next(recipe_filters
) then
983 local name
= player
:get_player_name()
984 local data
= player_data
[name
]
986 data
.items_raw
= get_filtered_items(player
)
991 on_player_receive_fields
= function(self
, player
, context
, fields
)
992 on_receive_fields(player
, fields
)
996 M
.register_on_player_receive_fields(function(player
, formname
, fields
)
997 if formname
== "mcl_craftguide" then
998 on_receive_fields(player
, fields
)
999 elseif fields
.__mcl_craftguide
then
1000 mcl_craftguide
.show(player
:get_player_name())
1004 local function on_use(user
)
1005 local name
= user
:get_player_name()
1007 if next(recipe_filters
) then
1008 local data
= player_data
[name
]
1009 data
.items_raw
= get_filtered_items(user
)
1013 show_formspec(name
, "mcl_craftguide", make_formspec(name
))
1018 if progressive_mode
then
1019 local function item_in_inv(item
, inv_items
)
1020 local inv_items_size
= #inv_items
1022 if sub(item
, 1, 6) == "group:" then
1023 local groups
= extract_groups(item
)
1024 for i
= 1, inv_items_size
do
1025 local inv_item
= reg_items
[inv_items
[i]]
1027 local item_groups
= inv_item
.groups
1028 if item_has_groups(item_groups
, groups
) then
1034 for i
= 1, inv_items_size
do
1035 if inv_items
[i
] == item
then
1042 local function recipe_in_inv(recipe
, inv_items
)
1043 for _
, item
in pairs(recipe
.items
) do
1044 if not item_in_inv(item
, inv_items
) then
1052 local function progressive_filter(recipes
, player
)
1053 local name
= player
:get_player_name()
1054 local data
= player_data
[name
]
1056 if #data
.inv_items
== 0 then
1060 local filtered
, c
= {}, 0
1061 for i
= 1, #recipes
do
1062 local recipe
= recipes
[i
]
1063 if recipe_in_inv(recipe
, data
.inv_items
) then
1065 filtered
[c
] = recipe
1072 -- Workaround. Need an engine call to detect when the contents
1073 -- of the player inventory changed, instead.
1074 local function poll_new_items()
1075 local players
= M
.get_connected_players()
1076 for i
= 1, #players
do
1077 local player
= players
[i
]
1078 local name
= player
:get_player_name()
1079 local data
= player_data
[name
]
1080 local inv_items
= get_inv_items(player
)
1081 local diff
= table_diff(inv_items
, data
.inv_items
)
1084 data
.inv_items
= table_merge(diff
, data
.inv_items
)
1088 M
.after(POLL_FREQ
, poll_new_items
)
1091 M
.register_on_mods_loaded(function()
1092 M
.after(1, poll_new_items
)
1095 mcl_craftguide
.add_recipe_filter("Default progressive filter", progressive_filter
)
1097 M
.register_on_joinplayer(function(player
)
1098 local name
= player
:get_player_name()
1100 local meta
= player
:get_meta()
1101 local name
= player
:get_player_name()
1102 local data
= player_data
[name
]
1104 data
.inv_items
= deserialize(meta
:get_string("inv_items")) or {}
1107 local function save_meta(player
)
1108 local meta
= player
:get_meta()
1109 local name
= player
:get_player_name()
1110 local data
= player_data
[name
]
1112 meta
:set_string("inv_items", serialize(data
.inv_items
))
1115 M
.register_on_leaveplayer(function(player
)
1117 local name
= player
:get_player_name()
1118 player_data
[name
] = nil
1121 M
.register_on_shutdown(function()
1122 local players
= M
.get_connected_players()
1123 for i
= 1, #players
do
1124 local player
= players
[i
]
1129 M
.register_on_joinplayer(function(player
)
1130 local name
= player
:get_player_name()
1134 M
.register_on_leaveplayer(function(player
)
1135 local name
= player
:get_player_name()
1136 player_data
[name
] = nil
1140 function mcl_craftguide
.show(name
)
1141 local player
= minetest
.get_player_by_name(name
)
1142 if next(recipe_filters
) then
1143 local data
= player_data
[name
]
1144 data
.items_raw
= get_filtered_items(player
)
1147 show_formspec(name
, "mcl_craftguide", make_formspec(name
))
1150 --[[ Custom recipes (>3x3) test code
1152 M.register_craftitem(":secretstuff:custom_recipe_test", {
1153 description = "Custom Recipe Test",
1159 for i = 1, 10 - x do
1161 for j = 1, 10 - x do
1162 cr[x][i][j] = "group:wood"
1167 output = "secretstuff:custom_recipe_test",