pax_global_header00006660000000000000000000000064136047422570014524gustar00rootroot0000000000000052 comment=ff13445851aba062803fae15bfee1989c8be75fc craftguide-1.11/000077500000000000000000000000001360474225700135635ustar00rootroot00000000000000craftguide-1.11/.gitignore000066400000000000000000000004101360474225700155460ustar00rootroot00000000000000## Files related to minetest development cycle /*.patch # GNU Patch reject file *.rej ## Editors and Development environments *~ *.swp *.bak* *.orig # Vim *.vim # Kate .*.kate-swp .swp.* # Eclipse (LDT) .project .settings/ .buildpath .metadata # Idea IDE .idea/* craftguide-1.11/.luacheckrc000066400000000000000000000003131360474225700156650ustar00rootroot00000000000000unused_args = false allow_defined_top = true read_globals = { "minetest", "default", "sfinv", "sfinv_buttons", "vector", "string", "table", "ItemStack", } globals = { "craftguide", "core", } craftguide-1.11/API.md000066400000000000000000000124121360474225700145160ustar00rootroot00000000000000## API ### Custom recipes Custom recipes are nonconventional crafts outside the main crafting grid. They can be registered in-game dynamically and have a size beyond 3x3 items. **Note:** the registration format differs from the default registration format in everything. The width is automatically calculated depending where you place the commas. Look at the examples attentively. #### Registering a custom crafting type (example) ```Lua craftguide.register_craft_type("digging", { description = "Digging", icon = "default_tool_steelpick.png", }) ``` #### Registering a custom crafting recipe (examples) ```Lua craftguide.register_craft({ type = "digging", result = "default:cobble 2", items = {"default:stone"}, }) ``` ```Lua craftguide.register_craft({ result = "default:cobble 16", items = { "default:stone, default:stone, default:stone", "default:stone, , default:stone", "default:stone, default:stone, default:stone", } }) ``` Recipes can 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", }) ``` 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", } }, 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 from a given URL containing a JSON file (HTTP support is required¹): ```Lua craftguide.register_craft({ url = "https://raw.githubusercontent.com/minetest-mods/craftguide/master/test.json" }) ``` --- ### Recipe filters Recipe filters can be used to filter the recipes shown to players. Progressive mode is implemented as a recipe filter. #### `craftguide.add_recipe_filter(name, function(recipes, player))` Adds a recipe filter with the given name. The filter function should return the recipes to be displayed, given the available recipes and an `ObjectRef` to the user. Each recipe is a table of the form returned by `minetest.get_craft_recipe`. Example function to hide recipes for items from a mod called "secretstuff": ```lua craftguide.add_recipe_filter("Hide secretstuff", function(recipes) local filtered = {} for _, recipe in ipairs(recipes) do if recipe.output:sub(1,12) ~= "secretstuff:" then filtered[#filtered + 1] = recipe end end return filtered end) ``` #### `craftguide.set_recipe_filter(name, function(recipe, player))` Removes all recipe filters and adds a new one. #### `craftguide.remove_recipe_filter(name)` Removes the recipe filter with the given name. #### `craftguide.get_recipe_filters()` Returns a map of recipe filters, indexed by name. --- ### Search filters Search filters are used to perform specific searches inside the search field. They can be used like so: `+=,,<...>` 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: - If `optional name` is omitted, the search filter will apply to all items, without pre-filtering. - Filters can be combined. - The `groups` filter is currently implemented by default. #### `craftguide.add_search_filter(name, function(item, values))` Adds a search filter with the given name. The search function should return a boolean value (whether the given item should be listed or not). Example function to show items which contain at least a recipe of given width(s): ```lua craftguide.add_search_filter("widths", function(item, widths) local has_width local recipes = recipes_cache[item] if recipes then for i = 1, #recipes do local recipe_width = recipes[i].width for j = 1, #widths do local width = tonumber(widths[j]) if width == recipe_width then has_width = true break end end end end return has_width end) ``` #### `craftguide.remove_search_filter(name)` Removes the search filter with the given name. #### `craftguide.get_search_filters()` Returns a map of search filters, indexed by name. --- ### Miscellaneous #### `craftguide.show(player_name, item, show_usages)` Opens the Crafting Guide with the current filter applied. * `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 * `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" ``` #### `craftguide.export_url` 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`. craftguide-1.11/README.md000066400000000000000000000017351360474225700150500ustar00rootroot00000000000000# ![Preview1](http://i.imgur.com/fIPNYkb.png) Crafting Guide #### `craftguide` is the most comprehensive crafting guide on Minetest. #### Consult the [Minetest Wiki](http://wiki.minetest.net/Crafting_guide) for more details. This crafting guide is a blue book named *"Crafting Guide"* or a wooden sign. This crafting guide features a **progressive mode**. This mode is a Terraria-like system that shows recipes you can craft from items you ever had in your inventory. To enable it: `craftguide_progressive_mode = true` in `minetest.conf`. `craftguide` is also integrated in `sfinv` (Minetest Game inventory). To enable it: `craftguide_sfinv_only = true` in `minetest.conf`. Use the command `/craft` to show the recipe(s) of the pointed node. For developers, `craftguide` also has a [modding API](https://github.com/minetest-mods/craftguide/blob/master/API.md). Love this mod? Donations are appreciated: https://www.paypal.me/jpg84240 ![Preview2](https://i.imgur.com/w7KMS9G.png) craftguide-1.11/init.lua000066400000000000000000001360771360474225700152470ustar00rootroot00000000000000craftguide = {} local p = 0 local CORE_VERSION = core.get_version().string:match("[^%-]*"):gsub("%.", function(a) p = p + 1 return p == 3 and a or "" end) CORE_VERSION = tonumber(CORE_VERSION) -- Caches local pdata = {} local init_items = {} local searches = {} local recipes_cache = {} local usages_cache = {} local fuel_cache = {} local toolrepair local progressive_mode = core.settings:get_bool("craftguide_progressive_mode") local sfinv_only = core.settings:get_bool("craftguide_sfinv_only") and rawget(_G, "sfinv") local http = core.request_http_api() local reg_items = core.registered_items local reg_tools = core.registered_tools local reg_aliases = core.registered_aliases local log = core.log local after = core.after local clr = core.colorize local parse_json = core.parse_json local write_json = core.write_json local chat_send = core.chat_send_player local show_formspec = core.show_formspec local globalstep = core.register_globalstep local on_shutdown = core.register_on_shutdown local get_players = core.get_connected_players local get_craft_result = core.get_craft_result local on_joinplayer = core.register_on_joinplayer local get_all_recipes = core.get_all_craft_recipes local register_command = core.register_chatcommand local get_player_by_name = core.get_player_by_name local slz, dslz = core.serialize, core.deserialize local on_mods_loaded = core.register_on_mods_loaded local on_leaveplayer = core.register_on_leaveplayer local on_receive_fields = core.register_on_player_receive_fields local ESC = core.formspec_escape local S = CORE_VERSION >= 500 and core.get_translator("craftguide") or function(...) local args, i = {...}, 1 return args[1]:gsub("@%d+", function() i = i + 1 return args[i] end) end local ES = function(...) return ESC(S(...)) end local maxn, sort, concat, copy, insert, remove = table.maxn, table.sort, table.concat, table.copy, table.insert, table.remove local fmt, find, gmatch, match, sub, split, upper, lower = string.format, string.find, string.gmatch, string.match, string.sub, string.split, string.upper, string.lower local min, max, floor, ceil = math.min, math.max, math.floor, math.ceil local pairs, next, type, tostring, io = pairs, next, type, tostring, io local vec_add, vec_mul = vector.add, vector.multiply local ROWS = 9 local LINES = sfinv_only and 5 or 9 local IPP = ROWS * LINES local WH_LIMIT = 8 local XOFFSET = sfinv_only and 3.83 or 11.2 local YOFFSET = sfinv_only and 6 or 1 local PNG = { bg = "craftguide_bg.png", bg_full = "craftguide_bg_full.png", search = "craftguide_search_icon.png", clear = "craftguide_clear_icon.png", prev = "craftguide_next_icon.png^\\[transformFX", next = "craftguide_next_icon.png", arrow = "craftguide_arrow.png", fire = "craftguide_fire.png", book = "craftguide_book.png", sign = "craftguide_sign.png", selected = "craftguide_selected.png", search_hover = "craftguide_search_icon_hover.png", clear_hover = "craftguide_clear_icon_hover.png", prev_hover = "craftguide_next_icon_hover.png^\\[transformFX", next_hover = "craftguide_next_icon_hover.png", } local FMT = { box = "box[%f,%f;%f,%f;%s]", label = "label[%f,%f;%s]", image = "image[%f,%f;%f,%f;%s]", button = "button[%f,%f;%f,%f;%s;%s]", tooltip = "tooltip[%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]", item_image_button = "item_image_button[%f,%f;%f,%f;%s;%s;%s]", arrow = "image_button[%f,%f;0.8,0.8;%s;%s;;;false;%s]", } local function mul_elem(elem, n) local fstr, elems = "", {} for i = 1, n do fstr = fstr .. "%s" elems[i] = elem end return fmt(fstr, unpack(elems)) end craftguide.group_stereotypes = { dye = "dye:white", wool = "wool:white", coal = "default:coal_lump", vessel = "vessels:glass_bottle", flower = "flowers:dandelion_yellow", water_bucket = "bucket:bucket_water", mesecon_conductor_craftable = "mesecons:wire_00000000_off", } local function err(str) return log("error", str) end local function msg(name, str) return chat_send(name, fmt("[craftguide] %s", clr("#f00", str))) end local function is_str(x) return type(x) == "string" end local function true_str(str) return is_str(str) and str ~= "" end local function is_table(x) return type(x) == "table" end local function is_func(x) return type(x) == "function" end local function is_group(item) return sub(item, 1, 6) == "group:" end local function clean_name(item) if sub(item, 1, 1) == ":" then item = sub(item, 2) end return item end local function array_diff(t1, t2) local hash = {} for i = 1, #t1 do local v = t1[i] hash[v] = true end for i = 1, #t2 do local v = t2[i] hash[v] = nil end local diff, c = {}, 0 for i = 1, #t1 do local v = t1[i] if hash[v] then c = c + 1 diff[c] = v end end return diff end local function table_eq(T1, T2) local avoid_loops = {} local function recurse(t1, t2) if type(t1) ~= type(t2) then return end if not is_table(t1) then return t1 == t2 end if avoid_loops[t1] then return avoid_loops[t1] == t2 end avoid_loops[t1] = t2 local t2k, t2kv = {}, {} for k in pairs(t2) do if is_table(k) then insert(t2kv, k) end t2k[k] = true end for k1, v1 in pairs(t1) do local v2 = t2[k1] if type(k1) == "table" then local ok for i = 1, #t2kv do local tk = t2kv[i] if table_eq(k1, tk) and recurse(v1, t2[tk]) then remove(t2kv, i) t2k[tk] = nil ok = true break end end if not ok then return end else if v2 == nil then return end t2k[k1] = nil if not recurse(v1, v2) then return end end end if next(t2k) then return end return true end return recurse(T1, T2) end local function table_merge(t1, t2, hash) t1 = t1 or {} t2 = t2 or {} if hash then for k, v in pairs(t2) do t1[k] = v end else local c = #t1 for i = 1, #t2 do c = c + 1 t1[c] = t2[i] end end return t1 end local function table_replace(t, val, new) for k, v in pairs(t) do if v == val then t[k] = new end end end local craft_types = {} function craftguide.register_craft_type(name, def) if not true_str(name) then return err"craftguide.register_craft_type(): name missing" end if not is_str(def.description) then def.description = "" end if not is_str(def.icon) then def.icon = "" end craft_types[name] = def end function craftguide.register_craft(def) local width, c = 0, 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 return err"craftguide.register_craft(): craft definition missing" end if #def > 1 then for _, v in pairs(def) do craftguide.register_craft(v) end return end 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" end if not is_table(def.items) then def.items = {} end if def.grid then if not is_table(def.grid) then def.grid = {} end if not is_table(def.key) then def.key = {} end local cp = copy(def.grid) sort(cp, function(a, b) return #a > #b end) width = #cp[1] for i = 1, #def.grid do while #def.grid[i] < width do def.grid[i] = def.grid[i] .. " " end end for symbol in gmatch(concat(def.grid), ".") do c = c + 1 def.items[c] = def.key[symbol] end else local items, len = def.items, #def.items def.items = {} for i = 1, len do items[i] = items[i]:gsub(",", ", ") local rlen = #split(items[i], ",") if rlen > width then width = rlen end end for i = 1, len do while #split(items[i], ",") < width do items[i] = items[i] .. ", " end end for name in gmatch(concat(items, ","), "[%s%w_:]+") do c = c + 1 def.items[c] = match(name, "%S+") end end local output = match(def.output, "%S+") recipes_cache[output] = recipes_cache[output] or {} def.custom = true def.width = width insert(recipes_cache[output], def) end local recipe_filters = {} function craftguide.add_recipe_filter(name, f) if not true_str(name) then return err"craftguide.add_recipe_filter(): name missing" elseif not is_func(f) then return err"craftguide.add_recipe_filter(): function missing" end recipe_filters[name] = f end function craftguide.set_recipe_filter(name, f) if not is_str(name) then return err"craftguide.set_recipe_filter(): name missing" elseif not is_func(f) then return err"craftguide.set_recipe_filter(): function missing" end recipe_filters = {[name] = f} end function craftguide.remove_recipe_filter(name) recipe_filters[name] = nil end function craftguide.get_recipe_filters() return recipe_filters end local function apply_recipe_filters(recipes, player) for _, filter in pairs(recipe_filters) do recipes = filter(recipes, player) end return recipes end local search_filters = {} function craftguide.add_search_filter(name, f) if not true_str(name) then return err"craftguide.add_search_filter(): name missing" elseif not is_func(f) then return err"craftguide.add_search_filter(): function missing" end search_filters[name] = f end function craftguide.remove_search_filter(name) search_filters[name] = nil end function craftguide.get_search_filters() return search_filters end local function item_has_groups(item_groups, groups) for i = 1, #groups do local group = groups[i] if (item_groups[group] or 0) == 0 then return end end return true end local function extract_groups(str) return split(sub(str, 7), ",") end local function item_in_recipe(item, recipe) local clean_item = reg_aliases[item] or item for _, recipe_item in pairs(recipe.items) do local clean_recipe_item = reg_aliases[recipe_item] or recipe_item if clean_recipe_item == clean_item then return true end end end local function groups_item_in_recipe(item, recipe) local def = reg_items[item] if not def then return end local item_groups = def.groups for _, recipe_item in pairs(recipe.items) do if is_group(recipe_item) then local groups = extract_groups(recipe_item) if item_has_groups(item_groups, groups) then local usage = copy(recipe) table_replace(usage.items, recipe_item, item) return usage end end end end local function get_filtered_items(player, data) local items, known, c = {}, 0, 0 for i = 1, #init_items do local item = init_items[i] local recipes = recipes_cache[item] local usages = usages_cache[item] recipes = #apply_recipe_filters(recipes or {}, player) usages = #apply_recipe_filters(usages or {}, player) if recipes > 0 or usages > 0 then c = c + 1 items[c] = item if data then known = known + recipes + usages end end end if data then data.known_recipes = known end return items end local function get_usages(item) local usages, c = {}, 0 for _, recipes in pairs(recipes_cache) do for i = 1, #recipes do local recipe = recipes[i] if item_in_recipe(item, recipe) then c = c + 1 usages[c] = recipe else recipe = groups_item_in_recipe(item, recipe) if recipe then c = c + 1 usages[c] = recipe end end end end if fuel_cache[item] then usages[#usages + 1] = { type = "fuel", items = {item}, replacements = fuel_cache.replacements[item], } end return usages end local function get_burntime(item) return get_craft_result{method = "fuel", items = {item}}.time end local function cache_fuel(item) local burntime = get_burntime(item) if burntime > 0 then fuel_cache[item] = burntime end end local function cache_usages(item) local usages = get_usages(item) if #usages > 0 then usages_cache[item] = table_merge(usages, usages_cache[item] or {}) end end local function cache_recipes(output) local recipes = get_all_recipes(output) or {} if #recipes > 0 then recipes_cache[output] = recipes end end local function get_recipes(item, data, player) local clean_item = reg_aliases[item] or item local recipes = recipes_cache[clean_item] local usages = usages_cache[clean_item] if recipes then recipes = apply_recipe_filters(recipes, player) end local no_recipes = not recipes or #recipes == 0 if no_recipes and not usages then return elseif sfinv_only and usages and no_recipes then data.show_usages = true end if not sfinv_only or (sfinv_only and data.show_usages) then usages = apply_recipe_filters(usages, player) end local no_usages = not usages or #usages == 0 return not no_recipes and recipes or nil, not no_usages and usages or nil end local function groups_to_items(groups, get_all) if not get_all and #groups == 1 then local group = groups[1] local def_gr = "default:" .. group local stereotypes = craftguide.group_stereotypes local stereotype = stereotypes and stereotypes[group] if stereotype then return stereotype elseif reg_items[def_gr] then return def_gr end end local names = {} for name, def in pairs(reg_items) do if item_has_groups(def.groups, groups) then if get_all then names[#names + 1] = name else return name end end end return get_all and names or "" end local function repairable(tool) local def = reg_tools[tool] return toolrepair and def and def.groups and def.groups.disable_repair ~= 1 end local function is_fav(data) local fav, i for j = 1, #data.favs do if data.favs[j] == data.query_item then fav = true i = j break end end return fav, i end local function get_desc(name) local def = reg_items[name] return def and (match(def.description, "%)([%w%s]*)") or def.description) or (def and match(name, ":.*"):gsub("%W%l", upper):sub(2):gsub("_", " ") or S("Unknown Item (@1)", name)) end local function get_tooltip(name, info) local tooltip if info.groups then local groupstr, c = {}, 0 for i = 1, #info.groups do c = c + 1 groupstr[c] = clr("#ff0", info.groups[i]) end groupstr = concat(groupstr, ", ") tooltip = S("Any item belonging to the group(s): @1", groupstr) else tooltip = get_desc(name) end local function add(str) return fmt("%s\n%s", tooltip, str) end if info.cooktime then tooltip = add(S("Cooking time: @1", clr("#ff0", info.cooktime))) end if info.burntime then tooltip = add(S("Burning time: @1", clr("#ff0", info.burntime))) end if info.replace then local desc = clr("#ff0", get_desc(info.replace)) if info.cooktime then tooltip = add(S("Replaced by @1 on smelting", desc)) elseif info.burntime then tooltip = add(S("Replaced by @1 on burning", desc)) else tooltip = add(S("Replaced by @1 on crafting", desc)) end end if info.repair then tooltip = add(S("Repairable by step of @1", clr("#ff0", toolrepair .. "%"))) end if info.rarity then local chance = (1 / info.rarity) * 100 tooltip = add(S("@1 of chance to drop", clr("#ff0", chance .. "%"))) end return fmt("tooltip[%s;%s]", name, ESC(tooltip)) end local function get_output_fs(fs, L) local custom_recipe = craft_types[L.recipe.type] if custom_recipe or L.shapeless or L.recipe.type == "cooking" then local icon = custom_recipe and custom_recipe.icon or L.shapeless and "shapeless" or "furnace" if not custom_recipe then icon = fmt("craftguide_%s.png^[resize:16x16", icon) end local pos_x = L.rightest + L.btn_size + 0.1 local pos_y = YOFFSET + (sfinv_only and 0.25 or -0.45) fs[#fs + 1] = fmt(FMT.image, pos_x, pos_y + L.spacing, 0.5, 0.5, icon) local tooltip = custom_recipe and custom_recipe.description or L.shapeless and S"Shapeless" or S"Cooking" if CORE_VERSION >= 500 then fs[#fs + 1] = fmt(FMT.tooltip, pos_x, pos_y + L.spacing, 0.5, 0.5, ESC(tooltip)) end end local arrow_X = L.rightest + (L._btn_size or 1.1) local output_X = arrow_X + 0.9 fs[#fs + 1] = fmt(FMT.image, arrow_X, YOFFSET + (sfinv_only and 0.9 or 0.2) + L.spacing, 0.9, 0.7, PNG.arrow) if L.recipe.type == "fuel" then fs[#fs + 1] = fmt(FMT.image, output_X, YOFFSET + (sfinv_only and 0.7 or 0) + L.spacing, 1.1, 1.1, PNG.fire) else local item = L.recipe.output item = clean_name(item) local name = match(item, "%S*") if CORE_VERSION >= 510 then fs[#fs + 1] = fmt(FMT.image, output_X, YOFFSET + (sfinv_only and 0.7 or 0) + L.spacing, 1.1, 1.1, PNG.selected) end fs[#fs + 1] = fmt(FMT.item_image_button, output_X, YOFFSET + (sfinv_only and 0.7 or 0) + L.spacing, 1.1, 1.1, item, name, "") local infos = { unknown = not reg_items[name] or nil, burntime = fuel_cache[name], repair = repairable(name), rarity = L.rarity, } if next(infos) then fs[#fs + 1] = get_tooltip(name, infos) end if infos.burntime then fs[#fs + 1] = fmt(FMT.image, output_X + 1, YOFFSET + (sfinv_only and 0.7 or 0.1) + L.spacing, 0.6, 0.4, PNG.arrow) fs[#fs + 1] = fmt(FMT.image, output_X + 1.6, YOFFSET + (sfinv_only and 0.55 or 0) + L.spacing, 0.6, 0.6, PNG.fire) end end end local function get_grid_fs(fs, rcp, spacing) local width = rcp.width or 1 local replacements = rcp.replacements local rarity = rcp.rarity local rightest, btn_size, _btn_size = 0, 1.1 local cooktime, shapeless if rcp.type == "cooking" then cooktime, width = width, 1 elseif width == 0 and not rcp.custom then shapeless = true local n = #rcp.items width = (n < 5 and n > 1) and 2 or min(3, max(1, n)) end local rows = ceil(maxn(rcp.items) / width) if width > WH_LIMIT or rows > WH_LIMIT then fs[#fs + 1] = fmt(FMT.label, XOFFSET + (sfinv_only and -1.5 or -1.6), YOFFSET + (sfinv_only and 0.5 or spacing), ES("Recipe's too big to be displayed (@1x@2)", width, rows)) return concat(fs) end local large_recipe = width > 3 or rows > 3 if large_recipe then fs[#fs + 1] = "style_type[item_image_button;border=true]" end for i = 1, width * rows do local item = rcp.items[i] or "" item = clean_name(item) local name = match(item, "%S*") local X = ceil((i - 1) % width - width) + XOFFSET local Y = ceil(i / width) + YOFFSET - min(2, rows) + spacing if large_recipe then local xof = 1 - 4 / width local yof = 1 - 4 / rows local x_y = width > rows and xof or yof btn_size = width > rows and (3.5 + (xof * 2)) / width or (3.5 + (yof * 2)) / rows _btn_size = btn_size X = (btn_size * ((i - 1) % width) + XOFFSET - (sfinv_only and 2.83 or 0.5)) * (0.83 - (x_y / 5)) Y = (btn_size * floor((i - 1) / width) + (sfinv_only and 5.81 or 5.5) + x_y) * (0.86 - (x_y / 5)) end if X > rightest then rightest = X end local groups if is_group(name) then groups = extract_groups(name) item = groups_to_items(groups) end local label = groups and "\nG" or "" local replace if replacements then for j = 1, #replacements do local replacement = replacements[j] if replacement[1] == name then label = (label ~= "" and "\n" or "") .. label .. "\nR" replace = replacement[2] end end end if CORE_VERSION >= 510 and not large_recipe then fs[#fs + 1] = fmt(FMT.image, X, Y + (sfinv_only and 0.7 or 0), btn_size, btn_size, PNG.selected) end fs[#fs + 1] = fmt(FMT.item_image_button, X, Y + (sfinv_only and 0.7 or 0), btn_size, btn_size, item, item, ESC(label)) local infos = { unknown = not reg_items[name] or nil, groups = groups, burntime = fuel_cache[name], cooktime = cooktime, replace = replace, } if next(infos) then fs[#fs + 1] = get_tooltip(item, infos) end end if large_recipe then fs[#fs + 1] = "style_type[item_image_button;border=false]" end get_output_fs(fs, { recipe = rcp, shapeless = shapeless, rightest = rightest, btn_size = btn_size, _btn_size = _btn_size, spacing = spacing, rarity = rarity, }) end local function get_panels(data, fs) local start_y = CORE_VERSION >= 520 and 0 or 0.33 local panels = { {dat = data.usages or {}, height = 3.5}, {dat = data.recipes or {}, height = 3.5}, } if CORE_VERSION >= 520 and not sfinv_only then panels.favs = {dat = {}, height = 2.19} elseif sfinv_only then panels = data.show_usages and {{dat = data.usages}} or {{dat = data.recipes}} end for k, v in pairs(panels) do start_y = start_y + 1 local spacing = (start_y - 1) * 3.6 if not sfinv_only then fs[#fs + 1] = CORE_VERSION >= 510 and fmt("background9[8.1,%f;6.6,%f;%s;false;%d]", -0.2 + spacing, v.height, PNG.bg_full, 10) or fmt("background[8.1,%f;6.6,%f;%s;false]", -0.2 + spacing, v.height, PNG.bg_full) if k ~= "favs" then local fav = is_fav(data) local nfavs = #data.favs fs[#fs + 1] = fmt( "style[fav;fgimg=%s;fgimg_hovered=%s;fgimg_pressed=%s]", fmt("craftguide_fav%s.png", fav and "" or "_off"), fmt("craftguide_fav%s.png", fav and "_off" or ""), fmt("craftguide_fav%s.png", fav and "_off" or "")) if nfavs < 6 or (nfavs >= 6 and fav) then fs[#fs + 1] = fmt(FMT.image_button, 14, spacing, 0.5, 0.45, "", "fav", "") end fs[#fs + 1] = fmt("tooltip[fav;%s]", fav and ES"Unmark this item" or ES"Mark this item") end end local rn = #v.dat local _rn = tostring(rn) local xu = tostring(data.unum) .. _rn local xr = tostring(data.rnum) .. _rn xu = max(-0.3, -((#xu - 3) * 0.05)) xr = max(-0.3, -((#xr - 3) * 0.05)) local is_recipe = k == 2 local lbl if not sfinv_only and rn == 0 then lbl = clr("#f00", is_recipe and ES"No recipes" or ES"No usages") elseif (not sfinv_only and is_recipe) or (sfinv_only and not data.show_usages) then lbl = ES("Recipe @1 of @2", data.rnum, rn) elseif not sfinv_only or (sfinv_only and data.show_usages) then lbl = ES("Usage @1 of @2", data.unum, rn) elseif sfinv_only then lbl = data.show_usages and ES("Usage @1 of @2", data.unum, rn) or ES("Recipe @1 of @2", data.rnum, rn) end fs[#fs + 1] = fmt(FMT.label, XOFFSET + (sfinv_only and 2.3 or 1.6) + (is_recipe and xr or xu), YOFFSET + (sfinv_only and 2.3 or 1.5 + spacing), lbl) if rn > 1 then local btn_suffix = is_recipe and "recipe" or "usage" local prev_name = fmt("prev_%s", btn_suffix) local next_name = fmt("next_%s", btn_suffix) local x_arrow = XOFFSET + (sfinv_only and 1.7 or 1) local y_arrow = YOFFSET + (sfinv_only and 2.2 or 1.4 + spacing) if CORE_VERSION >= 520 then fs[#fs + 1] = fmt(mul_elem(FMT.arrow, 2), x_arrow + (is_recipe and xr or xu), y_arrow, PNG.prev, prev_name, "", x_arrow + 1.8, y_arrow, PNG.next, next_name, "") else fs[#fs + 1] = fmt(mul_elem(FMT.arrow, 2), x_arrow + (is_recipe and xr or xu), y_arrow, PNG.prev, prev_name, PNG.prev_hover, x_arrow + 1.8, y_arrow, PNG.next, next_name, PNG.next_hover) end end local rcp = is_recipe and v.dat[data.rnum] or v.dat[data.unum] if rcp then get_grid_fs(fs, rcp, spacing) end if k == "favs" and CORE_VERSION >= 520 and not sfinv_only then fs[#fs + 1] = fmt(FMT.label, 8.3, spacing - 0.1, ES"Bookmarks") for i = 1, #data.favs do local item = data.favs[i] if data.query_item == item then fs[#fs + 1] = fmt(FMT.image, 7.85 + (i - 0.5), spacing + 0.45, 1.1, 1.1, PNG.selected) end fs[#fs + 1] = fmt(FMT.item_image_button, 7.85 + (i - 0.5), spacing + 0.45, 1.1, 1.1, item, item, "") end end end end local function make_formspec(name) local data = pdata[name] local fs = {} fs[#fs + 1] = fmt([[ size[%f,%f] no_prepend[] bgcolor[#0000] ]], ROWS + (data.query_item and 6.7 or 0) - 1.2, LINES - 0.3) if not sfinv_only then fs[#fs + 1] = CORE_VERSION >= 510 and fmt("background9[-0.15,-0.2;%f,%f;%s;false;%d]", ROWS - 0.9, LINES + 0.4, PNG.bg_full, 10) or fmt("background[-0.15,-0.2;%f,%f;%s;false]", ROWS - 0.9, LINES + 0.4, PNG.bg_full) end fs[#fs + 1] = fmt([[ field[0.25,0.2;%f,1;filter;;%s] field_close_on_enter[filter;false] ]], sfinv_only and 2.76 or 2.72, ESC(data.filter)) if CORE_VERSION >= 520 then fs[#fs + 1] = fmt([[ style_type[image_button;border=false] style_type[item_image_button;border=false;bgimg_hovered=%s;bgimg_pressed=%s] style[search;fgimg=%s;fgimg_hovered=%s] style[clear;fgimg=%s;fgimg_hovered=%s] style[prev_page;fgimg=%s;fgimg_hovered=%s;fgimg_pressed=%s] style[next_page;fgimg=%s;fgimg_hovered=%s;fgimg_pressed=%s] ]], PNG.selected, PNG.selected, PNG.search, PNG.search_hover, PNG.clear, PNG.clear_hover, PNG.prev, PNG.prev_hover, PNG.prev_hover, PNG.next, PNG.next_hover, PNG.next_hover) fs[#fs + 1] = fmt(mul_elem(FMT.image_button, 4), sfinv_only and 2.6 or 2.54, -0.06, 0.85, 0.85, "", "search", "", sfinv_only and 3.3 or 3.25, -0.06, 0.85, 0.85, "", "clear", "", sfinv_only and 5.45 or (ROWS * 6.83) / 11, -0.06, 0.85, 0.85, "", "prev_page", "", sfinv_only and 7.2 or (ROWS * 8.75) / 11, -0.06, 0.85, 0.85, "", "next_page", "") else fs[#fs + 1] = fmt([[ image_button[%f,-0.06;0.85,0.85;%s;search;;;false;%s] image_button[%f,-0.06;0.85,0.85;%s;clear;;;false;%s] ]], sfinv_only and 2.6 or 2.54, PNG.search, PNG.search_hover, sfinv_only and 3.3 or 3.25, PNG.clear, PNG.clear_hover) fs[#fs + 1] = fmt(mul_elem(FMT.arrow, 2), sfinv_only and 5.45 or (ROWS * 6.83) / 11, -0.05, PNG.prev, "prev_page", PNG.prev, sfinv_only and 7.2 or (ROWS * 8.75) / 11, -0.05, PNG.next, "next_page", PNG.next) end data.pagemax = max(1, ceil(#data.items / IPP)) fs[#fs + 1] = fmt("label[%f,%f;%s / %u]", sfinv_only and 6.35 or (ROWS * 7.85) / 11, 0.06, clr("#ff0", data.pagenum), data.pagemax) if #data.items == 0 then local no_item = S"No item to show" local pos = ROWS / 3 if next(recipe_filters) and #init_items > 0 and data.filter == "" then no_item = S"Collect items to reveal more recipes" pos = pos - 1 end fs[#fs + 1] = fmt(FMT.label, pos, 2, ESC(no_item)) end local first_item = (data.pagenum - 1) * IPP for i = first_item, first_item + IPP - 1 do local item = data.items[i + 1] if not item then break end local X = i % ROWS local Y = (i % IPP - X) / ROWS + 1 if CORE_VERSION >= 510 and data.query_item == item then fs[#fs + 1] = fmt(FMT.image, X - (X * (sfinv_only and 0.12 or 0.14)) - 0.05, Y - (Y * 0.1) - 0.1, 1, 1, PNG.selected) end fs[#fs + 1] = fmt("item_image_button[%f,%f;%f,%f;%s;%s_inv;]", X - (X * (sfinv_only and 0.12 or 0.14)) - 0.05, Y - (Y * 0.1) - 0.1, 1, 1, item, item) end if (data.recipes and #data.recipes > 0) or (data.usages and #data.usages > 0) then get_panels(data, fs) end return concat(fs) end local show_fs = function(player, name) if sfinv_only then sfinv.set_player_inventory_formspec(player) else show_formspec(name, "craftguide", make_formspec(name)) end end craftguide.register_craft_type("digging", { description = ES"Digging", icon = "default_tool_steelpick.png", }) craftguide.register_craft_type("digging_chance", { description = ES"Digging Chance", icon = "default_tool_mesepick.png", }) local function sfind(str, filter) if filter == "" then return 0 end if find(str, filter, 1, true) then return #str - #filter end end local function search(data) local filter = data.filter if searches[filter] then data.items = searches[filter] return 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, order, c = {}, {}, 0 for i = 1, #data.items_raw do local item = data.items_raw[i] local def = reg_items[item] local desc = (def and def.description) and lower(def.description) or "" local to_add if search_filter then for filter_name, values in pairs(filters) do local func = search_filters[filter_name] to_add = func(item, values) and (search_filter == "" or (sfind(item, search_filter) or sfind(desc, search_filter))) end else to_add = sfind(item, filter) or sfind(desc, filter) end if to_add then c = c + 1 filtered_list[c] = item order[item] = to_add end end if not next(recipe_filters) then -- Cache the results only if searched 2 times if searches[filter] == nil then searches[filter] = false else searches[filter] = filtered_list end end sort(filtered_list, function(a, b) return order[a] < order[b] end) data.items = filtered_list end craftguide.add_search_filter("groups", function(item, groups) local def = reg_items[item] local has_groups = true for i = 1, #groups do local group = groups[i] if not def.groups[group] then has_groups = nil break end end return has_groups end) --[[ As `core.get_craft_recipe` and `core.get_all_craft_recipes` do not return the replacements and toolrepair, we have to override `core.register_craft` and do some reverse engineering. See engine's issues #4901 and #8920. ]] fuel_cache.replacements = {} local old_register_craft = core.register_craft core.register_craft = function(def) old_register_craft(def) if def.type == "toolrepair" then toolrepair = def.additional_wear * -100 end local output = def.output or (true_str(def.recipe) and def.recipe) or nil if not output then return end output = {match(output, "%S+")} local groups if is_group(output[1]) then groups = extract_groups(output[1]) output = groups_to_items(groups, true) end for i = 1, #output do local name = output[i] if def.type ~= "fuel" then def.items = {} end if def.type == "fuel" then fuel_cache[name] = def.burntime fuel_cache.replacements[name] = def.replacements elseif def.type == "cooking" then def.width = def.cooktime def.cooktime = nil def.items[1] = def.recipe elseif def.type == "shapeless" then def.width = 0 for j = 1, #def.recipe do def.items[#def.items + 1] = def.recipe[j] end else def.width = #def.recipe[1] local c = 0 for j = 1, #def.recipe do if def.recipe[j] then for h = 1, def.width do c = c + 1 local it = def.recipe[j][h] if it and it ~= "" then def.items[c] = it end end end end end if def.type ~= "fuel" then def.recipe = nil recipes_cache[name] = recipes_cache[name] or {} insert(recipes_cache[name], 1, def) end end end local old_clear_craft = core.clear_craft core.clear_craft = function(def) old_clear_craft(def) if true_str(def) then def = match(def, "%S*") recipes_cache[def] = nil fuel_cache[def] = nil elseif is_table(def) then return -- TODO end end local function handle_drops_table(name, drop) -- Code borrowed and modified from unified_inventory -- https://github.com/minetest-mods/unified_inventory/blob/master/api.lua local drop_sure, drop_maybe = {}, {} local drop_items = drop.items or {} local max_items_left = drop.max_items local max_start = true for i = 1, #drop_items do if max_items_left and max_items_left <= 0 then break end local di = drop_items[i] for j = 1, #di.items do local dstack = ItemStack(di.items[j]) local dname = dstack:get_name() if not dstack:is_empty() and dname ~= name then local dcount = dstack:get_count() if #di.items == 1 and di.rarity == 1 and max_start then if not drop_sure[dname] then drop_sure[dname] = 0 end drop_sure[dname] = drop_sure[dname] + dcount if max_items_left then max_items_left = max_items_left - 1 if max_items_left <= 0 then break end end else if max_items_left then max_start = false end if not drop_maybe[dname] then drop_maybe[dname] = {} end 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 for item, count in pairs(drop_sure) do craftguide.register_craft{ type = "digging", items = {name}, output = fmt("%s %u", item, count), } end for item, data in pairs(drop_maybe) do craftguide.register_craft{ type = "digging_chance", items = {name}, output = fmt("%s %u", item, data.output), rarity = data.rarity, } end end local function register_drops(name, def) local drop = def.drop local dstack = ItemStack(drop) if not dstack:is_empty() and dstack:get_name() ~= name then craftguide.register_craft{ type = "digging", items = {name}, output = drop, } elseif is_table(drop) then handle_drops_table(name, drop) end end local function handle_aliases(hash) for oldname, newname in pairs(reg_aliases) do cache_recipes(oldname) local recipes = recipes_cache[oldname] if recipes then if not recipes_cache[newname] then recipes_cache[newname] = {} end local similar for i = 1, #recipes_cache[oldname] do local rcp_old = recipes_cache[oldname][i] for j = 1, #recipes_cache[newname] do local rcp_new = recipes_cache[newname][j] rcp_new.type = nil rcp_new.method = nil if table_eq(rcp_old, rcp_new) then similar = true break end end if not similar then insert(recipes_cache[newname], rcp_old) end end end if newname ~= "" and recipes_cache[oldname] and not hash[newname] then init_items[#init_items + 1] = newname end end end local function show_item(def) return not (def.groups.not_in_craft_guide == 1 or def.groups.not_in_creative_inventory == 1) and def.description and def.description ~= "" end local function tablelen(t) local c = 0 for _ in pairs(t) do c = c + 1 end return c end local function get_init_items() local ic, it, last_str = 0, tablelen(reg_items), "" local hash = {} local function iop(str) io.write(("\b \b"):rep(#last_str)) io.write(str) io.flush() last_str = str end local full_char, empty_char = "▰", "▱" for name, def in pairs(reg_items) do ic = ic + 1 local percent, bar, len = (ic * 100) / it, "", 20 for i = 1, len do bar = bar .. (i <= percent / (100 / len) and full_char or empty_char) end iop(fmt("[craftguide] Caching data %s %u%%\r", bar, percent)) if show_item(def) then if not fuel_cache[name] then cache_fuel(name) end if not recipes_cache[name] then cache_recipes(name) end cache_usages(name) register_drops(name, def) if name ~= "" and recipes_cache[name] or usages_cache[name] then init_items[#init_items + 1] = name hash[name] = true end end end handle_aliases(hash) sort(init_items) if http and true_str(craftguide.export_url) then local post_data = { recipes = recipes_cache, usages = usages_cache, fuel = fuel_cache, } http.fetch_async{ url = craftguide.export_url, post_data = write_json(post_data), } end print() end local function init_data(name) local items = CORE_VERSION >= 500 and init_items or (#init_items == 0 and get_init_items() or init_items) pdata[name] = { filter = "", pagenum = 1, items = items, items_raw = items, favs = {}, } end local function reset_data(data) data.filter = "" data.pagenum = 1 data.rnum = 1 data.unum = 1 data.query_item = nil data.recipes = nil data.usages = nil data.show_usages = nil data.items = data.items_raw end if CORE_VERSION >= 500 then on_mods_loaded(get_init_items) end on_joinplayer(function(player) local name = player:get_player_name() init_data(name) end) local function fields(player, _f) local name = player:get_player_name() local data = pdata[name] if _f.clear then reset_data(data) return true, show_fs(player, name) elseif _f.prev_recipe or _f.next_recipe then local num = data.rnum + (_f.prev_recipe and -1 or 1) data.rnum = data.recipes[num] and num or (_f.prev_recipe and #data.recipes or 1) return true, show_fs(player, name) elseif _f.prev_usage or _f.next_usage then local num = data.unum + (_f.prev_usage and -1 or 1) data.unum = data.usages[num] and num or (_f.prev_usage and #data.usages or 1) return true, show_fs(player, name) elseif (_f.key_enter_field == "filter" or _f.search) and _f.filter ~= "" then local str = lower(_f.filter) if data.filter == str then return end data.filter = str data.pagenum = 1 search(data) return true, show_fs(player, name) elseif _f.prev_page or _f.next_page then if data.pagemax == 1 then return end data.pagenum = data.pagenum - (_f.prev_page and 1 or -1) if data.pagenum > data.pagemax then data.pagenum = 1 elseif data.pagenum == 0 then data.pagenum = data.pagemax end return true, show_fs(player, name) elseif _f.fav then local fav, i = is_fav(data) local total = #data.favs if total < 6 and not fav then data.favs[total + 1] = data.query_item elseif fav then remove(data.favs, i) end return true, show_fs(player, name) else local item for field in pairs(_f) do if find(field, ":") then item = field break end end if not item then return elseif sub(item, -4) == "_inv" then item = sub(item, 1, -5) end item = reg_aliases[item] or item if sfinv_only then if item ~= data.query_item then data.show_usages = nil else data.show_usages = not data.show_usages end end local recipes, usages = get_recipes(item, data, player) if not recipes and not usages then return end data.query_item = item data.recipes = recipes data.usages = usages data.rnum = 1 data.unum = 1 return true, show_fs(player, name) end end if sfinv_only then sfinv.register_page("craftguide:craftguide", { title = S"Craft Guide", get = function(self, player, context) local name = player:get_player_name() local formspec = make_formspec(name) return sfinv.make_formspec(player, context, formspec) end, on_enter = function(self, player, context) if next(recipe_filters) then local name = player:get_player_name() local data = pdata[name] data.items_raw = get_filtered_items(player) search(data) end end, on_player_receive_fields = function(self, player, context, _f) fields(player, _f) end, }) else on_receive_fields(function(player, formname, _f) if formname == "craftguide" then fields(player, _f) end end) local function on_use(user) local name = user:get_player_name() if next(recipe_filters) then local data = pdata[name] data.items_raw = get_filtered_items(user) search(data) end show_formspec(name, "craftguide", make_formspec(name)) end core.register_craftitem("craftguide:book", { description = S"Crafting Guide", inventory_image = PNG.book, wield_image = PNG.book, stack_max = 1, groups = {book = 1}, on_use = function(itemstack, user) on_use(user) end }) core.register_node("craftguide:sign", { description = S"Crafting Guide Sign", drawtype = "nodebox", tiles = {PNG.sign}, inventory_image = PNG.sign, wield_image = PNG.sign, paramtype = "light", paramtype2 = "wallmounted", sunlight_propagates = true, groups = {oddly_breakable_by_hand = 1, flammable = 3}, node_box = { type = "wallmounted", wall_top = {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5}, wall_bottom = {-0.5, -0.5, -0.5, 0.5, -0.4375, 0.5}, wall_side = {-0.5, -0.5, -0.5, -0.4375, 0.5, 0.5} }, on_construct = function(pos) local meta = core.get_meta(pos) meta:set_string("infotext", "Crafting Guide Sign") end, on_rightclick = function(pos, node, user, itemstack) on_use(user) end }) core.register_craft{ output = "craftguide:book", type = "shapeless", recipe = {"default:book"} } core.register_craft{ type = "fuel", recipe = "craftguide:book", burntime = 3 } core.register_craft{ output = "craftguide:sign", type = "shapeless", recipe = {"default:sign_wall_wood"} } core.register_craft{ type = "fuel", recipe = "craftguide:sign", burntime = 10 } if rawget(_G, "sfinv_buttons") then sfinv_buttons.register_button("craftguide", { title = S"Crafting Guide", tooltip = S"Shows a list of available crafting recipes, cooking recipes and fuels", image = PNG.book, action = function(player) on_use(player) end, }) end end if progressive_mode then local PLAYERS = {} local POLL_FREQ = 0.25 local HUD_TIMER_MAX = 1.5 local function item_in_inv(item, inv_items) local inv_items_size = #inv_items if is_group(item) then local groups = extract_groups(item) for i = 1, inv_items_size do local def = reg_items[inv_items[i]] if def then local item_groups = def.groups if item_has_groups(item_groups, groups) then return true end end end else for i = 1, inv_items_size do if inv_items[i] == item then return true end end end end local function recipe_in_inv(recipe, inv_items) for _, item in pairs(recipe.items) do if not item_in_inv(item, inv_items) then return end end return true end local function progressive_filter(recipes, player) if not recipes then return {} end local name = player:get_player_name() local data = pdata[name] if #data.inv_items == 0 then return {} end local filtered, c = {}, 0 for i = 1, #recipes do local recipe = recipes[i] if recipe_in_inv(recipe, data.inv_items) then c = c + 1 filtered[c] = recipe end end return filtered end local item_lists = { "main", "craft", "craftpreview", } 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) -- It'd better to have an engine function `hud_move` to only need -- 2 calls for the notification's back and forth. local hud_info_bg = player:hud_get(data.hud.bg) local dt = 0.016 if hud_info_bg.position.y <= 0.9 then data.show_hud = false data.hud_timer = (data.hud_timer or 0) + dt 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 - (dt / 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 >= HUD_TIMER_MAX 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 + (dt / 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 of -- the player inventory changed, instead. local function poll_new_items() for i = 1, #PLAYERS do local player = PLAYERS[i] local name = player:get_player_name() local data = pdata[name] local inv_items = get_inv_items(player) local diff = array_diff(inv_items, data.inv_items) if #diff > 0 then data.inv_items = table_merge(diff, data.inv_items) local oldknown = data.known_recipes or 0 local items = 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 if sfinv_only then data.items_raw = items search(data) sfinv.set_player_inventory_formspec(player) end end end after(POLL_FREQ, poll_new_items) end poll_new_items() globalstep(function() 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) end end end) craftguide.add_recipe_filter("Default progressive filter", progressive_filter) on_joinplayer(function(player) PLAYERS = get_players() local name = player:get_player_name() local data = pdata[name] if CORE_VERSION >= 500 then local meta = player:get_meta() data.inv_items = dslz(meta:get_string("inv_items")) or {} data.known_recipes = dslz(meta:get_string("known_recipes")) or 0 else data.inv_items = dslz(player:get_attribute("inv_items")) or {} data.known_recipes = dslz(player:get_attribute("known_recipes")) or 0 end data.hud = { bg = player:hud_add{ hud_elem_type = "image", position = {x = 0.78, y = 1}, alignment = {x = 1, y = 1}, scale = {x = 370, y = 112}, text = PNG.bg, }, book = player:hud_add{ hud_elem_type = "image", position = {x = 0.79, y = 1.02}, alignment = {x = 1, y = 1}, scale = {x = 4, y = 4}, text = PNG.book, }, text = player:hud_add{ hud_elem_type = "text", position = {x = 0.84, y = 1.04}, alignment = {x = 1, y = 1}, number = 0xfff, text = "", }, } end) local to_save = { "inv_items", "known_recipes", } local function save_meta(player) local meta local name = player:get_player_name() local data = pdata[name] if CORE_VERSION >= 500 then meta = player:get_meta() end for i = 1, #to_save do local meta_name = to_save[i] if CORE_VERSION >= 500 then meta:set_string(meta_name, slz(data[meta_name])) else player:set_attribute(meta_name, slz(data[meta_name])) end end end on_leaveplayer(function(player) PLAYERS = get_players() save_meta(player) end) on_shutdown(function() for i = 1, #PLAYERS do local player = PLAYERS[i] save_meta(player) end end) end on_leaveplayer(function(player) local name = player:get_player_name() pdata[name] = nil end) function craftguide.show(name, item, show_usages) if not true_str(name)then return err"craftguide.show(): player name missing" end local data = pdata[name] local player = get_player_by_name(name) local query_item = data.query_item reset_data(data) item = reg_items[item] and item or query_item local recipes, usages = get_recipes(item, data, player) if not recipes and not usages then if not recipes_cache[item] and not usages_cache[item] then return false, msg(name, fmt("%s: %s", S"No recipe or usage for this item", get_desc(item))) end return false, msg(name, fmt("%s: %s", S"You don't know a recipe or usage for this item", get_desc(item))) end data.query_item = item data.recipes = recipes data.usages = usages if sfinv_only then data.show_usages = show_usages end show_fs(player, name) end register_command("craft", { description = S"Show recipe(s) of the pointed node", func = function(name) local player = get_player_by_name(name) local dir = player:get_look_dir() local ppos = player:get_pos() ppos.y = ppos.y + 1.625 local node_name for i = 1, 10 do local look_at = vec_add(ppos, vec_mul(dir, i)) local node = core.get_node(look_at) if node.name ~= "air" then node_name = node.name break end end if not node_name then return false, msg(name, S"No node pointed") end return true, craftguide.show(name, node_name) end, }) craftguide-1.11/license.txt000066400000000000000000000050231360474225700157460ustar00rootroot00000000000000License of source code ---------------------- The MIT License (MIT) Copyright (c) 2015-2020 Jean-Patrick Guerrero and contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Licenses of media (textures) ---------------------------- Copyright © Diego Martínez (kaeza): craftguide_*_icon.png (CC BY-SA 3.0) You are free to: Share — copy and redistribute the material in any medium or format. Adapt — remix, transform, and build upon the material for any purpose, even commercially. The licensor cannot revoke these freedoms as long as you follow the license terms. Under the following terms: Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original. No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. Notices: You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation. No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material. For more details: http://creativecommons.org/licenses/by-sa/3.0/ craftguide-1.11/locale/000077500000000000000000000000001360474225700150225ustar00rootroot00000000000000craftguide-1.11/locale/craftguide.de.tr000066400000000000000000000014511360474225700200760ustar00rootroot00000000000000# textdomain: craftguide Craft Guide=Rezeptbuch Crafting Guide=Rezeptbuch Crafting Guide Sign=Rezepttafel Usage @1 of @2=Verwendung @1 von @2 Recipe @1 of @2=Rezept @1 von @2 Burning time: @1=Brennzeit: @1 Cooking time: @1=Kochzeit: @1 Any item belonging to the group(s): @1=Beliebiger Gegenstand aus Gruppe(n): @1 Recipe's too big to be displayed (@1x@2)=Rezept ist zu groß für die Anzeige (@1×@2) Shapeless=Formlos Cooking=Kochen No item to show=Nichts anzuzeigen Collect items to reveal more recipes=Gegenstände aufsammeln, um mehr Rezepte aufzudecken Show recipe(s) of the pointed node=Rezept(e) des gezeigten Blocks anzeigen No node pointed=Auf keinen Block gezeigt You don't know a recipe for this node=Sie kennen kein Rezept für diesen Block No recipe for this node=Kein Rezept für diesen Block craftguide-1.11/locale/craftguide.fr.tr000066400000000000000000000020431360474225700201130ustar00rootroot00000000000000# textdomain: craftguide Craft Guide=Guide de recettes Crafting Guide=Guide de recettes Usage @1 of @2=Usage @1 de @2 Recipe @1 of @2=Recette @1 de @2 Burning time: @1=Temps de combustion : @1 Cooking time: @1=Temps de cuisson : @1 Replaced by @1 on smelting=Remplacé par @1 lors de la cuisson Replaced by @1 on burning=Remplacé par @1 lors de la combustion Replaced by @1 on crafting=Remplacé par @1 lors de la fabrication Repairable by step of @1=Réparable par étape de @1 Any item belonging to the group(s): @1=Tout item appartenant au(x) groupe(s) : @1 Recipe's too big to be displayed (@1x@2)=La recette est trop grande pour être affichée (@1x@2) Shapeless=Sans forme Cooking=Cuisson No item to show=Aucun item à afficher Collect items to reveal more recipes=Collecte des items pour révéler plus de recettes Show recipe(s) of the pointed node=Affiche les recettes du bloc visé No node pointed=Aucun bloc visé You don't know a recipe for this node=Tu ne connais aucune recette pour ce bloc No recipe for this node=Aucune recette pour ce bloc craftguide-1.11/locale/craftguide.it.tr000066400000000000000000000022771360474225700201310ustar00rootroot00000000000000# textdomain: craftguide Craft Guide=Guida di assemblaggio Crafting Guide=Guida d'assemblaggio Crafting Guide Sign=Cartello della guida d'assemblaggio Usage @1 of @2=Utilizzo @1 di @2 Recipe @1 of @2=Ricetta @1 di @2 Burning time: @1=Tempo di bruciatura: @1 Cooking time: @1=Tempo di cottura: @1 Replaced by @1 on smelting=Sostituito da @1 alla fusione Replaced by @1 on burning=Sostituito da @1 alla bruciatura Replaced by @1 on crafting=Sostituito da @1 all'assemblaggio Repairable by step of @1=Riparabile per passo di @1 Any item belonging to the group(s): @1=Qualunque oggetto appartenente al gruppo: @1 Recipe's too big to be displayed (@1x@2)=La ricetta è troppo grande per essere mostrata (@1x@2) Shapeless=Senza forma Cooking=Cottura No item to show=Nessun oggetto da mostrare Collect items to reveal more recipes=Raccogli oggetti per svelare più ricette Show recipe(s) of the pointed node=Mostra la ricetta del nodo puntato No node pointed=Nessun nodo puntato You don't know a recipe for this node=Non conosci una ricetta per questo nodo No recipe for this node=Nessuna ricetta per questo nodo Digging=Scavando Digging Chance=Probabilità di scavare @1 of chance to drop=@1 di probabilità di rilascio craftguide-1.11/locale/craftguide.ru.tr000066400000000000000000000022241360474225700201330ustar00rootroot00000000000000# textdomain: craftguide Craft Guide=книга рецептов крафта Crafting Guide=книга рецептов крафта Crafting Guide Sign=Знак с книгой рецептов Usage @1 of @2=использование @1 из @2 Recipe @1 of @2=Рецепт @1 из @2 Burning time: @1=Время горения: @1 Cooking time: @1=Время преготовления: @1 Any item belonging to the group(s): @1=Любой элемент из группы: @1 Recipe's too big to be displayed (@1x@2)=Рецепт слишком большой для показа (@1x@2) Shapeless=Бесформенный Cooking=Приготовление No item to show=Нет элемента для показа Collect items to reveal more recipes=Собирайте предметы, чтобы раскрыть больше рецептов Show recipe(s) of the pointed node=Показать рецепт(ы) выбранной ноды No node pointed=Не указана нода You don't know a recipe for this node=Вы не знаете рецепт для этой ноды No recipe for this node=Нет рецептов для этой ноды craftguide-1.11/locale/template000066400000000000000000000011501360474225700165550ustar00rootroot00000000000000# textdomain: craftguide Craft Guide= Crafting Guide= Crafting Guide Sign= Usage @1 of @2= Recipe @1 of @2= No recipes= No usages= Burning time: @1= Cooking time: @1= Replaced by @1 on smelting= Replaced by @1 on burning= Replaced by @1 on crafting= Repairable by step of @1= Any item belonging to the group(s): @1= Recipe's too big to be displayed (@1x@2)= Shapeless= Cooking= No item to show= Collect items to reveal more recipes= Show recipe(s) of the pointed node= No node pointed= You don't know a recipe or usage for this item= No recipe or usage for this item= Digging= Digging Chance= @1 of chance to drop= craftguide-1.11/mod.conf000066400000000000000000000001721360474225700152110ustar00rootroot00000000000000name = craftguide optional_depends = sfinv, sfinv_buttons description = The most comprehensive Crafting Guide on Minetest craftguide-1.11/screenshot.png000066400000000000000000001057751360474225700164650ustar00rootroot00000000000000PNG  IHDR,ݽKgAMA a cHRMz&u0`:pQ<bKGDtIME IDATxgxu~ L':;%JTeYD(8qrGϏ\ۉ8VNHαlEeYPD! fۇmul#83>`8s0[ֻ^k+_ Q,2~?<_/sVc`f~?.ѥֺzz:" l'>/‡>$I.c, oqqqaaAqC1Ls>>>826%/ę: ls;x#G<ϻ5Ƙ/*gi sf18ph¶>ޅI-oxbݾBRNvI5q3פ@@+|"JQ䤚XԮ`?­ /n͎d;w;z%H";y~onaZ=tHf 1qy&`/m4:2=|Ziz"imK7P\4ư\NknSۯ8m3c3<9{=:}:M+.ֺ\.wpaj\w]qs{?q*5B܅/'?9{_y?~oٟyw|(\V .0 &vf|Eq;?O|k.j=w}'"!Ν;3ֺFN!:*&52ҧ>+.eGyd`````@)ucNG)gϞ4M{ $jX>d5j3qr8sssG83fff8170"tW5 ^89ytx)>Ƙ o߿?cdt9FPQ,]|GEGup_ o$~ (;~al-{qi-܁ n٧'FcsoZ1ht$D!Hҷ/f<>\4:/8K*فKeiJL{`?T4Cx٣:ڷ] nl&34ux1l6jO>F+=ı`C$27 0=3Ȋ#](MB5u4s/a݃"Utw78};/NO)̡,g-&tq]o׍?ZI@F)>Y4{Ԍ(8:;oʥ0b cdYCs"=~T<9=_x~wL,%"K0CDcEz-‚Yu=L|*I yo_u pNI!>Hd=w(=7m:k 7`MB A}Iop.}z[;|2K,Ŏ[:שn_CRklίBP8&Z5UɉȐY&:F)͞Wanv֛лkra1]\Oa8鿤;V>xK_Eohk;~$~|n=+1"hBB)\VČَJ+x)n]&r&@A#HR~q1䞷t_!2]H#NL|9W` !2`ЬMg= b3!L !wLi@k= 5/}^Oq`sy<=L8s?;v Gc0IڍZ^ -8DT3Γi.1b!܁bYh/%Noy |t|5e6\ Qݠf;\7gNJbߞAa];Hx6X&dj4=-|˹7\6Bb2:_=:{?E3KOi&h&[7\ If8Ȑ3 LP puu?`Ç$ f\BŸf9,$Yum#@C8K髈m.8LS$qb.KR :DH4^h1(kuhuodggN~THf4*F.v_Z2ö'2m,N r.zip{r&`3c_~ٳξ%?-MNCE66+TM-Oco zX,DE6_zTGR}Ο_ۭAغp;~suToq[}՛:E^@K$Z@nv} c]@W=lJ@"'ü#ȴ;(IErH`U,u_}nF1/`MG/83Z"'ˣ#hC*9d%xOY h gBҜ=}zi J|?ޛe@ڊG>q@˨GlB,r%:i`.N5(_ML(<)Tވ9,=y sSr cG}^lT)* q|o4p':N6.ڇ@`r{ !R!nr#3938\-ylG5;z3i$$yBU:HxJBwTșDy䮺cdK z8 I [9o2j;ՊpۏjygŚq=Ǟ;RޱC|fo}'j`(f͹icC1id 2MJdi# 9SD &Ly0R>[G ,u$ShD&H8(|v^s\INw ށźV~s=e+q _aF6*1Z$y.c,5w?X?Ρ#NO_p=L]<ϟ2Fo|bsmVr flCj1׆5scei$Ğ e+{vd6 Y湘=W)Epe8In0=ݮ\aB|֖z7i~I›y'7Bj%m4kSzo)juΞ<ݬ-%ї;w34#wQÎ_{z  Kq,RpucS,Pj+NOÖOBeȂ''9\v,qzr5ZdLʰ#LkoG1zq^Kd-QNiw|gK=_+ԓ7dw 0M/:'8gO UZk1! +q)KfR)ԕկج 48s9%2e&)2.{S !I#b`qIK0Q)P+kRBSL~ }c@BVs#L6?/=D6p@+AWn > c i)e g)NEK㻌ͺ0syp;yޛ;?- B&`ETs ZR6ra(p75}>=3'uic'-7|,[mgP\pnfn^s89( #*mKՎl 1pYچ elÓy5"^ I?]N?3Y؋d=1=3%xk AYuixI2q /-TU9@*qzWCPL\i7+F]3FlR- _ @2et 9$%kurҬ7$e('խ̙cOŗxiܞ:2.U#Xh+~/Dݹkt8o4JX,m:?q܇X,syxcrꆟw=8 7~}VKR-/}s"E@辂b_R*'L*躚}Hg!3gR2n @z l8݋pFILR/KYpMPB\S\"T"h_:?% 2N^?8&:\Wnl3"@УWJ[4L œщf02F+5ߟS9Z1O8=zn̏:ѩZMR&~{t9oa\KKhu;ލ|\QsCI2^+3|w m;6ӳc<-ղqK{hKEagKdѣG9?4mĹby+M2B!3''L4u#!hFČè/Mm~A_E_;t+4 ;8GH l,͟xT#йaS&Y> H$ls=q(Zl?>.!|;,¯)#T"9De*wY~^&bjP*Vt°l=^&8|{~&!cn#6Μ+Kbmpdx/cw?\]Ss2 f~nuv{rF0a˙}WSjHC1qv„!mUg3^+0> @y7o ı^z1D%E[5__`̀{k pPᆯ٬M)\ {nT%Lk͊iRQx.k"0 !b`YwWzMm@p4QOEv6 n?DŽrMA,x |>ߔ*p10X,?LFz3:K2I/8jޗ.Ur5'}p6lU" a99``&ZWMBd9O1uiӹac"@{c.V `&}^ks4cqR)gTҚ8Ɯp]M$@C@6iZ1_fEljǟ?F8BtRynqּ/>SІ8m{L1<]4hi/dz1_AնA0q1Ƙ4rYn2u{pȼyC!5|}lVu ۰ 17rK7>"GXwMMZZLqC{6Wx␽l8CYg+ qiI5"N7f6bP.Z>Dd2sAQ4& 5+:Φ/92Ez #"X6D]OTn4JRuADAn@D)eyV64f8 Vk-kOC4M;ΦCcu4MRWܖbppAsD7su;7 (N>}С+w-51\8&Gš=7Kp t<ѣJ\._,)###Rʍ@4j_< v]/Pk\B .:th j{Ykv`Z}衇6>q\=rH6B08f Dqfgg'&&v{ttڡ /+6t:"yĮ]M޸cŞ];S5Z=p>8I|!XZZzg7 &gf.4o[{L}2dEuPxYT0r{n0\?P29U `8B5+&&&^~;wпs J#v]Uz~,SGQ+i q5pESG\u[Z5k!3\~|yv|~6wű=y# MPF+xCe2jj0rٜT*7tԯ~-Jo-Bk63>>nS#5{̙RdŦ.--=zСCA(Ļ345DjG?^ɽVf=g @fZ=aWx~Ү_8C#x4n) ( tWxEo#)f8}g2\y%ҺVaW ǴJm s&+e*4M gx]Ϗ_;c Ϫ4~j v akqG)뺮"CCCϟWUq==gSWC=u/"&@O^wUX&''ۧBH)[y+٬24MvR 4%D$\ (EIjHsdV 7MS&=-] 2$pȞ܇qΡ8}nfn.}c 29/;SI?D\Wi +Sնʄȸ5&OvZӯSn/_>f^Ȏwî=7sèssͤM;fe`ƦJo%Dh6?S?U*FRJˁQn:[Q*$QJ AO| clQ<g36uHM7x;b<4vx&_:<<<33y^$aBH! ## ZA&{w+5=T;VO1!2NZΝzZ ~w`Tax܅B>kg_\70d3wnD\-ڡ,j-Q%n8wY[x#l)S)Յ)[uN-4j|MjD!wGNDل6(OO pF#c0VeTeY&_^w]wpp{뮻|g}P(\s&$BKϝ/m<}d2ЖXmA1\. !:NhZL&a\iZkZ-a@:ܙny .1( $.vϰ% >`"yEG-Ya'I.UJEIkm|>8>QZ!ěn1M?xxzxn WGlfمKv CC;Jvq́ |p' /^岎q<_[7:Ή{o8ZKR%乑C J\.?آl6غlu]vL$vdY;?ȑL&%l 2qvf^1t%.ڬD&-H(|B[@gJFamƘuu{JqU`@`";U @B FvI![;38PʼɾjR.;N'Z[8J\&GcgR O={N$N5>ȉg<7!䲽4?1:ٓ~8_rggf:掁~/-d*%b]c/B! N'nw49oЎ 0 %_._(6D;bY&zaLؽ@k$Z yt45ƴ;vp@6p…7ZVJR?a^c 9nmtA C3:$.2"@E"h9aB߼_:Iv 9\UV_}{ Bȸ_]_#*x]_1.:R66`;r {2{&KD{DSl$1$25&}J#8\Fs9Gw䂍mB]Jl6#GhP(iڈ6qff\*`mHL32!^D fb)vc`QJʔd,68fNºL{X{Ā>9"Jgyftt7te{!~>G mn`VϜws AtL@81sM34 ;D.ϒ$DNm!/8 vN^XJur~&X 3hs|znVU<}Tup9|p oس c1Z0R& 2KsbVd:a|IedSE/\t]oxS}LBՆuC knJn&W$Aa /ݻ^APV(BXi6vWfqq gcLk6gDDƠ/hu3D)_~#!pp6.c# W;ޖjm繮-X Vt.lDM9n[ߘٳG)588h_mKC ڪc+J'j$jU)%hEq!s4pgur! 4e_ޑjё&J&gʸx͹?z,UɗfDy./~a}CE#㤽bBMU+x>eP ~HsRT8shFF'Bع爁jF:|C&*%|T\7:(I[jd+lO?}7lnKE{Z-+6ﺮM^PDz9G"91fs #Jbvx^q޸Glk+FcĹ76wU]kEQBnbO:Z c:Iv](*Lzl~Ej4CCC7Z:ZTsN+5] M+]>\.4)#A Re XkZoeȹ]xfg_O5LO^qgKO7;ly *nxL=5ݧζ;iAD\:btԜziud >51>5Vg "W)B0P)28n.}s!cyC 4Jຮu \p7tSƳxuj%6u: ZЍy:u |(oJre]["8>n@#v݅8戡} rn m}VX;MJI 6+mރ`Wq[(8 K)mrRZB{j;N(* cL 8HL^[m*KH*p!`m%i>CzdV/{}B.KS) pA{Ptcb/fG Z \L.&O~3Ӌ2Vu1k%R=;?D;0 = <{|Zi=9h1Xwr>˄͖dS_láJ=>awL+-Cf ! "Z :y^Oc>{Nj--{p"Uٹ\|ΗObFԘĘ.:qgEw YfYY?ƺaE܉njB!"4Rdu`"Fk]cj48 1XhE=T`GL'*E  @ `3"C78;Q;;YWR 2GpxbsژJ }pDl&q315-5R2X*Lcգ<# RVM.y0o'j4O2s6e34Fj=⺅ K'#._@Ď֧°{ p>+Phs@K) }9+}lV[Wv%%DQچ Lƪ6;4cu=ϳVdX-G TT3 'CHG js"2AmwTQK3l4Wk0:#U.L/EttXʰRȅ8O4>V *o:xd; 7FX77qѳsgϷ%c1RʹEmLcAa b3;zfCTJDHq qP$N7y"/AVr iۯ&o/3?[:rF>7Ez,Ǟ81V5,.R>(rxd٬a! c7yEQo}ݪy{Qvxl6;c}W'RJ3fV\e 3yw@)P#G +7 Klc.l`1kZqk<ϲE$fv:nC*žoVڞNviz\faX.ieaݶp0 0Ɨs$qE^Qe)W |? 4 7#C}Lc/Nf<>X f`3SV<Uo='\^`Zz#I%z=d cռB>9a,tTbRvlУfMiDwlf*}}l668VUo93ۛҵ~ˍ`ِ F\N\C&+k]DM$X?=! {Yc e p%(l$D+7\VZ~NBxy@bSMQfdfmNL>Ql6kǑk8#P1XDB$`4h8AL$L6DCd<9<"2R\o{A#.ԓ-!犗N-pٿ3;vwy_YSLa0e%? @7D\BJݗw8p~CfF+ZVxbMuy~#v3W!)%#Rsw{ZDgY>a$A:77r{ǬiuT+mvA1*rbK)CTtT∑O6hID-:8!t&fe"brC m&rT6olS^YЦclvׅfY݆pmZ-6dVtvW$MS%" \f !I\R10:&̦  0RʨdQûON~5w1*=:tBB% wwcٌ93Zk62A{M ^0dp!:26u=sp'TcgSe"ee~ݞћsRi T(AǕAIX1kx؈ =aZ]wY9(ͺ)2DHR0v.Gl3f@ =MWr r5LVv,h"Edî:jpEҪ$Qƀ 9B`^dpMnƌ ӯBt uVtny3nprrr׮]l+. FhŐ1J4 h1D-@Km{Q/xbF !Mf':n*%g"jxp 8v\ɗ8J$qNTٳL F !\N%en"B4Mq(;ʂRI `unq2 ùV:@pe3MNc2|6.2FxֆQJu *`{_(@)Vz4 |Rӓ{ hQt]7/g6dJ;!;5|粎Q,8VKZ(h4[aq"/DZFqnbn`*gj7j뤩$ ˭&o8hǥ%o~fZ lc.v@׆" =f|߯jVG9~x:cJΜ? c7vfnDhtDd&lnQeuKske2_a+ mSW:rn &I288`j!P4!h pW۟3Ф) IKG)+(q A*x^j43*56>f6Fw9\-gDɕ-Q%ޣ]5˄l'a2'o-,3Z*gq/7q[Tp=?Vgک:~M@Z1-}{eYX(>m6$ܷZ8|~nny'+)+~DcvT =WsLxFr87YEa|XCduKb~Ѱ*ڍ뎏w08SBА*H !g"H4 Tz6p]!̈́(jD3iҜ~LXjsH$FQb6$ ;mZH =x/[TFJmb՛Ȩ4~9 L›vu~pux'iLF5;贎_#]Tl_7)g]X dam>!u kvmz}vvԩS 1Y{;wr=V馇j,>/*bnn. Jb WVJ:%yϟ?HeK(P\$@(%HҮm'ȬnVt/&RpK#ҳhrc @ 𲅜rKѨR(%pɱ@F+Xqٞ?9l9? qn뙙犬fLm)^P*J2Ya߽\^+_ٹsRRT*˄gړsq9RվW*;vh*{5ĹPW!omV{{1 7-qr\>1 g=BqįD;!UĐdaGT NgŒEGԄւͽV^u24@QQ FԚDA&"al&Irl",pbb deUY㳺c(A_dPM+[ 0z׆Pl7.Lw..L\(` Wo/K{9&֫Y(j< ډNZK_* 6 =]vWl89F#V7$}ΝDdCDdEd١v]ZZ4MgKdE10fb}*wV.8P^kr !ZF BD~͖rݎζvthb8;щ1 4pvŚU*]m_edy(¥ҟ[dCcpr>Q&S( }$ʼnb$kMDرcaaܹs7|sX.JVkƘG}j};q]wtt /X2o}&`{,t_#LtavM62d]|> MŢ0 s<玝1.GuŭɿMֈ%묷`txb/DSǧg+r{/NirƋY cfsqqq6 LکMÕbd%uR]DX c՛o}[atM}݅Bӟtַe&hrrr-bgiiɶx zn "={u`B ZEy`*|߳gO8{eKDW@`z$rY$XMBkEG"]4{^>2g ص044dceW="r\-_t29Hd! "f3mL&'&&(оoi:00UW8}SlMT*W\G<g\=ݮ0Lp걶-=^Ou3&6S#q`֟yfk:|@t{Rs5 bzS'N/m=۸kgWU.!O:lHn׈8;liŕ9;Xo΀%|jӣr5@f[Zc{gkM- ksּɲߵ_ {*yM̓-<  AfGm6L ݆u!{xCp^cz=JD6gz肔rqquCu]ڰ[p^{_}\7vhQ[}56vm1;l#<EdL l<ӆR+!"sMa6nõեwi[b @Tca+gM u[o^pRQIH5S$67Y(# שp r`GV5'>=lxtp91+eNͪL ΰj=^.RZybWzŦY ps-$B'xWlPL ) yZAN Wz aR:ƠpI56[՚-%ZUӈ0B8qp= {L .ߔb/V+Øʁ?~ۭv':2DKT iuP0 %J#Wb+DBLt;$ĺR A)i \0c )m{#l36\+X) |Css$pApH-e8jJmu:B#3*MZM@D T11jx;JDRHWKU2O41B9)ghqR87iUM}-R̈́W|ZBѥv{V4M~kaBPbVKnw:qp)3sE9glT)"rc !J Kl?;5^XZ2Î`,LәF NnZ *%%8s:mu Q#$g}߱VBR*,Q*$nOU$F׍ƹ c%.&R1XnIuԊ0f&sw<婉XܐI4@bLFsFIJ93 `9w|nNtȶOmx]NߗR#B qrVt8c^#D9cT4;PJ|q"0+MS Ch4 aGFsRh8 (qđƶRcAH$lڅm&܆k;y3'v4y[PF\o~"(](SvD2!^7Dv痢O7q7sOjLpC7t,,6yfBkR"f\a*\p⸳(]cG)IaL=X L O"F4σ"4Ԕd<B) +4s&ic0t#\o_`3pTW1Q1Uʻ:Bݪח±QD0Α1!IZ`* S@k;u$܆7t>W 8ܳR‚ɔ6蜦=וJiJ}q`nnu[$_a1$I\ޱo_; uxth D6Lx9̏ sxXp.TqdDZu4US{r l36\+XBd2Hifd2e4E 7$j)Dq] r\irpՌ:{n p\7f40@hj-8yp\|CW& }n ^)F2DZ6Z 2 -^ 3gv~VVXc@ 0Yd̊\,p$DDMDRʅ\.t^<2wDq}ډ JX8;a86JF~H!6#M,rJ3g$a-oYq`"UfsYFF2v w "ikʹ311D 1A`mR6e}umTjCh44g_!B"d W RZ+v5 QcjYBm36\+i7t0MHпZ~LOM?s6`fHxˏ?i48nBJJ.Dj}{w[q1bMOD`L"̞8g_ʯ\C7G{FӺ-LL|i-K$?ҽͶ|BqmxC%5ADyڙ "VKz @iJWG9nˇX"9[톱q! DzoVL ^!DiTzԔVjp`hȄcd!h 1EΌ16(jT-UQ0@^V LPp]q%$ Ry.s$4tH,>]fmV@+eO1wS*.M^?jL>WYZsC\T*М[R=Zy2eM'e%J=>g|ҍG\ |~%٣G@$N8fu j̙yf-VS afmp B fVwS'fSx^6k !Xp y#eh :-J<} 21\SlϞՔR^&ˈDp!Tk5yk0Fq!Đ!u qKCĶp\n!!V{7U߱Siw}?u#G2Ţ@μ"(EB 34LtR΋>Er\_'1yu^ y8qͩxISUl5]p\"2%A@\ah(d,Wȕ2[oxA! @r9P*-\l Kk<DZ%{'qr99f!OBrΈV|mxZTxl=FjzG*D`k~^K X1P^#>k*J@s?yt82cN1 |wvnpMYΘن74:m@͘h6ˏ?srl칧JPƝȭP_ǹwƍIb_V=u)tF95 vVR>y~C k-UkD巙pppR&1*6M)Qĵ^$LQp6&AL8gD.oƂuE$V۲!zib` -tӯdsD r%"jL o,ƣш '>>1݃VQd=!`ИBJS_D޻߆7lnB(cA6{ 6co'oPL =̙3i^IBJ߿kϟGn:h}ywyFk'"Zo-k5[R7 }n5Dtl6d-!^׭)VfmV#"s \C}}azm&܆mö:uXZhSYox:i/͆췙pJ$I`l ch"r΋ug[&đRiz-CDaj_("zX6nlxj!b6% H~AAki"rgvv~-q뺾r5rgjjP(ecq7J2$I&''Jm3۹sg$.-"e}} %IJӵR1|;АR=o߾kֺX,NOOm+.Ç(zbqqĉ÷ I0 4 uMpsi3Ija@1snRk$QI)_Ç^gG@C$gZa q%'IbQm&*XnK"c&NtMͦ`aY,%! ͕8V{$N}D]蕑ז6rͭ6nz/AU˽vYo%gLG 2XGl;/\hLN>w.=wJ;vr9jF ?$&dQ;B/sUneRn{JZ.1"3<(t27)|V J?sb_%3I,J-$q<{b5Ϝ}VE$-Zm&*pّYHɶRՊx $1+-I'CiDLqCWuvs8FGG2x8' C4X^'ҺW\r%!к33 0,lyGLȀA2 )\=6^lψRJ/Ę$TrB$<F9Itx.ثʶ}uHqֺ:tDȇK1<ΐ A+WxF R0c,.>vu}gf:&GvZ0'$$T=ȓOKJ:6'q TjRZ Rdþ 7U YٻwoNY2[@ Έ B nd(!A~93#^ïh9OCΙ0D !ۈm{H6jnc&cRRJ4 ~?Ç$yӾ-`!1B  cһ/݋bw뺩'R(25!gZ 9\*Jm3U9(bg_3 K(M]Qq4gF/V֑8ZpkzpnyÜl8Is=c^QLկ, #C+Żo]$ɑɉ/B!<Iݘr=ҕrEٻm&bI  8J p\MyGmtX;0b˥^~6 ez+eB"pfQJ-23eWe1Nr)!(côvY'd$jqƒ4Ibv]q]'d Jq"h{46!d4ssɄHjo;w,ڕ_?{_xcCxicc,^qC Ą~_8ΆIfMKXRvZ0F\ͳnȌWJ q,>dD5vc/qܮob042lARMu .xќd2v;;8H2ITʑf rm&ZX`#"LP\TSKOL΍cP&(PjgSBt;%I"✏b |hֈlIrJf:1N) eheo?;E:/ԤMUۜc$c[o~їB_fT 3D1㹵/ TΞMjqFk 0VGЕė AaIq}$!cag)][ZZDtԩl6[*.A7Vg2FޒEg'ƕ'i#?<cUgfu} 7ݿǎ0IHqBT0P* F85=ϳL빮㸮K)繾O,H%)tkDŽ6dƘA^( *Ѭ"𤿘6#8>.M`߈>Yk絛ݥX$vt:G=Qׂ8dͻLUIhZ-&.=?ovxz!N.6f885o6zo|>V*ژ)f[j݉Bg;cts|<^\ a(mYd2\*@[_o R 7i_] b[Aоi[35|BuE>>cX,FQ/ [mal2S8CОj>\p'q)27~y?Z[Neͼ'X,7npaCo{XL IٮՐ1r >J7x|2f__ؘ ]ם?G}*[VBZrtU_O|bttq7aJk]դB`n ->t lGcJ%ʌ$:_?,Ra޽;_MUz[<=?qϜ{ Fɉ4M^|š!:˺)|D Rb $~(AwZnn8v 4Bbmta e"]c:VdƄsn3'.Y?{ C=#t5 MwC`tlWWϝ;O~rrrˁi#<o}k$缁DCDdL/tQfa"君N0A- E"@cCq5]kA\{ᥗ^z'y晁[~PfODV$Dg~>7>ySgϞ9{Vp&8hiil%NyV%˝_fʕFlrZ)3t:6}qrܷmqŢmo$uA@+#.𗰙enCЭ:;;w *Nۇ۷O:uA{YE]woo?ÿ{w]w}NG-J?yfK_Ȁ1@ 5#te֙Lf~~~rrrϞ=V{c!"nȑj߁F}qǎ}(ٟŋxfi{kG8< L)?Z qG{*N$BȮ ~+_f_u8?Ow}vӟW򕉉|3?>~=~]{. w޹GG믿~ll?o{hhGq]=yUGi4~B'Nw}rC=TV_O&dA0 ag)zqկ~~pЇG>;c||>sl6_zbXV-k:{rx%cp5Gp IJRa:P8˝?> Cι N\kV,É!%)I X&_:ѿۿ? P(㎿jNPǖ)Cxvw:VaI=!I /%j*nxb6O~7M??8~뷢(f֮0!/>hX׿;;{n\}s=_JR7`RtNswg>uGG~a"ZV,--}#)_؝kX]c8sbI;*Fu56z](BwfhPf3_Ν;я~W~WqjjooaC5PGz;fG@c…dվgz.Y1c"q+om?Z1d 7lmbЍF:sر 2LV닢{]וRV*庵 gTVmǝwi6O1&]OuhGBXf/7<<<-6kyu=ɫ?osD}_;vlhhhS_j&TLb 5 sq") pWVn} 1l "YDZ-zMI9֪\)n ("YzJ޽[2ϟ_i7Z֭:55%t:zk6獎&I277Z@f)=|u!lTܷo_$fs~MDJ뮻~~nbbbpp0I,X3-Q q]dzbz *jP1WM+:&ΆC0n;{~^0WRp??4M7srr_sU*|_/J%u I}v[~s+ ~Gٵk׻??00&&|l؏rz*Ƙ\.Oc;uԻ#G?|>ZE0 :++=wsǭzUF@&Hxٹv>}Mcp؝ ޅ&K84"̚=2Zi-y<1WՏ~O_3<3999fԧ~G~_/RZ/| G~j}ӟ`S$>J裏~򓟜7a# Dٳg/}{ /q(J>hӹDjwqW*~'O\λ}]a72Wj =\mZJ u]nc/!{æl;J% CsR\.^xAkMɷw \Mٙ!DsVb6tvv^;c{ZPZ]WPJMNNu+1lvtt4ɼ>kury5 5x-UG###J?V<֧pMU5GGG Ronnnaauvl}+w7[2YkVa]/Zk)-t]ܹsccc'&&ժmK+Yi %2mu+]}e#Vӯ1kɺ`I@ h7 9-1fSC…m7bѽrWwmUY?INc gR(Tf vUHa3bQͲb[$Z*Ph*6*""2*j)T i4qĿ޻Zi&i~G$'}{߹{ڴaDLR.{]uG2?_Hi+ߙ]Tt(%ksΗ9}S~*b$dLtCXX2spRqE07OC +Yolט-YfNxWX,Ax*57r2OboB,Z,0WJYT":'NPJyܲ4KEII  ^[h}gSI 7^lB422}:XZZ K[o~#.(R]]ra:P.7q2r<D.D'R4g寜Uye(( d+{E؊h2 0S {L9>)u`,ʂ%@*GN2dF'adLŕϕŜX" ? .ΗB!a cs2uHHPX< dS,,ZHB2Z3_%',ߌW@4,חrw IDAT2acX{DA'RAűAhLRadHu c8O+"YJR`0O"CATBVxuD;'4]]7gS ,ZJiӦN0bZ gv8u#c1ry,D$"dM@ YejDaMTŭ[ y￟oq< ؑ20a.)7nG{r`Jx}dorZ00dmLI[J^#Ľ ܫɻ 1 qofccf2V^^Υ822t:cX6|.'D+))q\Dbrr,//w8?={>`ll2,--u8٩`d-`O*B1""$M@f2 ݽ) RUXO}~Mׄ< 믿rʎ'O8p   8p -[7nXSS[o=s}ٶmnǏ=S/=gxx>駟Z[[{?۷0z!9rt,mEHQ JZu#hH:~ D`zfjBӞܬ#G4M۰aCYYdSSӳ>F?s=3O<ę3gzzzx 6t駟:q™(_ѣG;S{o(f`)!z٨D&M8tG,KQ\>BC֖[ Ql6*Q__;>cx|Ŋ{[{{{)}}}mmmTnd2<pccΝ;xN^UUw֦իz͛7:uJU-[455A:UxGFH%  QD20 ӳ 7D#XYYg?#wuWYYY, _|E2t\ǎ#B!˥iZGGGmmm"ޚ~~{6 B.]xq/ @D0o_y啊 {``Wl4n#|=*/)`lH$v_|߮߹s]~auuÇݫi!o,əVsf R*m=ooO:xp *VYn]<zÁ@/'&&NMx[Q/\PYY)2qҥ@ bX:|RD"eee cX,0GcLU%I1F A 2h^z*OQ#݋'xhenW3̃>аk.~_]-~j<$0y?^Y>8U]h{gtDxP Ƞ Ò.c|tdߵc*~ӑL&H$xt1Pa9ԕۙ^64sD-ld=+ XjG:<1N_x1Wlo&BoBDh(H4s’[|K,w/A鵰ٰDhayLL%tEXtdate:create2016-03-27T22:28:02-07:00 %tEXtdate:modify2016-03-27T22:28:02-07:00IENDB`craftguide-1.11/settingtypes.txt000066400000000000000000000003761360474225700170740ustar00rootroot00000000000000# The progressive mode shows recipes you can craft from items you ever had in your inventory. craftguide_progressive_mode (Progressive Mode) bool false # Integration in the default Minetest Game inventory. craftguide_sfinv_only (Sfinv only) bool falsecraftguide-1.11/test.json000066400000000000000000000003041360474225700154320ustar00rootroot00000000000000{ "items": [ "default:stone, default:stone, default:stone", "default:stone, , default:stone", "default:stone, default:stone, default:stone" ], "result": "default:cobble 16" }craftguide-1.11/textures/000077500000000000000000000000001360474225700154465ustar00rootroot00000000000000craftguide-1.11/textures/craftguide_arrow.png000066400000000000000000000003461360474225700215060ustar00rootroot00000000000000PNG  IHDRٱ\sRGBgAMA a pHYsod{IDAT8O E 19SbVO1rҏc"#7jC{y}:]Ma*nkE3ZQ _V-#6y@SA5dCXŸp[PҬ4؀qi&Z_4`l}WG¯ԛ1f` 1pc-ƎnXefk<.=dqt5Aַ{+!Ҕ)!>$>@TS`#y5FK/1Pb@ X))ǒ#YM],9e9[oT5dj܊UOܽx-\O\,/ԊM+LWШCt;(GmHܬy+vq{I'RLrց\2Б eM=mjXZzv/4@MHh̢SJ ]bKGD&x pHYs.#.#x?vtIME EzIDATxرjZa?8$ HNK9Cǐ SҺ.!PQ:9"ն3<9^n:iw,24(e{2mVۗ )/ӴhE.5Z:DŽ^n>3ENss>wmNu>\*@O߼2 *YP|@> @> |@>|@> |@>|@>  ||@>  ||@> @> |@> @> |@> @> |@>|@> |@>|@>  ||@>  ||@> @> |@> @> |@> @> |@>|@> |@>|@>  ||@>  ||@> @> |@> @> |@> @> |@>|@> |@>|@>  ||@>  ||_gieYԲ(YjLL1mK@'$cK@eKZ;sUIʍ5$ 6Lr3>jaOrN$nL"7w+7tI؂dPĩق }F1(1E";cX| v[="ޚ%qQ-["LqEVaf"+IĦ"&BD)+Rn|nbң2ޜT@`d0l[zZ ?KF\[fFf_nM{H? }_z=YQmv|c34 )[W%I Ȱ316rX7(ݝ ⺱SӅ|zfšyq_0sxpєqyv\7GSa؟8"Q>j1>s@7|8ՉŹ,߳e%9-$H*P*@#`l=p0VHiA>@ vjP @h'@8 .:n``a!2D UH 2!y@PAB&*: :]B=h~L2 p"΃ p\ u6<?g! DCJiA^&2L#PEGQި(j5jU:jGnFQ3Oh2Z mC#щlt݈nC_BF`0FcDa1k0Vy f 3bXl `{ǰCq[3yq<\ww7Zx;| ŗ]8~ M!8Ʉ*B !HT'\b8 q$C'bHBvay=+2Mv&G&Ec[ [bDDĐ I* Zc0&8(&iYH~Ho(%46h0װu wKDŽ7EGGDDōFG7FϮX{xULQ̝:+sV^]*uՙXXf8t\DѸ@f=s6'~_ ˍ̮`Oq8圉D]SINII\7n5ewrm\J`ᔅԈ4\Z\) /ד>aQ1n3|?~c&2S@L uYY5YoóOHrrsNy};_-cZuuk/\?kÑ)*0-(/x)bSWr±^$E[nEmnfmOk%%%JY׾1ꛅ ˬir]+wZiYYGgʿs{?T'U߮qiݧo۾C*זԾ?=xΫ^P֡ 2mjTl,ixwxHȑ&JG˚faԱc7sŨZr}wN>8(mP{nLGRHgT)S]]m?x3g]8wn| ƺc\x'ߥ+=/_u=wvWO]c\n}Ϫ'l:o\:xviMoܺ~{;˾;y/Ylx~XHQc?:b=rf}Icda)iDӤ)ϩV<|~W_}oοDΌ\«ï-_w>~f~#zGPQc'O6gAMA|Q cHRMz%u0`:o_F}IDATxb?% ( D"VmX!abh?:{mˆ,?L 6me \=?6 n~G|oQ`qa 2f`Akr=$ C@ z6\@ @{1Ԋe`O14O fyz%n@10ᚑ ~t8Xs}F .fsi|Stx@؀u`A\u'fJX nfX $Wݴf p:mkL`lgع~2X"0ۗwpg OH0ss1̛pmA 0R"LqR  Hiv0}>IENDB`craftguide-1.11/textures/craftguide_clear_icon.png000066400000000000000000000013041360474225700224450ustar00rootroot00000000000000PNG  IHDR>aIDATx1nAv܃۝HA 9O7 \FdfNY˲7J̵I>3wm76~f~̼̫̇v5a\T Q%-GJ .Jp$|%h%x"|%h$%xAJP" ֵ9c]JPi7'|`N3wL~I;_ *KP0|%h~Wg.:3?E'A׽|4ɇA0q2o#ICDk#`ml_~utaV/%=r=A pt >1VEψZuE0 wOW1q |@&@7 ?Io5R!77n|%@J`+vJ`+;{%XؿK  _ W)v'& o"˜oo2".s*Y=ڊEuKNps6T,'$@ 8WTSK%~IQ .-fu=՝xxKp>>~\+_赵8+GLIENDB`craftguide-1.11/textures/craftguide_clear_icon_hover.png000066400000000000000000000111611360474225700236520ustar00rootroot00000000000000PNG  IHDR>azTXtRaw profile type exifxW$ y 4ǡ t|}`'?ۦH۬{K&J.d5VxSw:C ϡk>u8./xo7 :Ϧ׵j9?79f1o .XA1}N}cg߾ w>ކt><3#2/u/s{g{]k1dE=-N(ù,< KdlcW'E7]sۭ:`/y~p}$%mI4A\+<gzW|xG|<NJyy43gʉy67O dPN l_^N牍^o@[ d&%g9XOc>DɀәMnBH$xk;zaDHH! d('FDd)RbRNQ-s.VBEJ*RK(LjRkmAn\8{KO=ko8dGugL3YfmV\ʫk;ewuY}5.s_gYӌs^~s~S:ё^sfk4gzB:ЌM ޯFRr~{B ˞@96Q2s e |:84FE\\@yA9kU?laVNmE;5)p@STxٽm4cvb]}.s+*/4aJ$u&]J2)ֹFPFeQ{T)'!f/[߫2uٹ8M܎ ۦhD7ڱtz[jsqh%W6JF$ !R)U2"K6ͯN&ջln foZÞ{9XƆ=(ԅ%nZ%@tQfmaUqER 0R~ײS4эbv hwncyRV ,YJ Y0 ~ұb=ִVmK`.2?z Z[d5<ƆYM) +,+}V)'B\wxi x|eCYL#:$čuL6|5Ww,3]=¾W,٫JKB ;u3dt-A\ES!r+dTڛ9h2DMm*N䪻\ XZ" X*HZ2w#֩_؃`I<,`V2j'`P>݌".*=şjmy-/i_ēLVWL aB((QOlh1Im> Gҕ?B % 7Pq`!nFĻ2JiM*-LJ'cTFJ@TM*1͸LS|*?TcH\ qFCB jϩo]䋿Nŝny5Z.[!n^J,{ FO(.,@fj5:$ʻ'BN+eBIꊻ[5 Y6Z۲Ͳen¯荸LB֚Ri(ϣ΄:gc08ds`S%tp`* Yh, @u .R B:λ9'~iQ+'~v٬c}Њ堞kQD`R0.hB%F'8q?*!DT/20ˁo )|/@(f`N!@TP,G;>k\f=4N(?8LMx##c 5zH<3%uP(r1yDJ^^/Jј4ț}9)HTTaDB?Gy:F4 ?("c)k񣞆3v@( hɳX"/a:t_W ;<- .NEʪkêa%bbcvwrAqXIơBXt 7<4|Bx7,6WքuksX@MuZ:^{V ]W `;#mP-/e@_< S!YmtgE=B<%T/aqoͯk"X5[{Bh40?q(omkݖf]ᇍ]>.]=|Vnth*H[T:y@(Ws;)EK{7՝?C]sE_@<]jn{hy'uL# &Ӡ*6K^[`sajh 3di\V-9%ac {SL@n3@A߲gءNBT-=a3NY /ft0TN HS aZ8 fl wGŷ0n:>9̯Oz90)gtiZX.TϠj9c=~tE?}&j8csq:,QrJg"RIMEcki'ͻJA'B>6Nn\16 B JXa_l&y<ϱjDٴ&ٷ10u>jt< KwÈPϭ90-fiVBpf@m4Thl~$ő=w<ێ.{@6g#+B"rp54r6&"ps-NS>{'_ ,ԞҊ;:10-n|`}*,rO$=MWSFӂ]#c)_!r x#ٜOGf28t)]vͼI0^~#;H0893]e?wBůll# y F)Tvӎ tX$`r8?Mi3WyoJO6z2 j$~!{I4Ÿ$*6hh8 k*xj+uS R TcVu0^ɪΩxve2FoOBٸP`"w9M--<P!wp+PD7GˆtHBWP ܯXwoT_8bKGD pHYs.#.#x?vtIME HbIDATx1OA9hĎ_^#XYPI VB"RgEkqCB ,<1^| P`bX ߼&N.w.xU\ߥU 0F| P0 J| 0! ;8@ ~>-r&{U,+MP|KRpE&9#s_w Pu'Aʯ; Qs/n== =ٳzF3oeYn&j?Kfů}I-M2 tZua*﷍W%ZyM\+靵͘(>jJ>) L420U$d%Lo r/* LͰ%Ét4IENDB`craftguide-1.11/textures/craftguide_fire.png000066400000000000000000000060731360474225700213040ustar00rootroot00000000000000PNG  IHDRa :iCCPPhotoshop ICC profilexwTTϽwz0)C 7Da`(34!EDA"""` `QQy3Vt彗g}k=g}ֺtX 4Jc `23B=ÀH>nL"7w+7tI؂dPĩق }F1(1E";cX| v[="ޚ%qQ-["LqEVaf"+IĦ"&BD)+Rn|nbң2ޜT@`d0l[zZ ?KF\[fFf_nM{H? }_z=YQmv|c34 )[W%I Ȱ316rX7(ݝ ⺱SӅ|zfšyq_0sxpєqyv\7GSa؟8"Q>j1>s@7|8ՉŹ,߳e%9-$H*P*@#`l=p0VHiA>@ vjP @h'@8 .:n``a!2D UH 2!y@PAB&*: :]B=h~L2 p"΃ p\ u6<?g! DCJiA^&2L#PEGQި(j5jU:jGnFQ3Oh2Z mC#щlt݈nC_BF`0FcDa1k0Vy f 3bXl `{ǰCq[3yq<\ww7Zx;| ŗ]8~ M!8Ʉ*B !HT'\b8 q$C'bHBvay=+2Mv&G&Ec[ [bDDĐ I* Zc0&8(&iYH~Ho(%46h0װu wKDŽ7EGGDDōFG7FϮX{xULQ̝:+sV^]*uՙXXf8t\DѸ@f=s6'~_ ˍ̮`Oq8圉D]SINII\7n5ewrm\J`ᔅԈ4\Z\) /ד>aQ1n3|?~c&2S@L uYY5YoóOHrrsNy};_-cZuuk/\?kÑ)*0-(/x)bSWr±^$E[nEmnfmOk%%%JY׾1ꛅ ˬir]+wZiYYGgʿs{?T'U߮qiݧo۾C*זԾ?=xΫ^P֡ 2mjTl,ixwxHȑ&JG˚faԱc7sŨZr}wN>8(mP{nLGRHgT)S]]m?x3g]8wn| ƺc\x'ߥ+=/_u=wvWO]c\n}Ϫ'l:o\:xviMoܺ~{;˾;y/Ylx~XHQc?:b=rf}Icda)iDӤ)ϩV<|~W_}oοDΌ\«ï-_w>~f~#zGPQc'O6gAMA|Q cHRMz%u0`:o_FIDATxb?% X8#3#> a @Lph@ @>BFU<@1Q(0 Yӕ  Fwݡ +м @L [~ik)'K@A58dH$s)@0e%4̉B4 /5CaؐCPkfq< dbCl X me+@C;#21gD/Ff@w2Bą!AŁ BDoo9VpM Ia&L0W e  0MA~?IENDB`craftguide-1.11/textures/craftguide_furnace.png000066400000000000000000000064451360474225700220050ustar00rootroot00000000000000PNG  IHDRa :iCCPPhotoshop ICC profilexwTTϽwz0)C 7Da`(34!EDA"""` `QQy3Vt彗g}k=g}ֺtX 4Jc `23B=ÀH>nL"7w+7tI؂dPĩق }F1(1E";cX| v[="ޚ%qQ-["LqEVaf"+IĦ"&BD)+Rn|nbң2ޜT@`d0l[zZ ?KF\[fFf_nM{H? }_z=YQmv|c34 )[W%I Ȱ316rX7(ݝ ⺱SӅ|zfšyq_0sxpєqyv\7GSa؟8"Q>j1>s@7|8ՉŹ,߳e%9-$H*P*@#`l=p0VHiA>@ vjP @h'@8 .:n``a!2D UH 2!y@PAB&*: :]B=h~L2 p"΃ p\ u6<?g! DCJiA^&2L#PEGQި(j5jU:jGnFQ3Oh2Z mC#щlt݈nC_BF`0FcDa1k0Vy f 3bXl `{ǰCq[3yq<\ww7Zx;| ŗ]8~ M!8Ʉ*B !HT'\b8 q$C'bHBvay=+2Mv&G&Ec[ [bDDĐ I* Zc0&8(&iYH~Ho(%46h0װu wKDŽ7EGGDDōFG7FϮX{xULQ̝:+sV^]*uՙXXf8t\DѸ@f=s6'~_ ˍ̮`Oq8圉D]SINII\7n5ewrm\J`ᔅԈ4\Z\) /ד>aQ1n3|?~c&2S@L uYY5YoóOHrrsNy};_-cZuuk/\?kÑ)*0-(/x)bSWr±^$E[nEmnfmOk%%%JY׾1ꛅ ˬir]+wZiYYGgʿs{?T'U߮qiݧo۾C*זԾ?=xΫ^P֡ 2mjTl,ixwxHȑ&JG˚faԱc7sŨZr}wN>8(mP{nLGRHgT)S]]m?x3g]8wn| ƺc\x'ߥ+=/_u=wvWO]c\n}Ϫ'l:o\:xviMoܺ~{;˾;y/Ylx~XHQc?:b=rf}Icda)iDӤ)ϩV<|~W_}oοDΌ\«ï-_w>~f~#zGPQc'O6gAMA|Q cHRMz%u0`:o_FjIDATxbLOLon6vV\Ǐ `6333ׯ_@,y^~o ll ~fPRcx9 g @A$'wW 77'UWH1032| X` 9ؘİm W3HH2$" 4ûk5LdGLP$Ry˛wm`f`aF؈ }Čl \ v.6IU6&o]w@1Ƅ)tpRb/&8u>_F&^Vb\|_~1prB0-oVV0 ߿`ί?~aIDATxܽnA@3bwdE!xWd$RC`THt$CX{>) su$I$I$I$I$I$I$ITÍ9&W)v7m%>0*L)Jn8^ρGTP!p(1Lʥܓ FS~>u /ia&$@))_`V x9@ `A O8 /mZOX}b/Cp14Cp14O$0"/"0OkGҽh?B/|14_ bhC@/pX眿 xн;yV|D5u) X˱}>ϿNʇ_yo%0ɠ _ 0݆_y3j߶k{y /||3 _ g>@/|7i~n~l7o8|p ;?X]nR 7"c3hK$I$I$I$I$I$I$It_F nqIENDB`craftguide-1.11/textures/craftguide_next_icon_hover.png000066400000000000000000000055441360474225700235520ustar00rootroot00000000000000PNG  IHDR>aHzTXtRaw profile type exifxYr#;E^'pX'Dʒey(\$E!$.pج?|BLD%Ք,XcImt6j,p%Xruǥ\]_rRm]Mk8ןK1o .XexPCk}왇Jzv>}2}d;ۮƦK =ZwWo^v~ y,vJ:ԯSX |U8vkWDzꢛ9u ^13ÇVBՏJܗSgP@bFŝ}o!O{rn:gV>kl)qzT}e>@P lTtq/9[h GOR[4w-&ȑSӉ>f]}ТtWsʑ%9\xRS\bSBletu$j9wʿ#$EO,풖TF;rK(&3HWְ2/Z8|2?]Bсߏ#p&oNa4.hbw()Pj j9w&4e3?Zvۛ/v5?Gv5_^]@nH$` FL+@x݁:m\bU?Q6@6SSFI#UӬH4ω9pVCۅ̋B_lTQ3@XhY&$[hN'Ch|V'W)DQ|Y< ߵe[>Q-XaLf^鍳BjN2$/LW# O09awʼn[,| A?^+g"%PRG!+#q:|%Z /1nxL^Ó4MUoTڗABؠAGߺ8Gc TZQz߃)m9LW{VWK\I-DHB~B4icbCyuE=^A7?D>ϑʭt=EQ=EQdڢk(T憎h`SYes&`CS{_BPv|sJ$I$I$I$I$I$I$IF45#`[#8T܎˦fQX9Ǖ܎pTP9 xbKÅ'A&ew\#jGwKڬnj1"xk\Ai1A` `B{ 9>Py Ia\ \ < |h"^"D`Os6GPѾh6?B/|14_ bhC@/X55?sKiߝ~ϿN}X/6SЇdPЅ/v~6|m<lg>@/||3 _ g)~.~l7o8|pT 7pNFM,+ E.;E7~8Ä݅ÏOX-ڒ$I$I$I$I$I$I$I$=/Exc[7IENDB`craftguide-1.11/textures/craftguide_search_icon.png000066400000000000000000000133611360474225700226320ustar00rootroot00000000000000PNG  IHDR>a7zTXtRaw profile type exifxir>8*7n-4#~#vIʂ[&Rs[l::W7 ϩ1\O/7xoϛ2q={|~>Y{l}_sXJ;`])yt|;|.xwbg}> }ޥw1͌˓|ϫ؉*T6K9ppn˼ y5^%N[9xMDV\tu'nt)F}ᜫ%ˉ/L 1A-p?syt'/Ǖ1_^棓?y=Q:g3Vk" @1M'e^ƾ6`:a,q 1{V88K6{+d\]H.;[/ >_؄z}6w_H!4$`ŘOR4)JC9KV%XRɥZZ5XS͵Z[ͷ[1z硝;ww}FiQFm }fiYfmWXʫUW[} vi]vmwk$J,E4OnTߢ!9jFMKy TNbb>:/.F)fy"yPsIYNOؽ )n&?9'3 ݍܯ}ꧢfAظ`kך?=*ӄba%<eNs!͍f9őou^!3,1^ ګ/V9¥<`4;ե5k{v#G0e)'jǺw{n2dfdoxfK//Y)!R^KcE Lksm԰ YTM*adig.kX-G n4h, S!– ݵ6}T^ Yܫ#,Zc%9cYA6}Ɏ3:5) t?>̼tfLQGHņc)MV* , T.&?9&R 94%L!1o2nJCNuh uѾiMnRGj7ˊZR-kCnʡ6âTO3ϺM ;x`1 lBdP:j= S1/S5"v!$$P9K/H8P+;Sqhv\~s Q;eNZIB:}OZӞsZ뮫50 a#!;@w; 1HL&7ei+LS4}y i|ZBFm<0F1n[g7-3ə2DXĨ*]Gm[)~ !a#vb#AQ=m%rԤuoO|tG2j`f`X 4dauc -7{2|ȴm(NQb!rHdCu$vt-ByǓk:(SnFGd MX"/ JsU-{9 c}cY*٤,Aڑ}PXhmȿk}FG&23Z{bR[ImO-' ٢7KjdiY A>IL`odb$ĝ%ȱDjQrAB۩狔!S֓Ee&cj7g7 a ;C߀P-ˡNq4^ZPHi)um#R en}F!ai=C!v1㇇Y]pKL+dHM#2xaWIt">ܓΝҮdy2IEr|4c:"4\LSp{ Jt;1:ba±2\2L>P$]ȝ'\5Nr KWt瀡F;J!c| n)WMJFNu`P@;Ӥ=  q8,mo*;e}m^I &0.uN 6h xy}yEIi ZC~`j}7OGG"d%SRW6EehV?_4셕vRVkXˤsj*Cׅ NOHܯBJ]R@t?})7^>`D<1pO{tv }x[|ڗnp7~4_ +mUP诏Ơ צ حu/%4OTfz:S~M q uhri/T:v)ak1իȐGpZMSks.QZNg$8KV,*&bP{`JQ4ڊHgQ]#ZA[i J>@Cit+g1.Esb/F1Ϛw(nhaO*DJEh QةGZqh_;j3Sq:R⬪N RtgQ醂 U~kCAho̢nBx`u$L[qV ^_6Vx'IP[~JMV`{LVr~\Kq`_l"j5~,Gc|Md(B 8{xb bsˎ`5h6PaO!tR*lڷgrjۮN5j{ M/t5 _ICC|(Ɔ(v*XVnM=lcv䀂 ^i|~ZpY͇vw/۔ԗuĚEA?!6g@mPEiV=k;ϪD/b W:QvFim޽ºI{?6LA9f~dw٤>8V)k*dI7D!Ry %vV} zY5,W3 ދZS).9z-'7TL:;M}/_+ÆG<mm\ |9,n%(Ew)BnREpj 9>2\u~6U(f:<7Q3H]{_:#}5= *D "pA 095ie͆7&HVAa9g^9YCw@4 r1pY:I<@-ǫ٫JTO 8ˁ\XիӃ Q`َMv֍}]mwοqn^ÛSj^5 7慛GMͿyg9#6sCOGxU8jU?uw*&$͉Od>bSɜN Nw&Z[BsNEyCT2!oe>!'G2|"zoEU  L0 tt(ɕtM&Ǝm )"l~˺?;P inbKGD pHYs.#.#x?vtIME  j];IDATxKleZn!SBD! !qaD\$ܲ E7.HtaX@+RIw$ ADn-BiS~.C@C/3ϦszB!B!B!B!B!B!Jȳx3&`0>/ 8B@ Xf{sj7` @'B.d;oKoP3ΟnW` ķe@ y?!Lo׀FOS|Q7B7'pV(}0p 8 !|#&u~/fI^|B!L]Wۀ5ȫɈao$vNfd"u̻w}ݾ11yw g{[bwN55fE9BF͓_(3{KIaߝ+x"WKzi}zqҺSVOfc1`flm7]]]l⮮`fk7o5T'3oJA6fv>1$(ok kS&fv:l5OfF_3}29~3;)3f>`f~;mw [gK~hf7Skfl4hMxҠ]fv;mF\CZMpv)̎T^oʀM?Qp'rGUh7no8L I5$ 5zde)=׀2+r-'][`Q`gʳR;U<= V$W!3Y k:j%^Vǭ~f8V{]`^I{ GBj\[kcUx&Z7n>F4זxֱ^fi&Ztwwz\ck2*q,r\s2 .zN8ze7Nn g]sdL.)wTuq2R, ^H|h _^J@/NxetazTXtRaw profile type exifxYr$9pDq rf${iGf"ŪLd/H_y&Rs[lyt6/|ά{izr~}ϱP8^Ug !&{뫇kg>~Wgm)w5z_wi7+r_/lr~UY\]Je^ǥgqPp?.KD?K/g !Ӝun>S}Oyj)Ph DbL z )RN%R!ǜr%z %Tr)Vz 5Ts-V{-@aVL9iНOw#8ȣ:8̳:˯W1v(λpI'riv]su]soԱxWtK8$xQWlu1zuN=35Ԝ1:}ofRW}Q7:gԺs?'][*J 67}Ҥ~4qokSl;] Vvi,Ӑu^2@e^Ç;Y۪9v}WeڜnΆ,)R>S9@Ѭ73xUPTVw\0 0D}l}} }W0"S7Ht_cTPq0^o8a:SiV yA w[&{lF>ۛw oR2Q[wb |0޳pd,7πH Mջ( S+f$I%VR˄!§ #%V]z%ōg2Hèeڟ*]Džƛ{m_qn#X)=|<ǰGizOܺv`Vfh/ׁ!W)u3lSۍا맽 ܸO/_{P3[cLoΘR-hjAxΆm圴ºe4P,(e `^D/j .dT&58 \ z Q$>L_y9J)&ƴVxcI}tmXk=zxwX/޺p_Lc;I9BinryCƭ Fݭ661h0QT:f"`EpZIi4SkO=^n%.H|ܡcA5bbaF@7(&'y;2B@Ǟ koo~R4CAk8m\ puA[P'ԉ!umȷ oD8=NŖRn R2vH3p2o)C1 3GH.*KZad,_Ȓ^Mk~7}BMDj3*+t,G|-r,|]dAb0w"_TB-biA|alcb \oЕ:\Do?Б^8tnH 7~K'c.#N>bqwjׅ=[iْqw?gF%I!Qp']x|q1HQ pUxfX/QM9L0hSE-']?<:X\ IPFgF+`FoVih1^sZU"u33䣌 -e&8;["2W.Gu9a IǴpMuD}@FVsOMh$,܉ѵpRrm fd.$K3K/Y=,2BWXJ&Z=#8V 9o 7< CVC;MP rcHr{gػݡ4@]ROj!IlDt8Dmq+d6f4qhÃF. k嵙3 q`(' v풙(i HQ8fC`.Vp'1 ˅}q:cbȆё<)b)ʴXITֱ]Oߧɰ=&lQ Uv.a$\Ha'NJUy(4F @:aJLF\d0 YѦ<&p6 Rm5쒮8٪Qǁ7/$ţڃ&;'̌-sfq- Z=]ra6f'B/sAl#dmօĿk`h{b֌ m$xe'# R+3]zR:HhITe ^)Tox)ߢ֜k,Ӡn7Kj[_".?w]Z73!0}rb%d1vƸ2j7 <N6Җjgϳ&\rJflnCg}PDvCֈ0 [PUb|Bb hW; %c-񙸩B'"R2rHayk{4DE d &PT1w(hNv~Fn*NL(Kf) BB]HcbvV,+F\n{8K+Ij=ak)рlWw[22>˸@XaYƬWyL](<1BG|}R*]F;)S&~hǨP%e˽Pi]f[uC\vXJ(cR`=#k5?Ǽgxͽ0N\GdVH)7oݘïq)ҲiUx |<[p2߲47\Mw঑H`/pigԣ6tOF@ ޛ"7<;պ r֐bUﺿZ>yGٞͺ{7#=g٣i ($O"-*,(0`њ0o5F[aJm~J-kK%onfZ1ڑ}0."b`T28hTS 諟`3뽋taOOʳIZ7D 7I6Dc"AKLM$xcI{ìk$b;($Ί/N+4Ht'tq 4){kfƔs'$,NV7c:Vp p][CSpetgdr)mtn Jtq}[pUw-0I;+k{W1ww H=MQ@q_ ?[łuN|LWqӶsb\cj pƉE>tOUs+Wˀ˜< u쑼/^%$z#kVwM|cη=߭*_T@,dz/]LJ/_5/'݈za|\S7:hZH=ƺt_.q0&nd3Fr;.v3A[j'r&YT9\]("At/%EH§v)Nu[w<T%߫tbKGD pHYs.#.#x?vtIME !;&XAIDATxMlU(#Jh#-U+!qaD\MXeAPn\°pD b l !$H A|@SGo9 ̣{yK^}̹qB!B!B!B!B!B!DV lw`>0e_~/@+X,j-y\` @'p"p 88.l& 3E J-[`2 ǂH | E3lҀ9nI j`uSbڰuO;IRjRwm7D^eZK,b!ש;>/Bo k5Mr- n;*mXf;~^w<֞;Z?hCټZb;k8};.zAػ[Bk `5Nw{yp NEٮi׌əIwOptwG_9{; ݷ2lQD.wNppwͮݗ`m+K"[swyGO5wlA;7 I-AwRd sjwMh nKmf@­-Ze_oo,t9$IhC43Qde&|&{n勴8l9mE"B;6oNf`B_`Z';p1aO\ rO̦fc\+*VyL!w`o}l*2QZ%U.lNQ̷2K܅L^wqX)La]c 5*mQ̳2gR$|>N×Ō ܗX'<3g"3 <`"I̡l p=3 L O2K-=[Lj6beDϜwgHy&xwq4gβTH搕Y0~M@pVVXb Y% 3gKUjfsw` ,%ei1[]L>-?< bWfl̀6U1#,?WNꁦYڟ'Lu++'̤lN6Q+%o7vĭ-֥5-XG|^cp=kNr-9|/VAivn-'5+︜pDwv)!!1)jŒ `cZ6ZsѤjrc޿[ND<%r8"F"|HTو A7&Y~#ܥw f"x[=826vEG<:"|h DlJUD@"$;eKj $@"$@"PP"PK HD L.rzA"`+z Wɥ xlc\y"P+X ~@p| VT+X ~@`(,EWm_!B!B!B!S_ . ,y8%O+0b2$ p$D1tt)9KH6" pc|Y2h1cёCEEcPդYK,3X4dfnيGWOnd΂#LsK zxr)gSrF$=lqq$I ͓#OrّBH'FNnvr4+n3raD} V7,@sNM8н_ x_ 1=bKGD3'| pHYs  tIME   ZJ4IDAT8Ա 0QY#@mzoAVCk,i73j7=IENDB`craftguide-1.11/textures/craftguide_shapeless.png000066400000000000000000000004611360474225700223410ustar00rootroot00000000000000PNG  IHDRw=sRGBgAMA a pHYsodIDATHKa u'3z-5ԄlYZM>J9'1EJ)«!sB|FO[xݙ ݰBԠt@V )QB.SI4r)\ZB%\JJ(IQ`aIbҊwWq -r+M!{깗.]{<@<2tIRqg2uZ{ NF29Vv Aٻӧ3WgBMzaJT3RPrƠC*azDE"ٰTm#Z󎅼fuozZFzYaRm:<';zuC9de:PeIxsyVᔩZjnPC!^p8ؗWHIBfYip{a{Դ4|u'qR^B)JpĎ2g[+}`#ga\D3_aSpިgVY[@ # & ){>su+TсMV(\'r&3b.A-ӑO'64A \N]k] v5Mc,z-T[ {x MBOp9n0>z+ ԨY_k5a" 4+~ 垛{Lr:3LJk˩C{L&nX( ܦx^oez.Γ-B; ޟON̑ޙUk{b\hYLH]| .)~w~++Xj>Պ7ǐ+R!3OTqawV%qBc#E/GNx+l\Û53]o*?[/s(&|7Ytc륜[9C^`[Εn*dwկ% I%q. pHYs.#.#x?vtIME  6̦`IDAT(mMOQ;ǝ P$@ QbB/pڥߠ[6n`Bbb*N?fڙw[Mgu>7w?P$Irqi,T/pC~q? #~ipכ+Ԧ7Ǧ禌$wl[d|skE=VqۍrcxnˍIKAKKttr>ʜtġjx~1%F+/?R]CfJD=CYjmܺvfa/+.NĦn)k[kʞ 8;;Ǥ$PWX0nʉF '+ \#a rDfT璫u=tTI5kHؽIb:gK!,zzDG|_lsU1;nƳedН0YSُ2iAuW0JVMVlm"N8-@: j9ReBPza`@.y>^]RـB$[w7v^iO,ZP xJQ,xX 5)!'ZC$rĐ)\^BNIENDB`