Compare commits

...

21 Commits
1.5 ... 1.6

Author SHA1 Message Date
d324c5f1e5 Progressive mode: Add a Steam-like HUD success + remove useless API functions 2019-08-30 17:05:03 +02:00
dfa45789e2 Style cleaning 2019-08-29 15:41:32 +02:00
80a0d67f15 Ability to register recipes in MC-like way 2019-08-29 14:56:53 +02:00
22c5c9444e Interpret group value 0 as "not in group".
This fixes instances where items are counted as part of a group by
craftguide but not by the engine.
2019-08-29 14:47:19 +02:00
97676d094e Fix possible crash 2019-06-30 19:42:32 +02:00
ca18ae0e3a Fix nil item crash in get_tooltip() - Fixes #84, regression caused by d950c71db (@pauloue) 2019-06-30 16:37:09 +02:00
e710fcd483 Small optimization 2019-06-23 01:42:12 +02:00
a2e4f20791 Items with no recipes but with usages are shown 2019-06-23 01:25:18 +02:00
22a85f50c1 Sugar 2019-05-08 20:57:45 +02:00
0271f61fc2 Change tooltip format 2019-03-21 17:44:32 +01:00
84756af3a1 Opens the stereotypes to the API 2019-03-21 17:34:20 +01:00
9dc656d5a2 Fix broken assert() 2019-03-21 00:21:11 +01:00
4c0371c5cc Progressive mode: Fix crash on player leaving 2019-03-14 14:11:44 +01:00
b6181ebd7a Minor cleanup 2019-03-13 16:56:35 +01:00
eb7292da7a 2×2 for shapeless recipe w/ <= 4 items 2019-03-13 15:22:45 +01:00
9df355b899 Add more assert checks 2019-03-12 16:54:26 +01:00
a242f6c61c Revert some changes 2019-03-11 03:01:37 +01:00
c9ebd5c069 Cleanup 2019-03-10 16:27:36 +01:00
054a7ab3af Update russian translations 2019-03-07 13:47:41 +01:00
b1a67eb632 Add examples in doc 2019-03-07 13:43:47 +01:00
e0e57b45ea Quick fix 2019-03-04 01:19:13 +01:00
4 changed files with 407 additions and 300 deletions

View File

@ -2,6 +2,7 @@ unused_args = false
allow_defined_top = true allow_defined_top = true
read_globals = { read_globals = {
"core",
"minetest", "minetest",
"default", "default",
"sfinv", "sfinv",

81
API.md
View File

@ -11,17 +11,35 @@ craftguide.register_craft_type("digging", {
}) })
``` ```
#### Registering a custom crafting recipe (example) #### Registering a custom crafting recipe (examples)
```Lua ```Lua
craftguide.register_craft({ craftguide.register_craft({
type = "digging", type = "digging",
width = 1, width = 1,
output = "default:cobble 2", result = "default:cobble 2",
items = {"default:stone"}, items = {"default:stone"},
}) })
``` ```
Recipes can also be registered in a Minecraft-like way:
```Lua
craftguide.register_craft({
grid = {
"X #",
" ## ",
"X#X#",
"X X",
},
key = {
['#'] = "default:wood",
['X'] = "default:glass",
},
result = "default:mese 3",
})
```
--- ---
### Recipe filters ### Recipe filters
@ -70,10 +88,15 @@ Returns a map of recipe filters, indexed by name.
Search filters are used to perform specific searches inside the search field. Search filters are used to perform specific searches inside the search field.
They can be used like so: `<optional name>+<filter name>=<value1>,<value2>,<...>` They can be used like so: `<optional name>+<filter name>=<value1>,<value2>,<...>`
Examples:
- `+groups=cracky,crumbly`: search for groups `cracky` and `crumbly` in all items.
- `sand+groups=falling_node`: search for group `falling_node` for items which contain `sand` in their names.
Notes: Notes:
- If `optional name` is omitted, the search filter will apply to all items, without pre-filtering. - If `optional name` is omitted, the search filter will apply to all items, without pre-filtering.
- Filters can be combined. - Filters can be combined.
- The `+groups=` filter is currently implemented by default. - The `groups` filter is currently implemented by default.
#### `craftguide.add_search_filter(name, function(item, values))` #### `craftguide.add_search_filter(name, function(item, values))`
@ -114,49 +137,6 @@ Returns a map of search filters, indexed by name.
--- ---
### Custom formspec elements
#### `craftguide.add_formspec_element(name, def)`
Adds a formspec element to the current formspec.
Supported types: `box`, `label`, `image`, `button`, `tooltip`, `item_image`, `image_button`, `item_image_button`
Example:
```lua
craftguide.add_formspec_element("export", {
type = "button",
element = function(data)
-- Should return a table of parameters according to the formspec element type.
-- Note: for all buttons, the 'name' parameter *must not* be specified!
if data.recipes then
return {
data.iX - 3.7, -- X
sfinv_only and 7.9 or 8, -- Y
1.6, -- W
1, -- H
ESC(S("Export")) -- label
}
end
end,
-- Optional.
action = function(player, data)
-- When the button is pressed.
print("Exported!")
end
})
```
#### `craftguide.remove_formspec_element(name)`
Removes the formspec element with the given name.
#### `craftguide.get_formspec_elements()`
Returns a map of formspec elements, indexed by name.
---
### Miscellaneous ### Miscellaneous
#### `craftguide.show(player_name, item, show_usages)` #### `craftguide.show(player_name, item, show_usages)`
@ -166,3 +146,12 @@ Opens the Crafting Guide with the current filter applied.
* `player_name`: string param. * `player_name`: string param.
* `item`: optional, string param. If set, this item is pre-selected. If the item does not exist or has no recipe, use the player's previous selection. By default, player's previous selection is used * `item`: optional, string param. If set, this item is pre-selected. If the item does not exist or has no recipe, use the player's previous selection. By default, player's previous selection is used
* `show_usages`: optional, boolean param. If true, show item usages. * `show_usages`: optional, boolean param. If true, show item usages.
#### `craftguide.group_stereotypes`
This is the table indexing the item groups by stereotypes.
You can add a stereotype like so:
```Lua
craftguide.group_stereotypes.radioactive = "mod:item"
```

569
init.lua
View File

@ -1,7 +1,6 @@
craftguide = {} craftguide = {}
local M = minetest local pdata = {}
local player_data = {}
-- Caches -- Caches
local init_items = {} local init_items = {}
@ -10,29 +9,31 @@ local recipes_cache = {}
local usages_cache = {} local usages_cache = {}
local fuel_cache = {} local fuel_cache = {}
local progressive_mode = M.settings:get_bool("craftguide_progressive_mode") local progressive_mode = core.settings:get_bool("craftguide_progressive_mode")
local sfinv_only = M.settings:get_bool("craftguide_sfinv_only") and rawget(_G, "sfinv") local sfinv_only = core.settings:get_bool("craftguide_sfinv_only") and rawget(_G, "sfinv")
local colorize = M.colorize local after = core.after
local reg_items = M.registered_items local colorize = core.colorize
local get_result = M.get_craft_result local reg_items = core.registered_items
local show_formspec = M.show_formspec local get_result = core.get_craft_result
local get_player_by_name = M.get_player_by_name local get_players = core.get_connected_players
local serialize, deserialize = M.serialize, M.deserialize local show_formspec = core.show_formspec
local get_all_recipes = core.get_all_craft_recipes
local get_player_by_name = core.get_player_by_name
local serialize, deserialize = core.serialize, core.deserialize
local ESC = M.formspec_escape local ESC = core.formspec_escape
local S = M.get_translator("craftguide") local S = core.get_translator("craftguide")
local maxn, sort, concat, insert, copy = local maxn, sort, concat, copy =
table.maxn, table.sort, table.concat, table.insert, table.maxn, table.sort, table.concat, table.copy
table.copy
local fmt, find, gmatch, match, sub, split, lower = local fmt, find, gmatch, match, sub, split, upper, lower =
string.format, string.find, string.gmatch, string.match, string.format, string.find, string.gmatch, string.match,
string.sub, string.split, string.lower string.sub, string.split, string.upper, string.lower
local min, max, floor, ceil = math.min, math.max, math.floor, math.ceil local min, max, floor, ceil = math.min, math.max, math.floor, math.ceil
local pairs, next, unpack = pairs, next, unpack local pairs, next = pairs, next
local vec_add, vec_mul = vector.add, vector.multiply local vec_add, vec_mul = vector.add, vector.multiply
local DEFAULT_SIZE = 10 local DEFAULT_SIZE = 10
@ -40,20 +41,19 @@ local MIN_LIMIT, MAX_LIMIT = 10, 12
DEFAULT_SIZE = min(MAX_LIMIT, max(MIN_LIMIT, DEFAULT_SIZE)) DEFAULT_SIZE = min(MAX_LIMIT, max(MIN_LIMIT, DEFAULT_SIZE))
local GRID_LIMIT = 5 local GRID_LIMIT = 5
local POLL_FREQ = 0.25
local FMT = { local FMT = {
box = "box[%f,%f;%f,%f;%s]", box = "box[%f,%f;%f,%f;%s]",
label = "label[%f,%f;%s]", label = "label[%f,%f;%s]",
image = "image[%f,%f;%f,%f;%s]", image = "image[%f,%f;%f,%f;%s]",
button = "button[%f,%f;%f,%f;%s;%s]", button = "button[%f,%f;%f,%f;%s;%s]",
tooltip = "tooltip[%s;%s]", tooltip = "tooltip[%f,%f;%f,%f;%s]",
item_image = "item_image[%f,%f;%f,%f;%s]", item_image = "item_image[%f,%f;%f,%f;%s]",
image_button = "image_button[%f,%f;%f,%f;%s;%s;%s]", image_button = "image_button[%f,%f;%f,%f;%s;%s;%s]",
item_image_button = "item_image_button[%f,%f;%f,%f;%s;%s;%s]", item_image_button = "item_image_button[%f,%f;%f,%f;%s;%s;%s]",
} }
local group_stereotypes = { craftguide.group_stereotypes = {
wool = "wool:white", wool = "wool:white",
dye = "dye:white", dye = "dye:white",
water_bucket = "bucket:bucket_water", water_bucket = "bucket:bucket_water",
@ -119,23 +119,74 @@ local function __func()
return debug.getinfo(2, "n").name return debug.getinfo(2, "n").name
end end
local function is_str(x)
return type(x) == "string"
end
local function is_num(x)
return type(x) == "number"
end
local function is_table(x)
return type(x) == "table"
end
local function is_func(x)
return type(x) == "function"
end
local custom_crafts, craft_types = {}, {} local custom_crafts, craft_types = {}, {}
function craftguide.register_craft_type(name, def) function craftguide.register_craft_type(name, def)
local func = "craftguide." .. __func() .. "(): " local func = "craftguide." .. __func() .. "(): "
assert(name, func .. "'name' field missing") assert(is_str(name), func .. "'name' field missing")
assert(def.description, func .. "'description' field missing") assert(is_str(def.description), func .. "'description' field missing")
assert(def.icon, func .. "'icon' field missing") assert(is_str(def.icon), func .. "'icon' field missing")
craft_types[name] = def craft_types[name] = def
end end
function craftguide.register_craft(def) function craftguide.register_craft(def)
local func = "craftguide." .. __func() .. "(): " if def.result then
assert(def.type, func .. "'type' field missing") def.output = def.result -- Backward compatibility
assert(def.width, func .. "'width' field missing") end
assert(def.output, func .. "'output' field missing")
assert(def.items, func .. "'items' field missing") if not is_str(def.output) then
def.output = ""
end
if not is_table(def.items) then
def.items = {}
end
if not is_num(def.width) then
def.width = 0
end
if def.grid then
if not is_table(def.grid) then
def.grid = {}
end
local cp = copy(def.grid)
sort(cp, function(a, b)
return #a > #b
end)
def.width = #cp[1]
for i = 1, #def.grid do
while #def.grid[i] < def.width do
def.grid[i] = def.grid[i] .. " "
end
end
local c = 1
for symbol in gmatch(concat(def.grid), ".") do
def.items[c] = def.key[symbol]
c = c + 1
end
end
custom_crafts[#custom_crafts + 1] = def custom_crafts[#custom_crafts + 1] = def
end end
@ -144,8 +195,8 @@ local recipe_filters = {}
function craftguide.add_recipe_filter(name, f) function craftguide.add_recipe_filter(name, f)
local func = "craftguide." .. __func() .. "(): " local func = "craftguide." .. __func() .. "(): "
assert(name, func .. "filter name missing") assert(is_str(name), func .. "filter name missing")
assert(f and type(f) == "function", func .. "filter function missing") assert(is_func(f), func .. "filter function missing")
recipe_filters[name] = f recipe_filters[name] = f
end end
@ -156,8 +207,8 @@ end
function craftguide.set_recipe_filter(name, f) function craftguide.set_recipe_filter(name, f)
local func = "craftguide." .. __func() .. "(): " local func = "craftguide." .. __func() .. "(): "
assert(name, func .. "filter name missing") assert(is_str(name), func .. "filter name missing")
assert(f and type(f) == "function", func .. "filter function missing") assert(is_func(f), func .. "filter function missing")
recipe_filters = {[name] = f} recipe_filters = {[name] = f}
end end
@ -178,8 +229,8 @@ local search_filters = {}
function craftguide.add_search_filter(name, f) function craftguide.add_search_filter(name, f)
local func = "craftguide." .. __func() .. "(): " local func = "craftguide." .. __func() .. "(): "
assert(name, func .. "filter name missing") assert(is_str(name), func .. "filter name missing")
assert(f and type(f) == "function", func .. "filter function missing") assert(is_func(f), func .. "filter function missing")
search_filters[name] = f search_filters[name] = f
end end
@ -192,33 +243,10 @@ function craftguide.get_search_filters()
return search_filters return search_filters
end end
local formspec_elements = {}
function craftguide.add_formspec_element(name, def)
local func = "craftguide." .. __func() .. "(): "
assert(def.element, func .. "'element' field not defined")
assert(def.type, func .. "'type' field not defined")
assert(FMT[def.type], func .. "'" .. def.type .. "' type not supported by the API")
formspec_elements[name] = {
type = def.type,
element = def.element,
action = def.action,
}
end
function craftguide.remove_formspec_element(name)
formspec_elements[name] = nil
end
function craftguide.get_formspec_elements()
return formspec_elements
end
local function item_has_groups(item_groups, groups) local function item_has_groups(item_groups, groups)
for i = 1, #groups do for i = 1, #groups do
local group = groups[i] local group = groups[i]
if not item_groups[group] then if (item_groups[group] or 0) == 0 then
return return
end end
end end
@ -240,6 +268,7 @@ end
local function groups_item_in_recipe(item, recipe) local function groups_item_in_recipe(item, recipe)
local item_groups = reg_items[item].groups local item_groups = reg_items[item].groups
for _, recipe_item in pairs(recipe.items) do for _, recipe_item in pairs(recipe.items) do
if sub(recipe_item, 1, 6) == "group:" then if sub(recipe_item, 1, 6) == "group:" then
local groups = extract_groups(recipe_item) local groups = extract_groups(recipe_item)
@ -252,7 +281,7 @@ local function groups_item_in_recipe(item, recipe)
end end
end end
local function get_item_usages(item) local function get_usages(item)
local usages, c = {}, 0 local usages, c = {}, 0
for _, recipes in pairs(recipes_cache) do for _, recipes in pairs(recipes_cache) do
@ -278,39 +307,54 @@ local function get_item_usages(item)
return usages return usages
end end
local function get_filtered_items(player) local function get_filtered_items(player, data)
local items, c = {}, 0 local items, c = {}, 0
local known = 0
for i = 1, #init_items do for i = 1, #init_items do
local item = init_items[i] local item = init_items[i]
local recipes = recipes_cache[item] local recipes = recipes_cache[item]
local usages = usages_cache[item] local usages = usages_cache[item]
if recipes and #apply_recipe_filters(recipes, player) > 0 or recipes = #apply_recipe_filters(recipes or {}, player)
usages and #apply_recipe_filters(usages, player) > 0 then usages = #apply_recipe_filters(usages or {}, player)
if recipes > 0 or usages > 0 then
if not data then
c = c + 1 c = c + 1
items[c] = item items[c] = item
else
known = known + recipes + usages
end
end end
end end
if data then
data.known_recipes = known
else
return items return items
end end
end
local function cache_recipes(output) local function cache_recipes(output)
local recipes = M.get_all_craft_recipes(output) or {} local recipes = get_all_recipes(output) or {}
local c = 0
for i = 1, #custom_crafts do for i = 1, #custom_crafts do
local custom_craft = custom_crafts[i] local custom_craft = custom_crafts[i]
if match(custom_craft.output, "%S*") == output then if match(custom_craft.output, "%S*") == output then
c = c + 1 recipes[#recipes + 1] = custom_craft
recipes[c] = custom_craft
end end
end end
if #recipes > 0 then if #recipes > 0 then
recipes_cache[output] = recipes recipes_cache[output] = recipes
return true end
end
local function cache_usages(item)
local usages = get_usages(item)
if #usages > 0 then
usages_cache[item] = usages
end end
end end
@ -331,7 +375,7 @@ local function get_recipes(item, data, player)
if data.show_usages then if data.show_usages then
recipes = apply_recipe_filters(usages_cache[item], player) recipes = apply_recipe_filters(usages_cache[item], player)
if #recipes == 0 then if recipes and #recipes == 0 then
return return
end end
end end
@ -355,9 +399,11 @@ local function groups_to_item(groups)
if #groups == 1 then if #groups == 1 then
local group = groups[1] local group = groups[1]
local def_gr = "default:" .. group local def_gr = "default:" .. group
local stereotypes = craftguide.group_stereotypes
local stereotype = stereotypes and stereotypes[group]
if group_stereotypes[group] then if stereotype then
return group_stereotypes[group] return stereotype
elseif reg_items[def_gr] then elseif reg_items[def_gr] then
return def_gr return def_gr
end end
@ -386,7 +432,11 @@ local function get_tooltip(item, groups, cooktime, burntime)
groupstr = concat(groupstr, ", ") groupstr = concat(groupstr, ", ")
tooltip = S("Any item belonging to the group(s): @1", groupstr) tooltip = S("Any item belonging to the group(s): @1", groupstr)
else else
tooltip = reg_items[item].description local def = reg_items[item]
tooltip = def and def.description or
(def and match(item, ":.*"):gsub("%W%l", upper):sub(2):gsub("_", " ") or
S("Unknown Item (@1)", item))
end end
if cooktime then if cooktime then
@ -399,7 +449,7 @@ local function get_tooltip(item, groups, cooktime, burntime)
S("Burning time: @1", colorize("yellow", burntime)) S("Burning time: @1", colorize("yellow", burntime))
end end
return fmt(FMT.tooltip, item, ESC(tooltip)) return fmt("tooltip[%s;%s]", item, ESC(tooltip))
end end
local function get_recipe_fs(data, iY) local function get_recipe_fs(data, iY)
@ -413,7 +463,8 @@ local function get_recipe_fs(data, iY)
cooktime, width = width, 1 cooktime, width = width, 1
elseif width == 0 then elseif width == 0 then
shapeless = true shapeless = true
width = min(3, #recipe.items) local n = #recipe.items
width = n <= 4 and 2 or min(3, n)
end end
local rows = ceil(maxn(recipe.items) / width) local rows = ceil(maxn(recipe.items) / width)
@ -448,7 +499,7 @@ local function get_recipe_fs(data, iY)
if width > 3 or rows > 3 then if width > 3 or rows > 3 then
btn_size = width > 3 and 3 / width or 3 / rows btn_size = width > 3 and 3 / width or 3 / rows
s_btn_size = btn_size s_btn_size = btn_size
X = btn_size * (i % width) + xoffset - 2.65 X = btn_size * ((i - 1) % width) + xoffset - 2.65
Y = btn_size * floor((i - 1) / width) + (iY + 3) - min(2, rows) Y = btn_size * floor((i - 1) / width) + (iY + 3) - min(2, rows)
end end
@ -457,6 +508,7 @@ local function get_recipe_fs(data, iY)
end end
local groups local groups
if sub(item, 1,6) == "group:" then if sub(item, 1,6) == "group:" then
groups = extract_groups(item) groups = extract_groups(item)
item = groups_to_item(groups) item = groups_to_item(groups)
@ -491,7 +543,7 @@ local function get_recipe_fs(data, iY)
end end
fs[#fs + 1] = fmt(FMT.image, fs[#fs + 1] = fmt(FMT.image,
rightest + 1.2, min(3.9, rightest) + 1.2,
sfinv_only and 6.2 or iY + 1.7, sfinv_only and 6.2 or iY + 1.7,
0.5, 0.5,
0.5, 0.5,
@ -561,7 +613,7 @@ local function get_recipe_fs(data, iY)
end end
local function make_formspec(name) local function make_formspec(name)
local data = player_data[name] local data = pdata[name]
local iY = sfinv_only and 4 or data.iX - 5 local iY = sfinv_only and 4 or data.iX - 5
local ipp = data.iX * iY local ipp = data.iX * iY
@ -577,33 +629,22 @@ local function make_formspec(name)
background[1,1;1,1;craftguide_bg.png;true] background[1,1;1,1;craftguide_bg.png;true]
]] ]]
fs[#fs + 1] = fmt([[ tooltip[size_inc;%s]
tooltip[size_dec;%s] ]],
ESC(S("Increase window size")),
ESC(S("Decrease window size")))
fs[#fs + 1] = fmt([[ fs[#fs + 1] = fmt([[
image_button[%f,0.12;0.8,0.8;craftguide_zoomin_icon.png;size_inc;] image_button[%f,0.12;0.8,0.8;craftguide_zoomin_icon.png;size_inc;]
image_button[%f,0.12;0.8,0.8;craftguide_zoomout_icon.png;size_dec;] ]], image_button[%f,0.12;0.8,0.8;craftguide_zoomout_icon.png;size_dec;]
]],
data.iX * 0.47, data.iX * 0.47,
data.iX * 0.47 + 0.6) data.iX * 0.47 + 0.6)
end end
fs[#fs + 1] = fmt("field[0.3,0.33;2.5,1;filter;;%s]", ESC(data.filter))
fs[#fs + 1] = [[ fs[#fs + 1] = [[
image_button[2.4,0.12;0.8,0.8;craftguide_search_icon.png;search;] image_button[2.4,0.12;0.8,0.8;craftguide_search_icon.png;search;]
image_button[3.05,0.12;0.8,0.8;craftguide_clear_icon.png;clear;] image_button[3.05,0.12;0.8,0.8;craftguide_clear_icon.png;clear;]
field_close_on_enter[filter;false] field_close_on_enter[filter;false]
]] ]]
fs[#fs + 1] = fmt([[ tooltip[search;%s]
tooltip[clear;%s]
tooltip[prev;%s]
tooltip[next;%s] ]],
ESC(S("Search")),
ESC(S("Reset")),
ESC(S("Previous page")),
ESC(S("Next page")))
fs[#fs + 1] = fmt("label[%f,%f;%s / %u]", fs[#fs + 1] = fmt("label[%f,%f;%s / %u]",
sfinv_only and 6.3 or data.iX - 2.2, sfinv_only and 6.3 or data.iX - 2.2,
0.22, 0.22,
@ -612,12 +653,11 @@ local function make_formspec(name)
fs[#fs + 1] = fmt([[ fs[#fs + 1] = fmt([[
image_button[%f,0.12;0.8,0.8;craftguide_prev_icon.png;prev;] image_button[%f,0.12;0.8,0.8;craftguide_prev_icon.png;prev;]
image_button[%f,0.12;0.8,0.8;craftguide_next_icon.png;next;] ]], image_button[%f,0.12;0.8,0.8;craftguide_next_icon.png;next;]
]],
sfinv_only and 5.5 or data.iX - 3.1, sfinv_only and 5.5 or data.iX - 3.1,
sfinv_only and 7.3 or (data.iX - 1.2) - (data.iX >= 11 and 0.08 or 0)) sfinv_only and 7.3 or (data.iX - 1.2) - (data.iX >= 11 and 0.08 or 0))
fs[#fs + 1] = fmt("field[0.3,0.32;2.5,1;filter;;%s]", ESC(data.filter))
if #data.items == 0 then if #data.items == 0 then
local no_item = S("No item to show") local no_item = S("No item to show")
local pos = (data.iX / 2) - 1 local pos = (data.iX / 2) - 1
@ -653,17 +693,6 @@ local function make_formspec(name)
fs[#fs + 1] = get_recipe_fs(data, iY) fs[#fs + 1] = get_recipe_fs(data, iY)
end end
for elem_name, def in pairs(formspec_elements) do
local element = def.element(data)
if element then
if find(def.type, "button") then
insert(element, #element, elem_name)
end
fs[#fs + 1] = fmt(FMT[def.type], unpack(element))
end
end
return concat(fs) return concat(fs)
end end
@ -698,25 +727,33 @@ local function search(data)
return return
end end
local opt = "^(.-)%+([%w_]+)=([%w_,]+)"
local search_filter = next(search_filters) and match(filter, opt)
local filters = {}
if search_filter then
for filter_name, values in gmatch(filter, sub(opt, 6)) do
if search_filters[filter_name] then
values = split(values, ",")
filters[filter_name] = values
end
end
end
local filtered_list, c = {}, 0 local filtered_list, c = {}, 0
for i = 1, #data.items_raw do for i = 1, #data.items_raw do
local item = data.items_raw[i] local item = data.items_raw[i]
local def = reg_items[item] local def = reg_items[item]
local desc = lower(def.description) local desc = (def and def.description) and lower(def.description) or ""
local search_in = item .. desc local search_in = item .. " " .. desc
local pattern = "%+([%w_]+)=([%w_,]+)"
local to_add local to_add
if find(filter, pattern) then if search_filter then
local prepend = match(filter, "^(.-)%+") for filter_name, values in pairs(filters) do
for filter_name, values in gmatch(filter, pattern) do
local func = search_filters[filter_name] local func = search_filters[filter_name]
if func then to_add = func(item, values) and (search_filter == "" or
values = split(values, ",") find(search_in, search_filter, 1, true))
to_add = func(item, values) and (not prepend or
find(search_in, prepend, 1, true))
end
end end
else else
to_add = find(search_in, filter, 1, true) to_add = find(search_in, filter, 1, true)
@ -740,33 +777,8 @@ local function search(data)
data.items = filtered_list data.items = filtered_list
end end
local function get_inv_items(player)
local inv = player:get_inventory()
local stacks = {}
for i = 1, #item_lists do
local l = inv:get_list(item_lists[i])
stacks = table_merge(stacks, l)
end
local inv_items, c = {}, 0
for i = 1, #stacks do
local stack = stacks[i]
if not stack:is_empty() then
local name = stack:get_name()
if reg_items[name] then
c = c + 1
inv_items[c] = name
end
end
end
return inv_items
end
local function init_data(name) local function init_data(name)
player_data[name] = { pdata[name] = {
filter = "", filter = "",
pagenum = 1, pagenum = 1,
iX = sfinv_only and 8 or DEFAULT_SIZE, iX = sfinv_only and 8 or DEFAULT_SIZE,
@ -785,39 +797,43 @@ local function reset_data(data)
data.items = data.items_raw data.items = data.items_raw
end end
local function cache_usages() local function check_item(def)
for i = 1, #init_items do return not (def.groups.not_in_craft_guide == 1 or
local item = init_items[i] def.groups.not_in_creative_inventory == 1) and
usages_cache[item] = get_item_usages(item) def.description and def.description ~= ""
end
end end
local function get_init_items() local function get_init_items()
local c = 0 local items, c = {}, 0
for name, def in pairs(reg_items) do for name, def in pairs(reg_items) do
local is_fuel = cache_fuel(name) if check_item(def) then
if not (def.groups.not_in_craft_guide == 1 or cache_fuel(name)
def.groups.not_in_creative_inventory == 1) and cache_recipes(name)
def.description and def.description ~= "" and
(cache_recipes(name) or is_fuel) then c = c + 1
items[c] = name
end
end
c = 0
for i = 1, #items do
local name = items[i]
cache_usages(name)
if recipes_cache[name] or usages_cache[name] then
c = c + 1 c = c + 1
init_items[c] = name init_items[c] = name
end end
end end
sort(init_items) sort(init_items)
cache_usages()
end end
local function on_receive_fields(player, fields) local function on_receive_fields(player, fields)
local name = player:get_player_name() local name = player:get_player_name()
local data = player_data[name] local data = pdata[name]
for elem_name, def in pairs(formspec_elements) do
if fields[elem_name] and def.action then
return def.action(player, data)
end
end
if fields.clear then if fields.clear then
reset_data(data) reset_data(data)
@ -864,7 +880,6 @@ local function on_receive_fields(player, fields)
data.pagenum = 1 data.pagenum = 1
data.iX = data.iX + (fields.size_inc and 1 or -1) data.iX = data.iX + (fields.size_inc and 1 or -1)
show_fs(player, name) show_fs(player, name)
else else
local item local item
for field in pairs(fields) do for field in pairs(fields) do
@ -899,18 +914,13 @@ local function on_receive_fields(player, fields)
end end
end end
M.register_on_mods_loaded(get_init_items) core.register_on_mods_loaded(get_init_items)
M.register_on_joinplayer(function(player) core.register_on_joinplayer(function(player)
local name = player:get_player_name() local name = player:get_player_name()
init_data(name) init_data(name)
end) end)
M.register_on_leaveplayer(function(player)
local name = player:get_player_name()
player_data[name] = nil
end)
if sfinv_only then if sfinv_only then
sfinv.register_page("craftguide:craftguide", { sfinv.register_page("craftguide:craftguide", {
title = S("Craft Guide"), title = S("Craft Guide"),
@ -925,7 +935,7 @@ if sfinv_only then
on_enter = function(self, player, context) on_enter = function(self, player, context)
if next(recipe_filters) then if next(recipe_filters) then
local name = player:get_player_name() local name = player:get_player_name()
local data = player_data[name] local data = pdata[name]
data.items_raw = get_filtered_items(player) data.items_raw = get_filtered_items(player)
search(data) search(data)
@ -937,7 +947,7 @@ if sfinv_only then
end, end,
}) })
else else
M.register_on_player_receive_fields(function(player, formname, fields) core.register_on_player_receive_fields(function(player, formname, fields)
if formname == "craftguide" then if formname == "craftguide" then
on_receive_fields(player, fields) on_receive_fields(player, fields)
end end
@ -947,7 +957,7 @@ else
local name = user:get_player_name() local name = user:get_player_name()
if next(recipe_filters) then if next(recipe_filters) then
local data = player_data[name] local data = pdata[name]
data.items_raw = get_filtered_items(user) data.items_raw = get_filtered_items(user)
search(data) search(data)
end end
@ -955,7 +965,7 @@ else
show_formspec(name, "craftguide", make_formspec(name)) show_formspec(name, "craftguide", make_formspec(name))
end end
M.register_craftitem("craftguide:book", { core.register_craftitem("craftguide:book", {
description = S("Crafting Guide"), description = S("Crafting Guide"),
inventory_image = "craftguide_book.png", inventory_image = "craftguide_book.png",
wield_image = "craftguide_book.png", wield_image = "craftguide_book.png",
@ -966,7 +976,7 @@ else
end end
}) })
M.register_node("craftguide:sign", { core.register_node("craftguide:sign", {
description = S("Crafting Guide Sign"), description = S("Crafting Guide Sign"),
drawtype = "nodebox", drawtype = "nodebox",
tiles = {"craftguide_sign.png"}, tiles = {"craftguide_sign.png"},
@ -984,7 +994,7 @@ else
}, },
on_construct = function(pos) on_construct = function(pos)
local meta = M.get_meta(pos) local meta = core.get_meta(pos)
meta:set_string("infotext", "Crafting Guide Sign") meta:set_string("infotext", "Crafting Guide Sign")
end, end,
@ -993,27 +1003,27 @@ else
end end
}) })
M.register_craft({ core.register_craft({
output = "craftguide:book", output = "craftguide:book",
recipe = { recipe = {
{"default:book"} {"default:book"}
} }
}) })
M.register_craft({ core.register_craft({
type = "fuel", type = "fuel",
recipe = "craftguide:book", recipe = "craftguide:book",
burntime = 3 burntime = 3
}) })
M.register_craft({ core.register_craft({
output = "craftguide:sign", output = "craftguide:sign",
recipe = { recipe = {
{"default:sign_wall_wood"} {"default:sign_wall_wood"}
} }
}) })
M.register_craft({ core.register_craft({
type = "fuel", type = "fuel",
recipe = "craftguide:sign", recipe = "craftguide:sign",
burntime = 10 burntime = 10
@ -1032,6 +1042,9 @@ else
end end
if progressive_mode then if progressive_mode then
local PLAYERS = {}
local POLL_FREQ = 0.25
local function item_in_inv(item, inv_items) local function item_in_inv(item, inv_items)
local inv_items_size = #inv_items local inv_items_size = #inv_items
@ -1066,8 +1079,12 @@ if progressive_mode then
end end
local function progressive_filter(recipes, player) local function progressive_filter(recipes, player)
if not recipes then
return {}
end
local name = player:get_player_name() local name = player:get_player_name()
local data = player_data[name] local data = pdata[name]
if #data.inv_items == 0 then if #data.inv_items == 0 then
return {} return {}
@ -1085,68 +1102,191 @@ if progressive_mode then
return filtered return filtered
end end
local function get_inv_items(player)
local inv = player:get_inventory()
local stacks = {}
for i = 1, #item_lists do
local list = inv:get_list(item_lists[i])
table_merge(stacks, list)
end
local inv_items, c = {}, 0
for i = 1, #stacks do
local stack = stacks[i]
if not stack:is_empty() then
local name = stack:get_name()
if reg_items[name] then
c = c + 1
inv_items[c] = name
end
end
end
return inv_items
end
local function show_hud_success(player, data, dtime)
local hud_info_bg = player:hud_get(data.hud.bg)
if hud_info_bg.position.y <= 0.9 then
data.show_hud = false
data.hud_timer = (data.hud_timer or 0) + dtime
end
if data.show_hud then
for _, def in pairs(data.hud) do
local hud_info = player:hud_get(def)
player:hud_change(def, "position", {
x = hud_info.position.x,
y = hud_info.position.y - (dtime / 5)
})
end
player:hud_change(data.hud.text, "text",
S("@1 new recipe(s) discovered!", data.discovered))
elseif data.show_hud == false then
if data.hud_timer > 3 then
for _, def in pairs(data.hud) do
local hud_info = player:hud_get(def)
player:hud_change(def, "position", {
x = hud_info.position.x,
y = hud_info.position.y + (dtime / 5)
})
end
if hud_info_bg.position.y >= 1 then
data.show_hud = nil
data.hud_timer = nil
end
end
end
end
-- Workaround. Need an engine call to detect when the contents -- Workaround. Need an engine call to detect when the contents
-- of the player inventory changed, instead. -- of the player inventory changed, instead
local function poll_new_items() local function poll_new_items()
local players = M.get_connected_players() for i = 1, #PLAYERS do
for i = 1, #players do local player = PLAYERS[i]
local player = players[i]
local name = player:get_player_name() local name = player:get_player_name()
local data = player_data[name] local data = pdata[name]
local inv_items = get_inv_items(player) local inv_items = get_inv_items(player)
local diff = table_diff(inv_items, data.inv_items) local diff = table_diff(inv_items, data.inv_items)
if #diff > 0 then if #diff > 0 then
data.inv_items = table_merge(diff, data.inv_items) data.inv_items = table_merge(diff, data.inv_items)
local oldknown = data.known_recipes or 0
get_filtered_items(player, data)
data.discovered = data.known_recipes - oldknown
if data.show_hud == nil and data.discovered > 0 then
data.show_hud = true
end
end end
end end
M.after(POLL_FREQ, poll_new_items) after(POLL_FREQ, poll_new_items)
end end
poll_new_items() poll_new_items()
minetest.register_globalstep(function(dtime)
for i = 1, #PLAYERS do
local player = PLAYERS[i]
local name = player:get_player_name()
local data = pdata[name]
if data.show_hud ~= nil then
show_hud_success(player, data, dtime)
end
end
end)
craftguide.add_recipe_filter("Default progressive filter", progressive_filter) craftguide.add_recipe_filter("Default progressive filter", progressive_filter)
M.register_on_joinplayer(function(player) core.register_on_joinplayer(function(player)
PLAYERS = get_players()
local meta = player:get_meta() local meta = player:get_meta()
local name = player:get_player_name() local name = player:get_player_name()
local data = player_data[name] local data = pdata[name]
data.inv_items = deserialize(meta:get_string("inv_items")) or {} data.inv_items = deserialize(meta:get_string("inv_items")) or {}
data.known_recipes = deserialize(meta:get_string("known_recipes")) or 0
data.hud = {
bg = player:hud_add({
hud_elem_type = "image",
position = {x = 0.8, y = 1},
alignment = {x = 1, y = 1},
scale = {x = 320, y = 112},
text = "craftguide_bg.png",
}),
book = player:hud_add({
hud_elem_type = "image",
position = {x = 0.81, y = 1.02},
alignment = {x = 1, y = 1},
scale = {x = 4, y = 4},
text = "craftguide_book.png",
}),
text = player:hud_add({
hud_elem_type = "text",
position = {x = 0.85, y = 1.04},
alignment = {x = 1, y = 1},
number = 0xFFFFFF,
text = "",
}),
}
end) end)
local function save_meta(player) local function save_meta(player)
local meta = player:get_meta() local meta = player:get_meta()
local name = player:get_player_name() local name = player:get_player_name()
local data = player_data[name] local data = pdata[name]
meta:set_string("inv_items", serialize(data.inv_items)) meta:set_string("inv_items", serialize(data.inv_items))
meta:set_string("known_recipes", serialize(data.known_recipes))
end end
M.register_on_leaveplayer(save_meta) core.register_on_leaveplayer(function(player)
PLAYERS = get_players()
save_meta(player)
end)
M.register_on_shutdown(function() core.register_on_shutdown(function()
local players = M.get_connected_players() for i = 1, #PLAYERS do
for i = 1, #players do local player = PLAYERS[i]
local player = players[i]
save_meta(player) save_meta(player)
end end
end) end)
end end
M.register_chatcommand("craft", { core.register_on_leaveplayer(function(player)
local name = player:get_player_name()
pdata[name] = nil
end)
core.register_chatcommand("craft", {
description = S("Show recipe(s) of the pointed node"), description = S("Show recipe(s) of the pointed node"),
func = function(name) func = function(name)
local player = get_player_by_name(name) local player = get_player_by_name(name)
local ppos = player:get_pos()
local dir = player:get_look_dir() local dir = player:get_look_dir()
local eye_h = {x = ppos.x, y = ppos.y + 1.625, z = ppos.z} local ppos = player:get_pos()
ppos.y = ppos.y + 1.625
local node_name local node_name
for i = 1, 10 do for i = 1, 10 do
local look_at = vec_add(eye_h, vec_mul(dir, i)) local look_at = vec_add(ppos, vec_mul(dir, i))
local node = M.get_node(look_at) local node = core.get_node(look_at)
if node.name ~= "air" then if node.name ~= "air" then
node_name = node.name node_name = node.name
@ -1160,7 +1300,7 @@ M.register_chatcommand("craft", {
return false, red .. S("No node pointed") return false, red .. S("No node pointed")
end end
local data = player_data[name] local data = pdata[name]
reset_data(data) reset_data(data)
local recipes = recipes_cache[node_name] local recipes = recipes_cache[node_name]
@ -1195,9 +1335,9 @@ M.register_chatcommand("craft", {
function craftguide.show(name, item, show_usages) function craftguide.show(name, item, show_usages)
local func = "craftguide." .. __func() .. "(): " local func = "craftguide." .. __func() .. "(): "
assert(name, func .. "player name missing") assert(is_str(name), func .. "player name missing")
local data = player_data[name] local data = pdata[name]
local player = get_player_by_name(name) local player = get_player_by_name(name)
local query_item = data.query_item local query_item = data.query_item
@ -1211,26 +1351,3 @@ function craftguide.show(name, item, show_usages)
show_fs(player, name) show_fs(player, name)
end end
--[[ Custom recipes (>3x3) test code
M.register_craftitem(":secretstuff:custom_recipe_test", {
description = "Custom Recipe Test",
})
local cr = {}
for x = 1, 6 do
cr[x] = {}
for i = 1, 10 - x do
cr[x][i] = {}
for j = 1, 10 - x do
cr[x][i][j] = "group:wood"
end
end
M.register_craft({
output = "secretstuff:custom_recipe_test",
recipe = cr[x]
})
end
]]

View File

@ -2,7 +2,7 @@
Craft Guide=книга рецептов крафта Craft Guide=книга рецептов крафта
Crafting Guide=книга рецептов крафта Crafting Guide=книга рецептов крафта
Crafting Guide Sign= Crafting Guide Sign=Знак с книгой рецептов
Search=Поиск Search=Поиск
Reset=Сброс Reset=Сброс
Previous page=Предыдущая страница Previous page=Предыдущая страница
@ -13,13 +13,13 @@ Burning time: @1=Время горения: @1
Cooking time: @1=Время преготовления: @1 Cooking time: @1=Время преготовления: @1
Any item belonging to the group(s): @1=Любой элемент из группы: @1 Any item belonging to the group(s): @1=Любой элемент из группы: @1
Recipe is too big to be displayed (@1x@2)=Рецепт слишком большой для показа (@1x@2) Recipe is too big to be displayed (@1x@2)=Рецепт слишком большой для показа (@1x@2)
Shapeless= Shapeless=Бесформенный
Cooking= Cooking=Приготовление
Increase window size=Увеличить окно Increase window size=Увеличить окно
Decrease window size=Уменьшить окно Decrease window size=Уменьшить окно
No item to show=Нет элемента для показа No item to show=Нет элемента для показа
Collect items to reveal more recipes= Collect items to reveal more recipes=Собирайте предметы, чтобы раскрыть больше рецептов
Show recipe(s) of the pointed node= Show recipe(s) of the pointed node=Показать рецепт(ы) выбранной ноды
No node pointed= No node pointed=Не указана нода
You don't know a recipe for this node= You don't know a recipe for this node=Вы не знаете рецепт для этой ноды
No recipe for this node= No recipe for this node=Нет рецептов для этой ноды