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]]
416 -- Treat the groups “compass” and “clock” as fake groups
417 -- and just print the normal item name without special formatting
418 if groups
[1] == "compass" or groups
[1] == "clock" then
419 groupstr
= reg_items
[item
].description
420 elseif group_names
[groups
[1]]
then
421 -- Use the special group name string
422 groupstr
= minetest
.colorize(gcol
, group_names
[groups
[1]]
)
424 --[[ Fallback: Generic group explanation: This always
425 works, but the internally used group name (which
426 looks ugly) is exposed to the user. ]]
427 groupstr
= minetest
.colorize(gcol
, groups
[1])
428 groupstr
= S("Any item belonging to the @1 group", groupstr
)
433 local groupstr
, c
= {}, 0
434 for i
= 1, #groups
do
436 groupstr
[c
] = colorize(gcol
, groups
[i
])
439 groupstr
= concat(groupstr
, ", ")
440 tooltip
= S("Any item belonging to the groups: @1", groupstr
)
443 tooltip
= reg_items
[item
].description
447 tooltip
= tooltip
.. "\n" ..
448 S("Cooking time: @1", colorize("yellow", cooktime
))
452 tooltip
= tooltip
.. "\n" ..
453 S("Burning time: @1", colorize("yellow", burntime
))
456 return fmt(FMT
.tooltip
, item
, ESC(tooltip
))
459 local function get_recipe_fs(data
, iY
)
461 local recipe
= data
.recipes
[data
.rnum
]
462 local width
= recipe
.width
463 local xoffset
= data
.iX
/ 2.15
464 local cooktime
, shapeless
466 if recipe
.type == "cooking" then
467 cooktime
, width
= width
, 1
468 elseif width
== 0 then
470 if #recipe
.items
<= 4 then
473 width
= min(3, #recipe
.items
)
477 local rows
= ceil(maxn(recipe
.items
) / width
)
478 local rightest
, btn_size
, s_btn_size
= 0, 1.1
480 local btn_lab
= data
.show_usages
and
481 ESC(S("Usage @1 of @2", data
.rnum
, #data
.recipes
)) or
482 ESC(S("Recipe @1 of @2", data
.rnum
, #data
.recipes
))
484 fs
[#fs
+ 1] = fmt(FMT
.button
,
485 sfinv_only
and 5.8 or data
.iX
- 2.6,
486 sfinv_only
and 7.9 or iY
+ 3.3,
492 if width
> GRID_LIMIT
or rows
> GRID_LIMIT
then
493 fs
[#fs
+ 1] = fmt(FMT
.label
,
496 ESC(S("Recipe is too big to be displayed (@1×@2)", width
, rows
)))
501 for i
, item
in pairs(recipe
.items
) do
502 local X
= ceil((i
- 1) % width
+ xoffset
- width
) -
503 (sfinv_only
and 0 or 0.2)
504 local Y
= ceil(i
/ width
+ (iY
+ 2) - min(2, rows
))
506 if width
> 3 or rows
> 3 then
507 btn_size
= width
> 3 and 3 / width
or 3 / rows
508 s_btn_size
= btn_size
509 X
= btn_size
* (i
% width
) + xoffset
- 2.65
510 Y
= btn_size
* floor((i
- 1) / width
) + (iY
+ 3) - min(2, rows
)
518 if sub(item
, 1, 6) == "group:" then
519 groups
= extract_groups(item
)
520 item
= groups_to_item(groups
)
524 if groups
and (#groups
>= 1 and groups
[1] ~= "compass" and groups
[1] ~= "clock") then
528 fs
[#fs
+ 1] = fmt(FMT
.item_image_button
,
530 Y
+ (sfinv_only
and 0.7 or 0.2),
537 local burntime
= fuel_cache
[item
]
539 if groups
or cooktime
or burntime
then
540 fs
[#fs
+ 1] = get_tooltip(item
, groups
, cooktime
, burntime
)
544 local custom_recipe
= craft_types
[recipe
.type]
546 if custom_recipe
or shapeless
or recipe
.type == "cooking" then
547 local icon
= custom_recipe
and custom_recipe
.icon
or
548 shapeless
and "shapeless" or "furnace"
550 if recipe
.type == "cooking" then
551 icon
= "default_furnace_front_active.png"
552 elseif not custom_recipe
then
553 icon
= fmt("craftguide_%s.png", icon
)
556 fs
[#fs
+ 1] = fmt(FMT
.image
,
558 sfinv_only
and 6.2 or iY
+ 1.7,
563 local tooltip
= custom_recipe
and custom_recipe
.description
or
564 shapeless
and S("Shapeless") or S("Cooking")
566 fs
[#fs
+ 1] = fmt("tooltip[%f,%f;%f,%f;%s]",
568 sfinv_only
and 6.2 or iY
+ 1.7,
574 local arrow_X
= rightest
+ (s_btn_size
or 1.1)
575 local output_X
= arrow_X
+ 0.9
577 fs
[#fs
+ 1] = fmt(FMT
.image
,
579 sfinv_only
and 6.85 or iY
+ 2.35,
582 "craftguide_arrow.png")
584 if recipe
.type == "fuel" then
585 fs
[#fs
+ 1] = fmt(FMT
.image
,
587 sfinv_only
and 6.68 or iY
+ 2.18,
590 "mcl_craftguide_fuel.png")
592 local output_name
= match(recipe
.output
, "%S+")
593 local burntime
= fuel_cache
[output_name
]
595 fs
[#fs
+ 1] = fmt(FMT
.item_image_button
,
597 sfinv_only
and 6.7 or iY
+ 2.2,
605 fs
[#fs
+ 1] = get_tooltip(output_name
, nil, nil, burntime
)
607 fs
[#fs
+ 1] = fmt(FMT
.image
,
609 sfinv_only
and 6.83 or iY
+ 2.33,
612 "craftguide_arrow.png")
614 fs
[#fs
+ 1] = fmt(FMT
.image
,
616 sfinv_only
and 6.68 or iY
+ 2.18,
619 "mcl_craftguide_fuel.png")
626 local function make_formspec(name
)
627 local data
= player_data
[name
]
628 local iY
= sfinv_only
and 4 or data
.iX
- 5
629 local ipp
= data
.iX
* iY
631 data
.pagemax
= max(1, ceil(#data
.items
/ ipp
))
635 if not sfinv_only
then
636 fs
[#fs
+ 1] = fmt("size[%f,%f;]", data
.iX
- 0.35, iY
+ 4)
639 background[1,1;1,1;craftguide_bg.png;true]
642 fs
[#fs
+ 1] = fmt([[ tooltip[size_inc;%s]
643 tooltip[size_dec;%s] ]],
644 ESC(S("Increase window size")),
645 ESC(S("Decrease window size")))
648 image_button[%f,0.12;0.8,0.8;craftguide_zoomin_icon.png;size_inc;]
649 image_button[%f,0.12;0.8,0.8;craftguide_zoomout_icon.png;size_dec;] ]],
651 data
.iX
* 0.47 + 0.6)
655 image_button[2.4,0.12;0.8,0.8;craftguide_search_icon.png;search;]
656 image_button[3.05,0.12;0.8,0.8;craftguide_clear_icon.png;clear;]
657 field_close_on_enter[filter;false]
660 fs
[#fs
+ 1] = fmt([[ tooltip[search;%s]
666 ESC(S("Previous page")),
669 fs
[#fs
+ 1] = fmt("label[%f,%f;%s / %u]",
670 sfinv_only
and 6.3 or data
.iX
- 2.2,
672 colorize("yellow", data
.pagenum
),
676 image_button[%f,0.12;0.8,0.8;craftguide_prev_icon.png;prev;]
677 image_button[%f,0.12;0.8,0.8;craftguide_next_icon.png;next;] ]],
678 sfinv_only
and 5.5 or data
.iX
- 3.1,
679 sfinv_only
and 7.3 or (data
.iX
- 1.2) - (data
.iX
>= 11 and 0.08 or 0))
681 fs
[#fs
+ 1] = fmt("field[0.3,0.32;2.5,1;filter;;%s]", ESC(data
.filter
))
683 if #data
.items
== 0 then
684 local no_item
= S("No item to show")
685 local pos
= (data
.iX
/ 2) - 1
687 if next(recipe_filters
) and #init_items
> 0 and data
.filter
== "" then
688 no_item
= S("Collect items to reveal more recipes")
692 fs
[#fs
+ 1] = fmt(FMT
.label
, pos
, 2, ESC(no_item
))
695 local first_item
= (data
.pagenum
- 1) * ipp
696 for i
= first_item
, first_item
+ ipp
- 1 do
697 local item
= data
.items
[i
+ 1]
702 local X
= i
% data
.iX
703 local Y
= (i
% ipp
- X
) / data
.iX
+ 1
705 fs
[#fs
+ 1] = fmt("item_image_button[%f,%f;%f,%f;%s;%s_inv;]",
706 X
- (sfinv_only
and 0 or (X
* 0.05)),
714 if data
.recipes
and #data
.recipes
> 0 then
715 fs
[#fs
+ 1] = get_recipe_fs(data
, iY
)
718 for elem_name
, def
in pairs(formspec_elements
) do
719 local element
= def
.element(data
)
721 if find(def
.type, "button") then
722 insert(element
, #element
, elem_name
)
725 fs
[#fs
+ 1] = fmt(FMT
[def
.type], unpack(element
))
732 local show_fs
= function(player
, name
)
734 sfinv
.set_player_inventory_formspec(player
)
736 show_formspec(name
, "mcl_craftguide", make_formspec(name
))
740 mcl_craftguide
.add_search_filter("groups", function(item
, groups
)
741 local itemdef
= reg_items
[item
]
742 local has_groups
= true
744 for i
= 1, #groups
do
745 local group
= groups
[i
]
746 if not itemdef
.groups
[group
] then
755 local function search(data
)
756 local filter
= data
.filter
758 if searches
[filter
] then
759 data
.items
= searches
[filter
]
763 local filtered_list
, c
= {}, 0
764 local extras
= "^(.-)%+([%w_]+)=([%w_,]+)"
765 local search_filter
= next(search_filters
) and match(filter
, extras
)
768 if search_filter
then
769 for filter_name
, values
in gmatch(filter
, sub(extras
, 6, -1)) do
770 if search_filters
[filter_name
] then
771 values
= split(values
, ",")
772 filters
[filter_name
] = values
777 for i
= 1, #data
.items_raw
do
778 local item
= data
.items_raw
[i
]
779 local def
= reg_items
[item
]
780 local desc
= lower(def
.description
)
781 local search_in
= item
.. desc
784 if search_filter
then
785 for filter_name
, values
in pairs(filters
) do
786 local func
= search_filters
[filter_name
]
787 to_add
= func(item
, values
) and (search_filter
== "" or
788 find(search_in
, search_filter
, 1, true))
791 to_add
= find(search_in
, filter
, 1, true)
796 filtered_list
[c
] = item
800 if not next(recipe_filters
) then
801 -- Cache the results only if searched 2 times
802 if searches
[filter
] == nil then
803 searches
[filter
] = false
805 searches
[filter
] = filtered_list
809 data
.items
= filtered_list
812 local function get_inv_items(player
)
813 local inv
= player
:get_inventory()
816 for i
= 1, #item_lists
do
817 local list
= inv
:get_list(item_lists
[i
])
818 table_merge(stacks
, list
)
821 local inv_items
, c
= {}, 0
823 for i
= 1, #stacks
do
824 local stack
= stacks
[i
]
825 if not stack
:is_empty() then
826 local name
= stack
:get_name()
827 if reg_items
[name
] then
837 local function init_data(name
)
838 player_data
[name
] = {
841 iX
= sfinv_only
and 8 or DEFAULT_SIZE
,
843 items_raw
= init_items
,
847 local function reset_data(data
)
851 data
.query_item
= nil
852 data
.show_usages
= nil
854 data
.items
= data
.items_raw
857 local function cache_usages()
858 for i
= 1, #init_items
do
859 local item
= init_items
[i
]
860 usages_cache
[item
] = get_item_usages(item
)
864 local function get_init_items()
866 for name
, def
in pairs(reg_items
) do
867 local is_fuel
= cache_fuel(name
)
868 if not (def
.groups
.not_in_craft_guide
== 1 or
869 def
.groups
.not_in_creative_inventory
== 1) and
870 def
.description
and def
.description
~= "" and
871 (cache_recipes(name
) or is_fuel
) then
881 local function on_receive_fields(player
, fields
)
882 local name
= player
:get_player_name()
883 local data
= player_data
[name
]
885 for elem_name
, def
in pairs(formspec_elements
) do
886 if fields
[elem_name
] and def
.action
then
887 return def
.action(player
, data
)
893 show_fs(player
, name
)
895 elseif fields
.alternate
then
896 if #data
.recipes
== 1 then
900 local num_next
= data
.rnum
+ 1
901 data
.rnum
= data
.recipes
[num_next
] and num_next
or 1
902 show_fs(player
, name
)
904 elseif (fields
.key_enter_field
== "filter" or fields
.search
) and
905 fields
.filter
~= "" then
906 local fltr
= lower(fields
.filter
)
907 if data
.filter
== fltr
then
914 show_fs(player
, name
)
916 elseif fields
.prev
or fields
.next then
917 if data
.pagemax
== 1 then
921 data
.pagenum
= data
.pagenum
- (fields
.prev
and 1 or -1)
923 if data
.pagenum
> data
.pagemax
then
925 elseif data
.pagenum
== 0 then
926 data
.pagenum
= data
.pagemax
929 show_fs(player
, name
)
931 elseif (fields
.size_inc
and data
.iX
< MAX_LIMIT
) or
932 (fields
.size_dec
and data
.iX
> MIN_LIMIT
) then
934 data
.iX
= data
.iX
+ (fields
.size_inc
and 1 or -1)
935 show_fs(player
, name
)
938 for field
in pairs(fields
) do
939 if find(field
, ":") then
947 elseif sub(item
, -4) == "_inv" then
948 item
= sub(item
, 1, -5)
951 if item
~= data
.query_item
then
952 data
.show_usages
= nil
954 data
.show_usages
= not data
.show_usages
957 local recipes
= get_recipes(item
, data
, player
)
962 data
.query_item
= item
963 data
.recipes
= recipes
966 show_fs(player
, name
)
970 M
.register_on_mods_loaded(get_init_items
)
972 -- TODO: Remove sfinv support
974 sfinv
.register_page("craftguide:craftguide", {
975 title
= "Craft Guide",
977 get
= function(self
, player
, context
)
978 local name
= player
:get_player_name()
979 local formspec
= make_formspec(name
)
981 return sfinv
.make_formspec(player
, context
, formspec
)
984 on_enter
= function(self
, player
, context
)
985 if next(recipe_filters
) then
986 local name
= player
:get_player_name()
987 local data
= player_data
[name
]
989 data
.items_raw
= get_filtered_items(player
)
994 on_player_receive_fields
= function(self
, player
, context
, fields
)
995 on_receive_fields(player
, fields
)
999 M
.register_on_player_receive_fields(function(player
, formname
, fields
)
1000 if formname
== "mcl_craftguide" then
1001 on_receive_fields(player
, fields
)
1002 elseif fields
.__mcl_craftguide
then
1003 mcl_craftguide
.show(player
:get_player_name())
1007 local function on_use(user
)
1008 local name
= user
:get_player_name()
1010 if next(recipe_filters
) then
1011 local data
= player_data
[name
]
1012 data
.items_raw
= get_filtered_items(user
)
1016 show_formspec(name
, "mcl_craftguide", make_formspec(name
))
1021 if progressive_mode
then
1022 local function item_in_inv(item
, inv_items
)
1023 local inv_items_size
= #inv_items
1025 if sub(item
, 1, 6) == "group:" then
1026 local groups
= extract_groups(item
)
1027 for i
= 1, inv_items_size
do
1028 local inv_item
= reg_items
[inv_items
[i]]
1030 local item_groups
= inv_item
.groups
1031 if item_has_groups(item_groups
, groups
) then
1037 for i
= 1, inv_items_size
do
1038 if inv_items
[i
] == item
then
1045 local function recipe_in_inv(recipe
, inv_items
)
1046 for _
, item
in pairs(recipe
.items
) do
1047 if not item_in_inv(item
, inv_items
) then
1055 local function progressive_filter(recipes
, player
)
1056 local name
= player
:get_player_name()
1057 local data
= player_data
[name
]
1059 if #data
.inv_items
== 0 then
1063 local filtered
, c
= {}, 0
1064 for i
= 1, #recipes
do
1065 local recipe
= recipes
[i
]
1066 if recipe_in_inv(recipe
, data
.inv_items
) then
1068 filtered
[c
] = recipe
1075 -- Workaround. Need an engine call to detect when the contents
1076 -- of the player inventory changed, instead.
1077 local function poll_new_items()
1078 local players
= M
.get_connected_players()
1079 for i
= 1, #players
do
1080 local player
= players
[i
]
1081 local name
= player
:get_player_name()
1082 local data
= player_data
[name
]
1083 local inv_items
= get_inv_items(player
)
1084 local diff
= table_diff(inv_items
, data
.inv_items
)
1087 data
.inv_items
= table_merge(diff
, data
.inv_items
)
1091 M
.after(POLL_FREQ
, poll_new_items
)
1096 mcl_craftguide
.add_recipe_filter("Default progressive filter", progressive_filter
)
1098 M
.register_on_joinplayer(function(player
)
1099 local name
= player
:get_player_name()
1101 local meta
= player
:get_meta()
1102 local name
= player
:get_player_name()
1103 local data
= player_data
[name
]
1105 data
.inv_items
= deserialize(meta
:get_string("inv_items")) or {}
1108 local function save_meta(player
)
1109 local meta
= player
:get_meta()
1110 local name
= player
:get_player_name()
1111 local data
= player_data
[name
]
1113 meta
:set_string("inv_items", serialize(data
.inv_items
))
1116 M
.register_on_leaveplayer(function(player
)
1118 local name
= player
:get_player_name()
1119 player_data
[name
] = nil
1122 M
.register_on_shutdown(function()
1123 local players
= M
.get_connected_players()
1124 for i
= 1, #players
do
1125 local player
= players
[i
]
1130 M
.register_on_joinplayer(function(player
)
1131 local name
= player
:get_player_name()
1135 M
.register_on_leaveplayer(function(player
)
1136 local name
= player
:get_player_name()
1137 player_data
[name
] = nil
1141 function mcl_craftguide
.show(name
)
1142 local player
= minetest
.get_player_by_name(name
)
1143 if next(recipe_filters
) then
1144 local data
= player_data
[name
]
1145 data
.items_raw
= get_filtered_items(player
)
1148 show_formspec(name
, "mcl_craftguide", make_formspec(name
))
1151 --[[ Custom recipes (>3x3) test code
1153 M.register_craftitem(":secretstuff:custom_recipe_test", {
1154 description = "Custom Recipe Test",
1160 for i = 1, 10 - x do
1162 for j = 1, 10 - x do
1163 cr[x][i][j] = "group:wood"
1168 output = "secretstuff:custom_recipe_test",