Add support for dropped items + support for URL fetched recipes +

bugfixes
This commit is contained in:
Jean-Patrick Guerrero 2019-10-10 00:49:02 +02:00
parent 98a652259b
commit 4fb4fe1763
5 changed files with 174 additions and 105 deletions

@ -9,6 +9,7 @@ read_globals = {
"vector", "vector",
"string", "string",
"table", "table",
"ItemStack",
} }
globals = { globals = {

52
API.md

@ -35,7 +35,7 @@ craftguide.register_craft({
}) })
``` ```
Recipes can also be registered in a Minecraft-like way: Recipes can be registered in a Minecraft-like way:
```Lua ```Lua
craftguide.register_craft({ craftguide.register_craft({
@ -53,6 +53,47 @@ craftguide.register_craft({
}) })
``` ```
Multiples recipes can also be registered:
```Lua
craftguide.register_craft({
{
result = "default:mese",
items = {
"default:mese_crystal, default:mese_crystal",
"default:mese_crystal, default:mese_crystal",
}
},
{
result = "default:mese 2",
items = {
"default:mese_crystal, default:mese_crystal",
"default:mese_crystal, default:mese_crystal",
"default:mese_crystal, default:mese_crystal",
}
},
big = {
result = "default:mese 4",
items = {
"default:mese_crystal, default:mese_crystal",
"default:mese_crystal, default:mese_crystal",
"default:mese_crystal, default:mese_crystal",
"default:mese_crystal, default:mese_crystal",
}
},
})
```
Recipes can be registered via an URL (HTTP support is required¹):
```Lua
craftguide.register_craft({
url = https://raw.githubusercontent.com/minetest-mods/craftguide/master/test.json
})
```
--- ---
### Recipe filters ### Recipe filters
@ -168,3 +209,12 @@ You can add a stereotype like so:
```Lua ```Lua
craftguide.group_stereotypes.radioactive = "mod:item" craftguide.group_stereotypes.radioactive = "mod:item"
``` ```
#### `craftguide.http_post_data`
If set, the mod will export all the cached recipes and usages in a JSON format
to the given URL (HTTP support is required¹).
---
**¹** Add `craftguide` to the `secure.http_mods` or `secure.trusted_mods` setting in `minetest.conf`.

@ -19,4 +19,4 @@ For developers, `craftguide` also has a [modding API](https://github.com/minetes
Love this mod? Donations are appreciated: https://www.paypal.me/jpg84240 Love this mod? Donations are appreciated: https://www.paypal.me/jpg84240
![Preview2](https://i.imgur.com/GVQvufb.png) ![Preview2](https://i.imgur.com/w7KMS9G.png)

214
init.lua

@ -29,6 +29,8 @@ local reg_aliases = core.registered_aliases
local log = core.log local log = core.log
local after = core.after local after = core.after
local clr = core.colorize local clr = core.colorize
local parse_json = core.parse_json
local write_json = core.write_json
local chat_send = core.chat_send_player local chat_send = core.chat_send_player
local show_formspec = core.show_formspec local show_formspec = core.show_formspec
local globalstep = core.register_globalstep local globalstep = core.register_globalstep
@ -119,12 +121,13 @@ local function msg(name, str)
return chat_send(name, fmt("[craftguide] %s", clr("#FFFF00", str))) return chat_send(name, fmt("[craftguide] %s", clr("#FFFF00", str)))
end end
local function clean_str(str)
return match(str, "%S*")
end
local function is_str(x) local function is_str(x)
return type(x) == "string" and clean_str(x) return type(x) == "string"
end
local function true_str(str)
return is_str(str) and str ~= ""
end end
local function is_table(x) local function is_table(x)
@ -249,7 +252,7 @@ end
local craft_types = {} local craft_types = {}
function craftguide.register_craft_type(name, def) function craftguide.register_craft_type(name, def)
if not is_str(name) then if not true_str(name) then
return err("craftguide.register_craft_type(): name missing") return err("craftguide.register_craft_type(): name missing")
end end
@ -265,19 +268,43 @@ function craftguide.register_craft_type(name, def)
end end
function craftguide.register_craft(def) function craftguide.register_craft(def)
def.custom = true local width, c = 0, 0
def.width = 0
local c = 0 if true_str(def.url) then
if not http then
return err("No HTTP support for this mod. " ..
"Add it to the `secure.http_mods` or `secure.trusted_mods` setting.")
end
http.fetch({url = def.url}, function(result)
if result.succeeded then
local t = parse_json(result.data)
if is_table(t) then
return craftguide.register_craft(t)
end
end
end)
return
end
if not is_table(def) or not next(def) then if not is_table(def) or not next(def) then
return err("craftguide.register_craft(): craft definition missing") return err("craftguide.register_craft(): craft definition missing")
end end
if def.result then if #def > 1 then
def.output = def.result -- Backward compatibility for _, v in pairs(def) do
craftguide.register_craft(v)
end
return
end end
if not is_str(def.output) then if def.result then
def.output = def.result -- Backward compatibility
def.result = nil
end
if not true_str(def.output) then
return err("craftguide.register_craft(): output missing") return err("craftguide.register_craft(): output missing")
end end
@ -299,10 +326,10 @@ function craftguide.register_craft(def)
return #a > #b return #a > #b
end) end)
def.width = #cp[1] width = #cp[1]
for i = 1, #def.grid do for i = 1, #def.grid do
while #def.grid[i] < def.width do while #def.grid[i] < width do
def.grid[i] = def.grid[i] .. " " def.grid[i] = def.grid[i] .. " "
end end
end end
@ -319,32 +346,35 @@ function craftguide.register_craft(def)
items[i] = items[i]:gsub(",", ", ") items[i] = items[i]:gsub(",", ", ")
local rlen = #split(items[i], ",") local rlen = #split(items[i], ",")
if rlen > def.width then if rlen > width then
def.width = rlen width = rlen
end end
end end
for i = 1, len do for i = 1, len do
while #split(items[i], ",") < def.width do while #split(items[i], ",") < width do
items[i] = items[i] .. ", " items[i] = items[i] .. ", "
end end
end end
for name in gmatch(concat(items, ","), "[%s%w_:]+") do for name in gmatch(concat(items, ","), "[%s%w_:]+") do
c = c + 1 c = c + 1
def.items[c] = clean_str(name) def.items[c] = match(name, "%S+")
end end
end end
local output = clean_str(def.output) local output = match(def.output, "%S+")
recipes_cache[output] = recipes_cache[output] or {} recipes_cache[output] = recipes_cache[output] or {}
def.custom = true
def.width = width
insert(recipes_cache[output], def) insert(recipes_cache[output], def)
end end
local recipe_filters = {} local recipe_filters = {}
function craftguide.add_recipe_filter(name, f) function craftguide.add_recipe_filter(name, f)
if not is_str(name) then if not true_str(name) then
return err("craftguide.add_recipe_filter(): name missing") return err("craftguide.add_recipe_filter(): name missing")
elseif not is_func(f) then elseif not is_func(f) then
return err("craftguide.add_recipe_filter(): function missing") return err("craftguide.add_recipe_filter(): function missing")
@ -382,7 +412,7 @@ end
local search_filters = {} local search_filters = {}
function craftguide.add_search_filter(name, f) function craftguide.add_search_filter(name, f)
if not is_str(name) then if not true_str(name) then
return err("craftguide.add_search_filter(): name missing") return err("craftguide.add_search_filter(): name missing")
elseif not is_func(f) then elseif not is_func(f) then
return err("craftguide.add_search_filter(): function missing") return err("craftguide.add_search_filter(): function missing")
@ -630,6 +660,11 @@ local function get_tooltip(item, info)
tooltip = add(S("Repairable by step of @1", clr("yellow", toolrepair .. "%"))) tooltip = add(S("Repairable by step of @1", clr("yellow", toolrepair .. "%")))
end end
if info.rarity then
local chance = (1 / info.rarity) * 100
tooltip = add(S("@1 of chance to drop", clr("yellow", chance .. "%")))
end
return fmt("tooltip[%s;%s]", item, ESC(tooltip)) return fmt("tooltip[%s;%s]", item, ESC(tooltip))
end end
@ -670,7 +705,7 @@ local function get_output_fs(fs, L)
1.1, 1.1, PNG.fire) 1.1, 1.1, PNG.fire)
else else
local item = L.recipe.output local item = L.recipe.output
local name = clean_str(item) local name = match(item, "%S*")
fs[#fs + 1] = fmt(FMT.item_image_button, fs[#fs + 1] = fmt(FMT.item_image_button,
output_X, YOFFSET + (sfinv_only and 0.7 or 0) + L.spacing, output_X, YOFFSET + (sfinv_only and 0.7 or 0) + L.spacing,
@ -682,17 +717,17 @@ local function get_output_fs(fs, L)
1.1, 1.1, PNG.selected) 1.1, 1.1, PNG.selected)
end end
local burntime = fuel_cache[name] local infos = {
local repair = repairable(name) burntime = fuel_cache[name],
repair = repairable(name),
rarity = L.rarity,
}
if burntime or repair then if next(infos) then
fs[#fs + 1] = get_tooltip(name, { fs[#fs + 1] = get_tooltip(name, infos)
burntime = burntime,
repair = repair,
})
end end
if burntime then if infos.burntime then
fs[#fs + 1] = fmt(FMT.image, fs[#fs + 1] = fmt(FMT.image,
output_X + 1, YOFFSET + (sfinv_only and 0.7 or 0.1) + L.spacing, output_X + 1, YOFFSET + (sfinv_only and 0.7 or 0.1) + L.spacing,
0.6, 0.4, PNG.arrow) 0.6, 0.4, PNG.arrow)
@ -704,40 +739,11 @@ local function get_output_fs(fs, L)
end end
end end
local function pretty_wrap(str, limit)
return #str > limit + 3 and sub(str, 1, limit) .. "..." or str
end
local function get_itemdef_fs(fs, item, last_y)
fs[#fs + 1] = CORE_VERSION >= 510 and
fmt("background9[8.1,%f;6.6,2.19;%s;false;%d]",
last_y, PNG.bg_full, 10) or
fmt("background[8.1,%f;6.6,2.19;%s;false]", last_y, PNG.bg_full)
local specs = {
ESC(S("Name")),
}
local namestr = fmt("%s (%s)", pretty_wrap(get_desc(item), 25), item)
local tstr = ""
for i = 1, #specs do
tstr = tstr .. "#6389FF," .. specs[i] .. ",#FFFFFF,%s,"
end
tstr = sub(tstr, 1, -2)
fs[#fs + 1] = [[
tableoptions[background=#00000000;highlight=#00000000;border=false]
tablecolumns[color;text]
]]
fs[#fs + 1] = fmt("table[8.1,%f;6.3,1.8;itemdef;" .. tstr .. ";0]",
last_y + 0.08, namestr)
end
local function get_grid_fs(fs, rcp, spacing) local function get_grid_fs(fs, rcp, spacing)
if not rcp then return end
local width = rcp.width or 1 local width = rcp.width or 1
local replacements = rcp.replacements local replacements = rcp.replacements
local rarity = rcp.rarity
local rightest, btn_size, _btn_size = 0, 1.1 local rightest, btn_size, _btn_size = 0, 1.1
local cooktime, shapeless local cooktime, shapeless
@ -755,8 +761,7 @@ local function get_grid_fs(fs, rcp, spacing)
fs[#fs + 1] = fmt(FMT.label, fs[#fs + 1] = fmt(FMT.label,
XOFFSET + (sfinv_only and -1.5 or -1.6), XOFFSET + (sfinv_only and -1.5 or -1.6),
YOFFSET + (sfinv_only and 0.5 or spacing), YOFFSET + (sfinv_only and 0.5 or spacing),
ESC(S("Recipe's too big to be displayed (@1x@2)", ESC(S("Recipe's too big to be displayed (@1x@2)", width, rows)))
width, rows)))
return concat(fs) return concat(fs)
end end
@ -784,7 +789,7 @@ local function get_grid_fs(fs, rcp, spacing)
X = (btn_size * ((i - 1) % width) + XOFFSET - X = (btn_size * ((i - 1) % width) + XOFFSET -
(sfinv_only and 2.83 or 0.5)) * (0.83 - (x_y / 5)) (sfinv_only and 2.83 or 0.5)) * (0.83 - (x_y / 5))
Y = (btn_size * floor((i - 1) / width) + Y = (btn_size * floor((i - 1) / width) +
(sfinv_only and 5.81 or 4) + x_y) * (0.86 - (x_y / 5)) (sfinv_only and 5.81 or 5.5) + x_y) * (0.86 - (x_y / 5))
end end
if X > rightest then if X > rightest then
@ -814,21 +819,18 @@ local function get_grid_fs(fs, rcp, spacing)
fs[#fs + 1] = fmt(FMT.item_image_button, fs[#fs + 1] = fmt(FMT.item_image_button,
X, Y + (sfinv_only and 0.7 or 0), X, Y + (sfinv_only and 0.7 or 0),
btn_size, btn_size, item, clean_str(item), ESC(label)) btn_size, btn_size, item, match(item, "%S*"), ESC(label))
local infos = { local infos = {
unknown = not reg_items[item], unknown = reg_items[item] and nil,
groups = groups, groups = groups,
burntime = fuel_cache[item], burntime = fuel_cache[item],
cooktime = cooktime, cooktime = cooktime,
replace = replace, replace = replace,
} }
for _, info in pairs(infos) do if next(infos) then
if info then
fs[#fs + 1] = get_tooltip(item, infos) fs[#fs + 1] = get_tooltip(item, infos)
break
end
end end
if CORE_VERSION >= 510 and not large_recipe then if CORE_VERSION >= 510 and not large_recipe then
@ -836,11 +838,11 @@ local function get_grid_fs(fs, rcp, spacing)
X, Y + (sfinv_only and 0.7 or 0), X, Y + (sfinv_only and 0.7 or 0),
btn_size, btn_size, PNG.selected) btn_size, btn_size, PNG.selected)
end end
end
if large_recipe then if large_recipe then
fs[#fs + 1] = "style_type[item_image_button;border=false]" fs[#fs + 1] = "style_type[item_image_button;border=false]"
end end
end
get_output_fs(fs, { get_output_fs(fs, {
recipe = rcp, recipe = rcp,
@ -849,12 +851,13 @@ local function get_grid_fs(fs, rcp, spacing)
btn_size = btn_size, btn_size = btn_size,
_btn_size = _btn_size, _btn_size = _btn_size,
spacing = spacing, spacing = spacing,
rarity = rarity,
}) })
end end
local function get_panels(data, fs) local function get_panels(data, fs)
local panels = {recipes = data.recipes, usages = data.usages} local panels = {recipes = data.recipes or {}, usages = data.usages or {}}
local x, last_y = 0 local x = 0.33
if sfinv_only then if sfinv_only then
panels = data.show_usages and panels = data.show_usages and
@ -864,7 +867,6 @@ local function get_panels(data, fs)
for k, v in pairs(panels) do for k, v in pairs(panels) do
x = x + 1 x = x + 1
local spacing = (x - 1) * 3.6 local spacing = (x - 1) * 3.6
last_y = -0.2 + x * 3.6
if not sfinv_only then if not sfinv_only then
fs[#fs + 1] = CORE_VERSION >= 510 and fs[#fs + 1] = CORE_VERSION >= 510 and
@ -876,7 +878,11 @@ local function get_panels(data, fs)
local btn_lab local btn_lab
if (not sfinv_only and k == "recipes") or if not sfinv_only and #v == 0 then
btn_lab = clr("red", k == "recipes" and
ESC(S("No recipes")) or ESC(S("No usages")))
elseif (not sfinv_only and k == "recipes") or
(sfinv_only and not data.show_usages) then (sfinv_only and not data.show_usages) then
btn_lab = ESC(S("Recipe @1 of @2", data.rnum, #v)) btn_lab = ESC(S("Recipe @1 of @2", data.rnum, #v))
@ -909,10 +915,6 @@ local function get_panels(data, fs)
local rcp = k == "recipes" and v[data.rnum] or v[data.unum] local rcp = k == "recipes" and v[data.rnum] or v[data.unum]
get_grid_fs(fs, rcp, spacing) get_grid_fs(fs, rcp, spacing)
end end
if not sfinv_only then
get_itemdef_fs(fs, data.query_item, last_y)
end
end end
local function make_formspec(name) local function make_formspec(name)
@ -1109,9 +1111,9 @@ core.register_craft = function(def)
toolrepair = def.additional_wear * -100 toolrepair = def.additional_wear * -100
end end
local output = def.output or is_str(def.recipe) local output = def.output or (true_str(def.recipe) and def.recipe) or nil
if not output then return end if not output then return end
output = {clean_str(output)} output = {match(output, "%S+")}
local groups local groups
@ -1180,11 +1182,12 @@ local function handle_drops_table(name, drop)
local di = drop_items[i] local di = drop_items[i]
for j = 1, #di.items do for j = 1, #di.items do
if not is_str(di.items[j]) then break end local dstack = ItemStack(di.items[j])
local dname, dcount = match(di.items[j], "(.*)%s*(%d*)") local dname = dstack:get_name()
dcount = dcount and tonumber(dcount) or 1
if not dstack:is_empty() and dname ~= name then
local dcount = dstack:get_count()
if dname ~= name then
if #di.items == 1 and di.rarity == 1 and max_start then if #di.items == 1 and di.rarity == 1 and max_start then
if not drop_sure[dname] then if not drop_sure[dname] then
drop_sure[dname] = 0 drop_sure[dname] = 0
@ -1202,10 +1205,17 @@ local function handle_drops_table(name, drop)
end end
if not drop_maybe[dname] then if not drop_maybe[dname] then
drop_maybe[dname] = 0 drop_maybe[dname] = {}
end end
drop_maybe[dname] = drop_maybe[dname] + dcount if not drop_maybe[dname].output then
drop_maybe[dname].output = 0
end
drop_maybe[dname] = {
output = drop_maybe[dname].output + dcount,
rarity = di.rarity,
}
end end
end end
end end
@ -1219,19 +1229,21 @@ local function handle_drops_table(name, drop)
}) })
end end
for item, count in pairs(drop_maybe) do for item, data in pairs(drop_maybe) do
craftguide.register_craft({ craftguide.register_craft({
type = "digging_chance", type = "digging_chance",
items = {name}, items = {name},
output = fmt("%s %u", item, count), output = fmt("%s %u", item, data.output),
rarity = data.rarity,
}) })
end end
end end
local function register_drops(name, def) local function register_drops(name, def)
local drop = def.drop local drop = def.drop
local dstack = ItemStack(drop)
if is_str(drop) and drop ~= name then if not dstack:is_empty() and dstack:get_name() ~= name then
craftguide.register_craft({ craftguide.register_craft({
type = "digging", type = "digging",
items = {name}, items = {name},
@ -1308,6 +1320,19 @@ local function get_init_items()
handle_aliases(hash) handle_aliases(hash)
sort(init_items) sort(init_items)
if http and true_str(craftguide.http_post_data) then
local post_data = {
recipes = recipes_cache,
usages = usages_cache,
fuel = fuel_cache,
}
http.fetch_async({
url = craftguide.http_post_data,
post_data = write_json(post_data),
})
end
end end
local function init_data(name) local function init_data(name)
@ -1808,7 +1833,7 @@ on_leaveplayer(function(player)
end) end)
function craftguide.show(name, item, show_usages) function craftguide.show(name, item, show_usages)
if not is_str(name) then if not true_str(name)then
return err("craftguide.show(): player name missing") return err("craftguide.show(): player name missing")
end end
@ -1871,12 +1896,3 @@ register_command("craft", {
return true, craftguide.show(name, node_name) return true, craftguide.show(name, node_name)
end, end,
}) })
if http then
local data = http.fetch_async({
url = "https://raw.githubusercontent.com/minetest-mods/craftguide/master/test.json",
})
print(type(data))
print(core.parse_json(data))
end

@ -5,6 +5,8 @@ Crafting Guide=
Crafting Guide Sign= Crafting Guide Sign=
Usage @1 of @2= Usage @1 of @2=
Recipe @1 of @2= Recipe @1 of @2=
No recipes=
No usages=
Burning time: @1= Burning time: @1=
Cooking time: @1= Cooking time: @1=
Replaced by @1 on smelting= Replaced by @1 on smelting=
@ -21,6 +23,6 @@ Show recipe(s) of the pointed node=
No node pointed= No node pointed=
You don't know a recipe or usage for this item= You don't know a recipe or usage for this item=
No recipe or usage for this item= No recipe or usage for this item=
Name=
Digging= Digging=
Digging Chance= Digging Chance=
@1 of chance to drop=