From bd5ea4e6a818ab48ee437ed3589634140a9a8762 Mon Sep 17 00:00:00 2001 From: Jean-Patrick Guerrero Date: Sun, 24 Oct 2021 23:31:01 +0200 Subject: [PATCH] Split init.lua into separate files (Part II) --- etc/api.lua | 288 +++++++++ etc/bags.lua | 97 +++ etc/common.lua | 169 ++++- etc/compress.lua | 2 +- etc/groups.lua | 2 +- etc/gui.lua | 36 +- etc/inventory.lua | 642 +++++++++++++++++++ etc/progressive.lua | 15 +- etc/recipes.lua | 314 +++++++++ etc/styles.lua | 4 +- init.lua | 1467 ++----------------------------------------- 11 files changed, 1549 insertions(+), 1487 deletions(-) create mode 100644 etc/api.lua create mode 100644 etc/bags.lua create mode 100644 etc/inventory.lua create mode 100644 etc/recipes.lua diff --git a/etc/api.lua b/etc/api.lua new file mode 100644 index 0000000..f261c97 --- /dev/null +++ b/etc/api.lua @@ -0,0 +1,288 @@ +local make_fs = i3.files.gui() + +local S, fmt, reg_items = i3.need("S", "fmt", "reg_items") +local gmatch, match, split = i3.need("gmatch", "match", "split") +local sort, concat, copy, insert, remove = i3.need("sort", "concat", "copy", "insert", "remove") +local true_str, is_str, is_table, clean_name = i3.need("true_str", "is_str", "is_table", "clean_name") + +local function err(str) + return core.log("error", str) +end + +local function is_func(x) + return type(x) == "function" +end + +function i3.register_craft_type(name, def) + if not true_str(name) then + return err "i3.register_craft_type: name missing" + end + + if not is_str(def.description) then + def.description = "" + end + + i3.craft_types[name] = def +end + +function i3.register_craft(def) + local width, c = 0, 0 + + if true_str(def.url) then + if not i3.http then + return err(fmt([[i3.register_craft(): Unable to reach %s. + No HTTP support for this mod: add it to the `secure.http_mods` or + `secure.trusted_mods` setting.]], def.url)) + end + + i3.http.fetch({url = def.url}, function(result) + if result.succeeded then + local t = core.parse_json(result.data) + if is_table(t) then + return i3.register_craft(t) + end + end + end) + + return + end + + if not is_table(def) or not next(def) then + return err "i3.register_craft: craft definition missing" + end + + if #def > 1 then + for _, v in pairs(def) do + i3.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 "i3.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 + 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] = fmt("%s,", items[i]) + end + end + + for name in gmatch(concat(items, ","), "[%s%w_:]+") do + c = c + 1 + def.items[c] = clean_name(name) + end + end + + local item = match(def.output, "%S+") + i3.recipes_cache[item] = i3.recipes_cache[item] or {} + + def.custom = true + def.width = width + + insert(i3.recipes_cache[item], def) +end + +function i3.add_recipe_filter(name, f) + if not true_str(name) then + return err "i3.add_recipe_filter: name missing" + elseif not is_func(f) then + return err "i3.add_recipe_filter: function missing" + end + + i3.recipe_filters[name] = f +end + +function i3.set_recipe_filter(name, f) + if not is_str(name) then + return err "i3.set_recipe_filter: name missing" + elseif not is_func(f) then + return err "i3.set_recipe_filter: function missing" + end + + i3.recipe_filters = {[name] = f} +end + +function i3.add_search_filter(name, f) + if not true_str(name) then + return err "i3.add_search_filter: name missing" + elseif not is_func(f) then + return err "i3.add_search_filter: function missing" + end + + i3.search_filters[name] = f +end + +function i3.get_recipes(item) + return { + recipes = i3.recipes_cache[item], + usages = i3.usages_cache[item] + } +end + +function i3.set_fs(player, _fs) + if not player or player.is_fake_player then return end + local name = player:get_player_name() + local data = i3.data[name] + if not data then return end + + local fs = fmt("%s%s", make_fs(player, data), _fs or "") + player:set_inventory_formspec(fs) +end + +local set_fs = i3.set_fs + +function i3.new_tab(def) + if not is_table(def) or not next(def) then + return err "i3.new_tab: tab definition missing" + end + + if not true_str(def.name) then + return err "i3.new_tab: tab name missing" + end + + if not true_str(def.description) then + return err "i3.new_tab: description missing" + end + + if #i3.tabs == 6 then + return err(fmt("i3.new_tab: cannot add '%s' tab. Limit reached (6).", def.name)) + end + + i3.tabs[#i3.tabs + 1] = def +end + +function i3.remove_tab(tabname) + if not true_str(tabname) then + return err "i3.remove_tab: tab name missing" + end + + for i, def in ipairs(i3.tabs) do + if tabname == def.name then + remove(i3.tabs, i) + end + end +end + +function i3.get_current_tab(player) + local name = player:get_player_name() + local data = i3.data[name] + + return data.current_tab +end + +function i3.set_tab(player, tabname) + local name = player:get_player_name() + local data = i3.data[name] + + if not tabname or tabname == "" then + data.current_tab = 0 + return + end + + local found + + for i, def in ipairs(i3.tabs) do + if not found and def.name == tabname then + data.current_tab = i + found = true + end + end + + if not found then + return err(fmt("i3.set_tab: tab name '%s' does not exist", tabname)) + end +end + +function i3.override_tab(tabname, newdef) + if not is_table(newdef) or not next(newdef) then + return err "i3.override_tab: tab definition missing" + end + + if not true_str(newdef.name) then + return err "i3.override_tab: tab name missing" + end + + if not true_str(newdef.description) then + return err "i3.override_tab: description missing" + end + + for i, def in ipairs(i3.tabs) do + if def.name == tabname then + i3.tabs[i] = newdef + end + end +end + +i3.register_craft_type("digging", { + description = S"Digging", + icon = "i3_steelpick.png", +}) + +i3.register_craft_type("digging_chance", { + description = S"Digging (by chance)", + icon = "i3_mesepick.png", +}) + +i3.add_search_filter("groups", function(item, groups) + local def = reg_items[item] + local has_groups = true + + for _, group in ipairs(groups) do + if not def.groups[group] then + has_groups = nil + break + end + end + + return has_groups +end) + +return set_fs, i3.set_tab diff --git a/etc/bags.lua b/etc/bags.lua new file mode 100644 index 0000000..e71f949 --- /dev/null +++ b/etc/bags.lua @@ -0,0 +1,97 @@ +local set_fs = i3.files.api() +local S, fmt, msg, spawn_item = i3.need("S", "fmt", "msg", "spawn_item") + +local function init_backpack(player) + local name = player:get_player_name() + local data = i3.data[name] + local inv = player:get_inventory() + + inv:set_size("main", data.bag_size and i3.BAG_SIZES[data.bag_size] or i3.INV_SIZE) + + data.bag = core.create_detached_inventory(fmt("%s_backpack", name), { + allow_put = function(_inv, listname, _, stack) + local empty = _inv:get_stack(listname, 1):is_empty() + local item_group = minetest.get_item_group(stack:get_name(), "bag") + + if empty and item_group > 0 and item_group <= #i3.BAG_SIZES then + return 1 + end + + msg(name, S"This is not a backpack") + + return 0 + end, + + on_put = function(_, _, _, stack) + local stackname = stack:get_name() + data.bag_item = stackname + data.bag_size = minetest.get_item_group(stackname, "bag") + + inv:set_size("main", i3.BAG_SIZES[data.bag_size]) + set_fs(player) + end, + + on_take = function() + for i = i3.INV_SIZE + 1, i3.BAG_SIZES[data.bag_size] do + local stack = inv:get_stack("main", i) + + if not stack:is_empty() then + spawn_item(player, stack) + end + end + + data.bag_item = nil + data.bag_size = nil + + inv:set_size("main", i3.INV_SIZE) + set_fs(player) + end, + }) + + data.bag:set_size("main", 1) + + if data.bag_item then + data.bag:set_stack("main", 1, data.bag_item) + end +end + +local bag_recipes = { + small = { + rcp = { + {"", "farming:string", ""}, + {"group:wool", "group:wool", "group:wool"}, + {"group:wool", "group:wool", "group:wool"}, + }, + size = 1, + }, + medium = { + rcp = { + {"farming:string", "i3:bag_small", "farming:string"}, + {"farming:string", "i3:bag_small", "farming:string"}, + }, + size = 2, + }, + large = { + rcp = { + {"farming:string", "i3:bag_medium", "farming:string"}, + {"farming:string", "i3:bag_medium", "farming:string"}, + }, + size = 3, + }, +} + +for size, item in pairs(bag_recipes) do + local bagname = fmt("i3:bag_%s", size) + + core.register_craftitem(bagname, { + description = fmt("%s Backpack", size:gsub("^%l", string.upper)), + inventory_image = fmt("i3_bag_%s.png", size), + stack_max = 1, + groups = {bag = item.size} + }) + + core.register_craft {output = bagname, recipe = item.rcp} + core.register_craft {type = "fuel", recipe = bagname, burntime = 3} +end + +return init_backpack diff --git a/etc/common.lua b/etc/common.lua index ec4a69e..65ff741 100644 --- a/etc/common.lua +++ b/etc/common.lua @@ -1,9 +1,8 @@ -local item_compression = core.settings:get_bool("i3_item_compression", true) -local reg_items, translate = core.registered_items, core.get_translated_string - +local translate = core.get_translated_string local fmt, find, gmatch, match, sub, split, lower = - string.format, string.find, string.gmatch, string.match, - string.sub, string.split, string.lower + string.format, string.find, string.gmatch, string.match, string.sub, string.split, string.lower +local reg_items, reg_nodes, reg_craftitems, reg_tools = + core.registered_items, core.registered_nodes, core.registered_craftitems, core.registered_tools local function reset_compression(data) data.alt_items = nil @@ -156,7 +155,7 @@ local function apply_recipe_filters(recipes, player) end local function compression_active(data) - return item_compression and not next(i3.recipe_filters) and data.filter == "" + return i3.item_compression and not next(i3.recipe_filters) and data.filter == "" end local function compressible(item, data) @@ -167,6 +166,10 @@ local function is_str(x) return type(x) == "string" end +local function is_table(x) + return type(x) == "table" +end + local function true_str(str) return is_str(str) and str ~= "" end @@ -184,27 +187,147 @@ local function is_fav(favs, query_item) return fav, i end +local function sort_by_category(data) + reset_compression(data) + local items = data.items_raw + + if data.filter ~= "" then + search(data) + items = data.items + end + + local new = {} + + for i = 1, #items do + local item = items[i] + local to_add = true + + if data.current_itab == 2 then + to_add = reg_nodes[item] + elseif data.current_itab == 3 then + to_add = reg_craftitems[item] or reg_tools[item] + end + + if to_add then + new[#new + 1] = item + end + end + + data.items = new +end + +local function clean_name(item) + if sub(item, 1, 1) == ":" or sub(item, 1, 1) == " " or sub(item, 1, 1) == "_" then + item = sub(item, 2) + end + + return item +end + +local function msg(name, str) + return core.chat_send_player(name, fmt("[i3] %s", str)) +end + +local function spawn_item(player, stack) + local dir = player:get_look_dir() + local ppos = player:get_pos() + ppos.y = ppos.y + 1.625 + local look_at = vector.add(ppos, vector.multiply(dir, 1)) + + core.add_item(look_at, stack) +end + +local S = core.get_translator "i3" +local ES = function(...) return core.formspec_escape(S(...)) end + return { - init = { - is_str, - show_item, - reset_compression, + groups = { + is_group = is_group, + extract_groups = extract_groups, + item_has_groups = item_has_groups, + groups_to_items = groups_to_items, }, - progressive = { - search, - table_merge, - is_group, - extract_groups, - item_has_groups, - apply_recipe_filters, + compression = { + compressible = compressible, + compression_active = compression_active, }, - gui = { - groups_to_items, - compression_active, - compressible, - true_str, - is_fav, + sorting = { + search = search, + sort_by_category = sort_by_category, + apply_recipe_filters = apply_recipe_filters, + }, + + misc = { + msg = msg, + is_fav = is_fav, + show_item = show_item, + spawn_item = spawn_item, + table_merge = table_merge, + }, + + core = { + clr = core.colorize, + ESC = core.formspec_escape, + check_privs = core.check_player_privs, + }, + + reg = { + reg_items = core.registered_items, + reg_nodes = core.registered_nodes, + reg_craftitems = core.registered_craftitems, + reg_tools = core.registered_tools, + reg_entities = core.registered_entities, + reg_aliases = core.registered_aliases, + }, + + i18n = { + S = S, + ES = ES, + translate = core.get_translated_string, + }, + + string = { + fmt = string.format, + find = string.find, + gmatch = string.gmatch, + match = string.match, + sub = string.sub, + split = string.split, + upper = string.upper, + lower = string.lower, + + is_str = is_str, + true_str = true_str, + clean_name = clean_name, + }, + + table = { + maxn = table.maxn, + sort = table.sort, + concat = table.concat, + copy = table.copy, + insert = table.insert, + remove = table.remove, + indexof = table.indexof, + + is_table = is_table, + }, + + math = { + min = math.min, + max = math.max, + floor = math.floor, + ceil = math.ceil, + random = math.random, + }, + + vec = { + vec_new = vector.new, + vec_add = vector.add, + vec_mul = vector.multiply, + vec_eq = vector.equals, + vec_round = vector.round, }, } diff --git a/etc/compress.lua b/etc/compress.lua index a61560b..b7cd6c8 100644 --- a/etc/compress.lua +++ b/etc/compress.lua @@ -1,4 +1,4 @@ -local fmt, insert, copy, pairs, ipairs = string.format, table.insert, table.copy, pairs, ipairs +local fmt, copy, insert = i3.need("fmt", "copy", "insert") local wood_types = { "acacia_wood", "aspen_wood", "junglewood", "pine_wood", diff --git a/etc/groups.lua b/etc/groups.lua index ea126e9..32d7297 100644 --- a/etc/groups.lua +++ b/etc/groups.lua @@ -1,4 +1,4 @@ -local S = core.get_translator "i3" +local S = i3.need("S") local group_stereotypes = { dye = "dye:white", diff --git a/etc/gui.lua b/etc/gui.lua index 77f799c..2415170 100644 --- a/etc/gui.lua +++ b/etc/gui.lua @@ -1,34 +1,18 @@ local damage_enabled = core.settings:get_bool "enable_damage" -local reg_items = core.registered_items -local reg_entities = core.registered_entities -local reg_tools = core.registered_tools - -local pairs, ipairs, next, type, setmetatable, unpack, select = - pairs, ipairs, next, type, setmetatable, unpack, select - -local sprintf, sub, find, match, upper = - string.format, string.sub, string.find, string.match, string.upper - -local concat, sort, insert, remove, maxn, copy = - table.concat, table.sort, table.insert, table.remove, table.maxn, - table.copy - -local min, max, floor, ceil = math.min, math.max, math.floor, math.ceil - -local clr, ESC, check_privs, translate = - core.colorize, core.formspec_escape, core.check_player_privs, core.get_translated_string - local model_aliases = i3.files.model_alias() local PNG, styles, fs_elements = i3.files.styles() -local _, _, is_group, extract_groups, item_has_groups = unpack(i3.files.common().progressive) -local groups_to_items, compression_active, compressible, true_str, is_fav = unpack(i3.files.common().gui) -local S = i3.S +local clr, ESC = i3.need("clr", "ESC") +local S, ES, translate = i3.need("S", "ES", "translate") +local min, max, floor, ceil = i3.need("min", "max", "floor", "ceil") +local sprintf, find, match, sub, upper = i3.need("fmt", "find", "match", "sub", "upper") +local reg_items, reg_tools, reg_entities = i3.need("reg_items", "reg_tools", "reg_entities") +local maxn, sort, concat, copy, insert, remove = i3.need("maxn", "sort", "concat", "copy", "insert", "remove") -local ES = function(...) - return ESC(S(...)) -end +local is_group, extract_groups, item_has_groups = i3.need("is_group", "extract_groups", "item_has_groups") +local groups_to_items, compression_active, compressible, true_str, is_fav = + i3.need("groups_to_items", "compression_active", "compressible", "true_str", "is_fav") local function is_num(x) return type(x) == "number" @@ -306,7 +290,7 @@ local function get_waypoint_fs(fs, data, player, yextra, ctn_len) fs("image_button", ctn_len - 1.5, yi, icon_size, icon_size, "", vsb, "") fs(fmt("tooltip[%s;%s]", vsb, v.hide and ES"Show waypoint" or ES"Hide waypoint")) - if check_privs(player, {teleport = true}) then + if core.check_player_privs(player, {teleport = true}) then local tp = fmt("waypoint_%u_teleport", i) fs(fmt("style[%s;fgimg=%s;fgimg_hovered=%s;content_offset=0]", diff --git a/etc/inventory.lua b/etc/inventory.lua new file mode 100644 index 0000000..1792b69 --- /dev/null +++ b/etc/inventory.lua @@ -0,0 +1,642 @@ +local set_fs, set_tab = i3.files.api() +local _, get_inventory_fs = i3.files.gui() + +local S, clr = i3.need("S", "clr") +local min, ceil, random = i3.need("min", "ceil", "random") +local reg_items, reg_aliases = i3.need("reg_items", "reg_aliases") +local fmt, find, match, sub, lower = i3.need("fmt", "find", "match", "sub", "lower") +local vec_new, vec_mul, vec_eq, vec_round = i3.need("vec_new", "vec_mul", "vec_eq", "vec_round") +local sort, copy, insert, remove, indexof = i3.need("sort", "copy", "insert", "remove", "indexof") + +local is_group, extract_groups, groups_to_items = i3.need("is_group", "extract_groups", "groups_to_items") +local search, sort_by_category, apply_recipe_filters = i3.need("search", "sort_by_category", "apply_recipe_filters") +local msg, is_str, is_fav, show_item, spawn_item, clean_name, compressible = + i3.need("msg", "is_str", "is_fav", "show_item", "spawn_item", "clean_name", "compressible") + +local old_is_creative_enabled = core.is_creative_enabled + +function core.is_creative_enabled(name) + if name == "" then + return old_is_creative_enabled(name) + end + + return core.check_player_privs(name, {creative = true}) or old_is_creative_enabled(name) +end + +local function reset_data(data) + data.filter = "" + data.expand = "" + data.pagenum = 1 + data.rnum = 1 + data.unum = 1 + data.scrbar_rcp = 1 + data.scrbar_usg = 1 + data.query_item = nil + data.recipes = nil + data.usages = nil + data.export_rcp = nil + data.export_usg = nil + data.alt_items = nil + data.confirm_trash = nil + data.items = data.items_raw + + if data.current_itab > 1 then + sort_by_category(data) + end +end + +local function get_recipes(player, item) + local clean_item = reg_aliases[item] or item + local recipes = i3.recipes_cache[clean_item] + local usages = i3.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 end + usages = apply_recipe_filters(usages, player) + + 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 __sort(inv, reverse) + sort(inv, function(a, b) + if not is_str(a) then + a = a:get_name() + end + + if not is_str(b) then + b = b:get_name() + end + + if reverse then + return a > b + end + + return a < b + end) +end + +local function sort_itemlist(player, az) + local inv = player:get_inventory() + local list = inv:get_list("main") + local size = inv:get_size("main") + local new_inv, stack_meta = {}, {} + + for i = 1, size do + local stack = list[i] + local name = stack:get_name() + local count = stack:get_count() + local empty = stack:is_empty() + local meta = stack:get_meta():to_table() + local wear = stack:get_wear() > 0 + + if not empty then + if next(meta.fields) or wear then + stack_meta[#stack_meta + 1] = stack + else + new_inv[#new_inv + 1] = fmt("%s %u", name, count) + end + end + end + + for i = 1, #stack_meta do + new_inv[#new_inv + 1] = stack_meta[i] + end + + if az then + __sort(new_inv) + else + __sort(new_inv, true) + end + + inv:set_list("main", new_inv) +end + +local function compress_items(player) + local inv = player:get_inventory() + local list = inv:get_list("main") + local size = inv:get_size("main") + local new_inv, _new_inv, special = {}, {}, {} + + for i = 1, size do + local stack = list[i] + local name = stack:get_name() + local count = stack:get_count() + local stackmax = stack:get_stack_max() + local empty = stack:is_empty() + local meta = stack:get_meta():to_table() + local wear = stack:get_wear() > 0 + + if not empty then + if next(meta.fields) or wear or count >= stackmax then + special[#special + 1] = stack + else + new_inv[name] = new_inv[name] or 0 + new_inv[name] = new_inv[name] + count + end + end + end + + for name, count in pairs(new_inv) do + local stackmax = ItemStack(name):get_stack_max() + local iter = ceil(count / stackmax) + local leftover = count + + for _ = 1, iter do + _new_inv[#_new_inv + 1] = fmt("%s %u", name, min(stackmax, leftover)) + leftover = leftover - stackmax + end + end + + for i = 1, #special do + _new_inv[#_new_inv + 1] = special[i] + end + + __sort(_new_inv) + inv:set_list("main", _new_inv) +end + +local function get_stack(player, stack) + local inv = player:get_inventory() + + if inv:room_for_item("main", stack) then + inv:add_item("main", stack) + else + spawn_item(player, stack) + end +end + +i3.new_tab { + name = "inventory", + description = S"Inventory", + formspec = get_inventory_fs, + + fields = function(player, data, fields) + local name = player:get_player_name() + local sb_inv = fields.scrbar_inv + + if fields.skins then + local id = tonumber(fields.skins) + local _skins = skins.get_skinlist_for_player(name) + skins.set_player_skin(player, _skins[id]) + end + + for field in pairs(fields) do + if sub(field, 1, 4) == "btn_" then + data.subcat = indexof(i3.SUBCAT, sub(field, 5)) + break + + elseif find(field, "waypoint_%d+") then + local id, action = match(field, "_(%d+)_(%w+)$") + id = tonumber(id) + local waypoint = data.waypoints[id] + if not waypoint then return end + + if action == "delete" then + player:hud_remove(waypoint.id) + remove(data.waypoints, id) + + elseif action == "teleport" then + local pos = vec_new(waypoint.pos) + pos.y = pos.y + 0.5 + + local vel = player:get_velocity() + player:add_velocity(vec_mul(vel, -1)) + player:set_pos(pos) + + msg(name, fmt("Teleported to %s", clr("#ff0", waypoint.name))) + + elseif action == "refresh" then + local color = random(0xffffff) + waypoint.color = color + player:hud_change(waypoint.id, "number", color) + + elseif action == "hide" then + if waypoint.hide then + local new_id = player:hud_add { + hud_elem_type = "waypoint", + name = waypoint.name, + text = " m", + world_pos = waypoint.pos, + number = waypoint.color, + z_index = -300, + } + + waypoint.id = new_id + waypoint.hide = nil + else + player:hud_remove(waypoint.id) + waypoint.hide = true + end + end + + break + end + end + + if fields.trash then + data.confirm_trash = true + + elseif fields.confirm_trash_yes or fields.confirm_trash_no then + if fields.confirm_trash_yes then + local inv = player:get_inventory() + inv:set_list("main", {}) + inv:set_list("craft", {}) + end + + data.confirm_trash = nil + + elseif fields.compress then + compress_items(player) + + elseif fields.sort_az or fields.sort_za then + sort_itemlist(player, fields.sort_az) + + elseif sb_inv and sub(sb_inv, 1, 3) == "CHG" then + data.scrbar_inv = tonumber(match(sb_inv, "%d+")) + return + + elseif fields.waypoint_add then + local pos = player:get_pos() + + for _, v in ipairs(data.waypoints) do + if vec_eq(vec_round(pos), vec_round(v.pos)) then + return msg(name, "You already set a waypoint at this position") + end + end + + local waypoint = fields.waypoint_name + + if fields.waypoint_name == "" then + waypoint = "Waypoint" + end + + local color = random(0xffffff) + + local id = player:hud_add { + hud_elem_type = "waypoint", + name = waypoint, + text = " m", + world_pos = pos, + number = color, + z_index = -300, + } + + insert(data.waypoints, {name = waypoint, pos = pos, color = color, id = id}) + data.scrbar_inv = data.scrbar_inv + 1000 + end + + return set_fs(player) + end, +} + +local function select_item(player, name, data, _f) + local item + + for field in pairs(_f) do + if find(field, ":") then + item = field + break + end + end + + if not item then return end + + if compressible(item, data) then + local idx + + for i = 1, #data.items do + local it = data.items[i] + if it == item then + idx = i + break + end + end + + if data.expand ~= "" then + data.alt_items = nil + + if item == data.expand then + data.expand = nil + return + end + end + + if idx and item ~= data.expand then + data.alt_items = copy(data.items) + data.expand = item + + if i3.compress_groups[item] then + local items = copy(i3.compress_groups[item]) + insert(items, fmt("_%s", item)) + + sort(items, function(a, b) + if a:sub(1, 1) == "_" then + a = a:sub(2) + end + + return a < b + end) + + local i = 1 + + for _, v in ipairs(items) do + if show_item(reg_items[clean_name(v)]) then + insert(data.alt_items, idx + i, v) + i = i + 1 + end + end + end + end + else + if sub(item, 1, 1) == "_" then + item = sub(item, 2) + elseif sub(item, 1, 6) == "group|" then + item = match(item, "([%w:_]+)$") + end + + item = reg_aliases[item] or item + if not reg_items[item] then return end + + if core.is_creative_enabled(name) then + local stack = ItemStack(item) + local stackmax = stack:get_stack_max() + stack = fmt("%s %s", item, stackmax) + + return get_stack(player, stack) + end + + if item == data.query_item then return end + local recipes, usages = get_recipes(player, item) + + data.query_item = item + data.recipes = recipes + data.usages = usages + data.rnum = 1 + data.unum = 1 + data.scrbar_rcp = 1 + data.scrbar_usg = 1 + data.export_rcp = nil + data.export_usg = nil + end +end + +local function craft_stack(player, data, craft_rcp) + local inv = player:get_inventory() + local rcp_usg = craft_rcp and "recipe" or "usage" + local output = craft_rcp and data.recipes[data.rnum].output or data.usages[data.unum].output + output = ItemStack(output) + local stackname, stackcount, stackmax = output:get_name(), output:get_count(), output:get_stack_max() + local scrbar_val = data[fmt("scrbar_%s", craft_rcp and "rcp" or "usg")] or 1 + + for name, count in pairs(data.export_counts[rcp_usg].rcp) do + local items = {[name] = count} + + if is_group(name) then + items = {} + local groups = extract_groups(name) + local item_groups = groups_to_items(groups, true) + local remaining = count + + for _, item in ipairs(item_groups) do + for _name, _count in pairs(data.export_counts[rcp_usg].inv) do + if item == _name and remaining > 0 then + local c = min(remaining, _count) + items[item] = c + remaining = remaining - c + end + + if remaining == 0 then break end + end + end + end + + for k, v in pairs(items) do + inv:remove_item("main", fmt("%s %s", k, v * scrbar_val)) + end + end + + local count = stackcount * scrbar_val + local iter = ceil(count / stackmax) + local leftover = count + + for _ = 1, iter do + local c = min(stackmax, leftover) + local stack = ItemStack(fmt("%s %s", stackname, c)) + get_stack(player, stack) + leftover = leftover - stackmax + end +end + +local function rcp_fields(player, data, fields) + local name = player:get_player_name() + local sb_rcp, sb_usg = fields.scrbar_rcp, fields.scrbar_usg + + if fields.cancel then + reset_data(data) + + elseif fields.exit then + data.query_item = nil + + elseif fields.key_enter_field == "filter" or fields.search then + if fields.filter == "" then + reset_data(data) + return set_fs(player) + end + + local str = lower(fields.filter) + if data.filter == str then return end + + data.filter = str + data.pagenum = 1 + + search(data) + + if data.current_itab > 1 then + sort_by_category(data) + end + + elseif fields.prev_page or fields.next_page then + if data.pagemax == 1 then return end + data.pagenum = data.pagenum - (fields.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 + + elseif fields.prev_recipe or fields.next_recipe then + local num = data.rnum + (fields.prev_recipe and -1 or 1) + data.rnum = data.recipes[num] and num or (fields.prev_recipe and #data.recipes or 1) + data.export_rcp = nil + data.scrbar_rcp = 1 + + elseif fields.prev_usage or fields.next_usage then + local num = data.unum + (fields.prev_usage and -1 or 1) + data.unum = data.usages[num] and num or (fields.prev_usage and #data.usages or 1) + data.export_usg = nil + data.scrbar_usg = 1 + + elseif fields.fav then + local fav, i = is_fav(data.favs, data.query_item) + local total = #data.favs + + if total < i3.MAX_FAVS and not fav then + data.favs[total + 1] = data.query_item + elseif fav then + remove(data.favs, i) + end + + elseif fields.export_rcp or fields.export_usg then + if fields.export_rcp then + data.export_rcp = not data.export_rcp + + if not data.export_rcp then + data.scrbar_rcp = 1 + end + else + data.export_usg = not data.export_usg + + if not data.export_usg then + data.scrbar_usg = 1 + end + end + + elseif (sb_rcp and sub(sb_rcp, 1, 3) == "CHG") or (sb_usg and sub(sb_usg, 1, 3) == "CHG") then + data.scrbar_rcp = sb_rcp and tonumber(match(sb_rcp, "%d+")) + data.scrbar_usg = sb_usg and tonumber(match(sb_usg, "%d+")) + + elseif fields.craft_rcp or fields.craft_usg then + craft_stack(player, data, fields.craft_rcp) + + if fields.craft_rcp then + data.export_rcp = nil + data.scrbar_rcp = 1 + else + data.export_usg = nil + data.scrbar_usg = 1 + end + else + select_item(player, name, data, fields) + end +end + +core.register_on_player_receive_fields(function(player, formname, fields) + local name = player:get_player_name() + + if formname == "i3_outdated" then + return false, core.kick_player(name, "Come back when your client is up-to-date.") + elseif formname ~= "" then + return false + end + + local data = i3.data[name] + if not data then return end + + for f in pairs(fields) do + if sub(f, 1, 4) == "tab_" then + local tabname = sub(f, 5) + set_tab(player, tabname) + break + elseif sub(f, 1, 5) == "itab_" then + data.pagenum = 1 + data.current_itab = tonumber(f:sub(-1)) + sort_by_category(data) + end + end + + rcp_fields(player, data, fields) + + local tab = i3.tabs[data.current_tab] + + if tab and tab.fields then + return true, tab.fields(player, data, fields) + end + + return true, set_fs(player) +end) + +core.register_on_player_hpchange(function(player, hpchange) + local name = player:get_player_name() + local data = i3.data[name] + if not data then return end + + local hp_max = player:get_properties().hp_max + data.hp = min(hp_max, player:get_hp() + hpchange) + + set_fs(player) +end) + +core.register_on_dieplayer(function(player) + local name = player:get_player_name() + local data = i3.data[name] + if not data then return end + + if data.bag_size then + data.bag_item = nil + data.bag_size = nil + data.bag:set_list("main", {}) + + local inv = player:get_inventory() + inv:set_size("main", i3.INV_SIZE) + end + + set_fs(player) +end) + +core.register_on_chatcommand(function(name) + local player = core.get_player_by_name(name) + core.after(0, set_fs, player) +end) + +core.register_on_priv_grant(function(name, _, priv) + if priv == "creative" or priv == "all" then + local data = i3.data[name] + reset_data(data) + data.favs = {} + + local player = core.get_player_by_name(name) + core.after(0, set_fs, player) + end +end) + +core.register_on_player_inventory_action(function(player, _, _, info) + local name = player:get_player_name() + + if not core.is_creative_enabled(name) and + ((info.from_list == "main" and info.to_list == "craft") or + (info.from_list == "craft" and info.to_list == "main") or + (info.from_list == "craftresult" and info.to_list == "main")) then + set_fs(player) + end +end) + +local trash = core.create_detached_inventory("i3_trash", { + allow_put = function(_, _, _, stack) + return stack:get_count() + end, + on_put = function(inv, listname, _, _, player) + inv:set_list(listname, {}) + + local name = player:get_player_name() + + if not core.is_creative_enabled(name) then + set_fs(player) + end + end, +}) + +trash:set_size("main", 1) + +local output_rcp = core.create_detached_inventory("i3_output_rcp", {}) +output_rcp:set_size("main", 1) + +local output_usg = core.create_detached_inventory("i3_output_usg", {}) +output_usg:set_size("main", 1) diff --git a/etc/progressive.lua b/etc/progressive.lua index 9f46b60..4fc72fa 100644 --- a/etc/progressive.lua +++ b/etc/progressive.lua @@ -1,13 +1,12 @@ +local set_fs = i3.files.api() +local singleplayer = core.is_singleplayer() + +local fmt, search, table_merge, is_group, extract_groups, item_has_groups, apply_recipe_filters = + i3.need("fmt", "search", "table_merge", "is_group", "extract_groups", "item_has_groups", "apply_recipe_filters") + local POLL_FREQ = 0.25 local HUD_TIMER_MAX = 1.5 -local search, table_merge, is_group, extract_groups, item_has_groups, apply_recipe_filters = - unpack(i3.files.common().progressive) - -local singleplayer = core.is_singleplayer() -local fmt, after, pairs = string.format, core.after, pairs -local set_fs = i3.set_fs - local function array_diff(t1, t2) local hash = {} @@ -255,7 +254,7 @@ local function poll_new_items() end end - after(POLL_FREQ, poll_new_items) + core.after(POLL_FREQ, poll_new_items) end poll_new_items() diff --git a/etc/recipes.lua b/etc/recipes.lua new file mode 100644 index 0000000..1054fde --- /dev/null +++ b/etc/recipes.lua @@ -0,0 +1,314 @@ +local replacements = {fuel = {}} + +local fmt, match = i3.need("fmt", "match") +local reg_items, reg_aliases = i3.need("reg_items", "reg_aliases") +local maxn, copy, insert, remove = i3.need("maxn", "copy", "insert", "remove") + +local true_str, is_table, show_item, table_merge = i3.need("true_str", "is_table", "show_item", "table_merge") +local is_group, extract_groups, item_has_groups, groups_to_items = + i3.need("is_group", "extract_groups", "item_has_groups", "groups_to_items") + +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 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 get_burntime(item) + return core.get_craft_result{method = "fuel", items = {item}}.time +end + +local function cache_fuel(item) + local burntime = get_burntime(item) + if burntime > 0 then + i3.fuel_cache[item] = { + type = "fuel", + items = {item}, + burntime = burntime, + replacements = replacements.fuel[item], + } + end +end + +local function get_item_usages(item, recipe, added) + local groups = extract_groups(item) + + if groups then + for name, def in pairs(reg_items) do + if not added[name] and show_item(def) and item_has_groups(def.groups, groups) then + local usage = copy(recipe) + table_replace(usage.items, item, name) + + i3.usages_cache[name] = i3.usages_cache[name] or {} + insert(i3.usages_cache[name], 1, usage) + + added[name] = true + end + end + elseif show_item(reg_items[item]) then + i3.usages_cache[item] = i3.usages_cache[item] or {} + insert(i3.usages_cache[item], 1, recipe) + end +end + +local function get_usages(recipe) + local added = {} + + for _, item in pairs(recipe.items) do + item = reg_aliases[item] or item + + if not added[item] then + get_item_usages(item, recipe, added) + added[item] = true + end + end +end + +local function cache_usages(item) + local recipes = i3.recipes_cache[item] or {} + + for i = 1, #recipes do + get_usages(recipes[i]) + end + + if i3.fuel_cache[item] then + i3.usages_cache[item] = table_merge(i3.usages_cache[item] or {}, {i3.fuel_cache[item]}) + end +end + +local function drop_table(name, drop) + local count_sure = 0 + local drop_items = drop.items or {} + local max_items = drop.max_items + + for i = 1, #drop_items do + local di = drop_items[i] + local valid_rarity = di.rarity and di.rarity > 1 + + if di.rarity or not max_items or + (max_items and not di.rarity and count_sure < max_items) then + for j = 1, #di.items do + local dstack = ItemStack(di.items[j]) + local dname = dstack:get_name() + local dcount = dstack:get_count() + local empty = dstack:is_empty() + + if not empty and (dname ~= name or (dname == name and dcount > 1)) then + local rarity = valid_rarity and di.rarity + + i3.register_craft { + type = rarity and "digging_chance" or "digging", + items = {name}, + output = fmt("%s %u", dname, dcount), + rarity = rarity, + tools = di.tools, + } + end + end + end + + if not di.rarity then + count_sure = count_sure + 1 + end + end +end + +local function cache_drops(name, drop) + if true_str(drop) then + local dstack = ItemStack(drop) + local dname = dstack:get_name() + local empty = dstack:is_empty() + + if not empty and dname ~= name then + i3.register_craft { + type = "digging", + items = {name}, + output = drop, + } + end + elseif is_table(drop) then + drop_table(name, drop) + end +end + +local function cache_recipes(item) + local recipes = core.get_all_craft_recipes(item) + + if replacements[item] then + local _recipes = {} + + for k, v in ipairs(recipes or {}) do + _recipes[#recipes + 1 - k] = v + end + + local shift = 0 + local size_rpl = maxn(replacements[item]) + local size_rcp = #_recipes + + if size_rpl > size_rcp then + shift = size_rcp - size_rpl + end + + for k, v in pairs(replacements[item]) do + k = k + shift + + if _recipes[k] then + _recipes[k].replacements = v + end + end + + recipes = _recipes + end + + if recipes then + i3.recipes_cache[item] = table_merge(recipes, i3.recipes_cache[item] or {}) + end +end + +--[[ As `core.get_craft_recipe` and `core.get_all_craft_recipes` do not + return the fuel, replacements and toolrepair recipes, we have to + override `core.register_craft` and do some reverse engineering. + See engine's issues #4901, #5745 and #8920. ]] + +local old_register_craft = core.register_craft +local rcp_num = {} + +core.register_craft = function(def) + old_register_craft(def) + + if def.type == "toolrepair" then + i3.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 item = output[i] + rcp_num[item] = (rcp_num[item] or 0) + 1 + + if def.replacements then + if def.type == "fuel" then + replacements.fuel[item] = def.replacements + else + replacements[item] = replacements[item] or {} + replacements[item][rcp_num[item]] = def.replacements + end + end + end +end + +local old_clear_craft = core.clear_craft + +core.clear_craft = function(def) + old_clear_craft(def) + + if true_str(def) then + return -- TODO + elseif is_table(def) then + return -- TODO + end +end + +local function resolve_aliases(hash) + for oldname, newname in pairs(reg_aliases) do + cache_recipes(oldname) + local recipes = i3.recipes_cache[oldname] + + if recipes then + if not i3.recipes_cache[newname] then + i3.recipes_cache[newname] = {} + end + + local similar + + for i = 1, #i3.recipes_cache[oldname] do + local rcp_old = i3.recipes_cache[oldname][i] + + for j = 1, #i3.recipes_cache[newname] do + local rcp_new = copy(i3.recipes_cache[newname][j]) + rcp_new.output = oldname + + if table_eq(rcp_old, rcp_new) then + similar = true + break + end + end + + if not similar then + insert(i3.recipes_cache[newname], rcp_old) + end + end + end + + if newname ~= "" and i3.recipes_cache[oldname] and not hash[newname] then + i3.init_items[#i3.init_items + 1] = newname + end + end +end + +return cache_drops, cache_fuel, cache_recipes, cache_usages, resolve_aliases diff --git a/etc/styles.lua b/etc/styles.lua index fd3fa10..a083afc 100644 --- a/etc/styles.lua +++ b/etc/styles.lua @@ -1,5 +1,3 @@ -local fmt = string.format - local PNG = { bg = "i3_bg.png", bg_full = "i3_bg_full.png", @@ -62,7 +60,7 @@ local PNG = { exit_hover = "i3_exit.png^\\[brighten", } -local styles = fmt([[ +local styles = string.format([[ style_type[field;border=false;bgcolor=transparent] style_type[label,field;font_size=16] style_type[button;border=false;content_offset=0] diff --git a/init.lua b/init.lua index 16cbcbd..1fe015e 100644 --- a/init.lua +++ b/init.lua @@ -6,6 +6,7 @@ end i3 = { modules = {}, + http = core.request_http_api(), MAX_FAVS = 6, INV_SIZE = 4*9, @@ -47,146 +48,56 @@ i3 = { tabs = {}, files = { + api = lf("/etc/api.lua"), + bags = lf("/etc/bags.lua"), common = lf("/etc/common.lua"), compress = lf("/etc/compress.lua"), groups = lf("/etc/groups.lua"), gui = lf("/etc/gui.lua"), + inventory = lf("/etc/inventory.lua"), model_alias = lf("/etc/model_aliases.lua"), progressive = lf("/etc/progressive.lua"), + recipes = lf("/etc/recipes.lua"), styles = lf("/etc/styles.lua"), - } + }, + + progressive_mode = core.settings:get_bool "i3_progressive_mode", + item_compression = core.settings:get_bool("i3_item_compression", true), } -local http = core.request_http_api() -local storage = core.get_mod_storage() +local common = i3.files.common() -i3.S = core.get_translator "i3" -local S, slz, dslz = i3.S, core.serialize, core.deserialize +function i3.need(...) + local t = {} + + for _, var in ipairs {...} do + for _, cat in pairs(common) do + for name, func in pairs(cat) do + if var == name then + t[#t + 1] = func + break + end + end + end + end + + return unpack(t) +end + +local storage = core.get_mod_storage() +local slz, dslz = core.serialize, core.deserialize i3.data = dslz(storage:get_string "data") or {} i3.compress_groups, i3.compressed = i3.files.compress() i3.group_stereotypes, i3.group_names = i3.files.groups() -local is_str, show_item, reset_compression = unpack(i3.files.common().init) -local groups_to_items, _, compressible, true_str, is_fav = unpack(i3.files.common().gui) +local set_fs = i3.files.api() +local init_backpack = i3.files.bags() +i3.files.inventory() +local cache_drops, cache_fuel, cache_recipes, cache_usages, resolve_aliases = i3.files.recipes() -local search, table_merge, is_group, extract_groups, item_has_groups, apply_recipe_filters = - unpack(i3.files.common().progressive) - -local make_fs, get_inventory_fs = i3.files.gui() - -local progressive_mode = core.settings:get_bool "i3_progressive_mode" - -local reg_items = core.registered_items -local reg_nodes = core.registered_nodes -local reg_craftitems = core.registered_craftitems -local reg_tools = core.registered_tools -local reg_aliases = core.registered_aliases - -local replacements = {fuel = {}} -local check_privs = core.check_player_privs -local after, clr = core.after, core.colorize -local create_inventory = core.create_detached_inventory - -local maxn, sort, concat, copy, insert, remove, indexof = - table.maxn, table.sort, table.concat, table.copy, - table.insert, table.remove, table.indexof - -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, ceil, random = math.min, math.ceil, math.random -local pairs, ipairs, next, type, tonum = pairs, ipairs, next, type, tonumber - -local vec_new, vec_add, vec_mul, vec_eq, vec_round = - vector.new, vector.add, vector.multiply, vector.equals, vector.round - -local function err(str) - return core.log("error", str) -end - -local function msg(name, str) - return core.chat_send_player(name, fmt("[i3] %s", str)) -end - -local function is_table(x) - return type(x) == "table" -end - -local function is_func(x) - return type(x) == "function" -end - -local function clean_name(item) - if sub(item, 1, 1) == ":" or sub(item, 1, 1) == " " or sub(item, 1, 1) == "_" then - item = sub(item, 2) - end - - return item -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 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 fmt, sort, copy = i3.need("fmt", "sort", "copy") +local show_item, reg_items = i3.need("show_item", "reg_items") local function get_lang_code(info) return info and info.lang_code @@ -203,728 +114,6 @@ local function outdated(name) core.show_formspec(name, "i3_outdated", fs) end -local old_is_creative_enabled = core.is_creative_enabled - -function core.is_creative_enabled(name) - if name == "" then - return old_is_creative_enabled(name) - end - - return check_privs(name, {creative = true}) or old_is_creative_enabled(name) -end - -function i3.register_craft_type(name, def) - if not true_str(name) then - return err "i3.register_craft_type: name missing" - end - - if not is_str(def.description) then - def.description = "" - end - - i3.craft_types[name] = def -end - -function i3.register_craft(def) - local width, c = 0, 0 - - if true_str(def.url) then - if not http then - return err(fmt([[i3.register_craft(): Unable to reach %s. - No HTTP support for this mod: add it to the `secure.http_mods` or - `secure.trusted_mods` setting.]], def.url)) - end - - http.fetch({url = def.url}, function(result) - if result.succeeded then - local t = core.parse_json(result.data) - if is_table(t) then - return i3.register_craft(t) - end - end - end) - - return - end - - if not is_table(def) or not next(def) then - return err "i3.register_craft: craft definition missing" - end - - if #def > 1 then - for _, v in pairs(def) do - i3.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 "i3.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 - 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] = fmt("%s,", items[i]) - end - end - - for name in gmatch(concat(items, ","), "[%s%w_:]+") do - c = c + 1 - def.items[c] = clean_name(name) - end - end - - local item = match(def.output, "%S+") - i3.recipes_cache[item] = i3.recipes_cache[item] or {} - - def.custom = true - def.width = width - - insert(i3.recipes_cache[item], def) -end - -function i3.add_recipe_filter(name, f) - if not true_str(name) then - return err "i3.add_recipe_filter: name missing" - elseif not is_func(f) then - return err "i3.add_recipe_filter: function missing" - end - - i3.recipe_filters[name] = f -end - -function i3.set_recipe_filter(name, f) - if not is_str(name) then - return err "i3.set_recipe_filter: name missing" - elseif not is_func(f) then - return err "i3.set_recipe_filter: function missing" - end - - i3.recipe_filters = {[name] = f} -end - -function i3.add_search_filter(name, f) - if not true_str(name) then - return err "i3.add_search_filter: name missing" - elseif not is_func(f) then - return err "i3.add_search_filter: function missing" - end - - i3.search_filters[name] = f -end - -function i3.remove_search_filter(name) - i3.search_filters[name] = nil -end - -local function get_burntime(item) - return core.get_craft_result{method = "fuel", items = {item}}.time -end - -local function cache_fuel(item) - local burntime = get_burntime(item) - if burntime > 0 then - i3.fuel_cache[item] = { - type = "fuel", - items = {item}, - burntime = burntime, - replacements = replacements.fuel[item], - } - end -end - -local function sort_by_category(data) - reset_compression(data) - local items = data.items_raw - - if data.filter ~= "" then - search(data) - items = data.items - end - - local new = {} - - for i = 1, #items do - local item = items[i] - local to_add = true - - if data.current_itab == 2 then - to_add = reg_nodes[item] - elseif data.current_itab == 3 then - to_add = reg_craftitems[item] or reg_tools[item] - end - - if to_add then - new[#new + 1] = item - end - end - - data.items = new -end - -local function get_item_usages(item, recipe, added) - local groups = extract_groups(item) - - if groups then - for name, def in pairs(reg_items) do - if not added[name] and show_item(def) and item_has_groups(def.groups, groups) then - local usage = copy(recipe) - table_replace(usage.items, item, name) - - i3.usages_cache[name] = i3.usages_cache[name] or {} - insert(i3.usages_cache[name], 1, usage) - - added[name] = true - end - end - elseif show_item(reg_items[item]) then - i3.usages_cache[item] = i3.usages_cache[item] or {} - insert(i3.usages_cache[item], 1, recipe) - end -end - -local function get_usages(recipe) - local added = {} - - for _, item in pairs(recipe.items) do - item = reg_aliases[item] or item - - if not added[item] then - get_item_usages(item, recipe, added) - added[item] = true - end - end -end - -local function cache_usages(item) - local recipes = i3.recipes_cache[item] or {} - - for i = 1, #recipes do - get_usages(recipes[i]) - end - - if i3.fuel_cache[item] then - i3.usages_cache[item] = table_merge(i3.usages_cache[item] or {}, {i3.fuel_cache[item]}) - end -end - -local function drop_table(name, drop) - local count_sure = 0 - local drop_items = drop.items or {} - local max_items = drop.max_items - - for i = 1, #drop_items do - local di = drop_items[i] - local valid_rarity = di.rarity and di.rarity > 1 - - if di.rarity or not max_items or - (max_items and not di.rarity and count_sure < max_items) then - for j = 1, #di.items do - local dstack = ItemStack(di.items[j]) - local dname = dstack:get_name() - local dcount = dstack:get_count() - local empty = dstack:is_empty() - - if not empty and (dname ~= name or (dname == name and dcount > 1)) then - local rarity = valid_rarity and di.rarity - - i3.register_craft { - type = rarity and "digging_chance" or "digging", - items = {name}, - output = fmt("%s %u", dname, dcount), - rarity = rarity, - tools = di.tools, - } - end - end - end - - if not di.rarity then - count_sure = count_sure + 1 - end - end -end - -local function cache_drops(name, drop) - if true_str(drop) then - local dstack = ItemStack(drop) - local dname = dstack:get_name() - local empty = dstack:is_empty() - - if not empty and dname ~= name then - i3.register_craft { - type = "digging", - items = {name}, - output = drop, - } - end - elseif is_table(drop) then - drop_table(name, drop) - end -end - -local function cache_recipes(item) - local recipes = core.get_all_craft_recipes(item) - - if replacements[item] then - local _recipes = {} - - for k, v in ipairs(recipes or {}) do - _recipes[#recipes + 1 - k] = v - end - - local shift = 0 - local size_rpl = maxn(replacements[item]) - local size_rcp = #_recipes - - if size_rpl > size_rcp then - shift = size_rcp - size_rpl - end - - for k, v in pairs(replacements[item]) do - k = k + shift - - if _recipes[k] then - _recipes[k].replacements = v - end - end - - recipes = _recipes - end - - if recipes then - i3.recipes_cache[item] = table_merge(recipes, i3.recipes_cache[item] or {}) - end -end - -function i3.get_recipes(item) - return { - recipes = i3.recipes_cache[item], - usages = i3.usages_cache[item] - } -end - -local function get_recipes(player, item) - local clean_item = reg_aliases[item] or item - local recipes = i3.recipes_cache[clean_item] - local usages = i3.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 end - usages = apply_recipe_filters(usages, player) - - 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 __sort(inv, reverse) - sort(inv, function(a, b) - if not is_str(a) then - a = a:get_name() - end - - if not is_str(b) then - b = b:get_name() - end - - if reverse then - return a > b - end - - return a < b - end) -end - -local function sort_itemlist(player, az) - local inv = player:get_inventory() - local list = inv:get_list("main") - local size = inv:get_size("main") - local new_inv, stack_meta = {}, {} - - for i = 1, size do - local stack = list[i] - local name = stack:get_name() - local count = stack:get_count() - local empty = stack:is_empty() - local meta = stack:get_meta():to_table() - local wear = stack:get_wear() > 0 - - if not empty then - if next(meta.fields) or wear then - stack_meta[#stack_meta + 1] = stack - else - new_inv[#new_inv + 1] = fmt("%s %u", name, count) - end - end - end - - for i = 1, #stack_meta do - new_inv[#new_inv + 1] = stack_meta[i] - end - - if az then - __sort(new_inv) - else - __sort(new_inv, true) - end - - inv:set_list("main", new_inv) -end - -local function compress_items(player) - local inv = player:get_inventory() - local list = inv:get_list("main") - local size = inv:get_size("main") - local new_inv, _new_inv, special = {}, {}, {} - - for i = 1, size do - local stack = list[i] - local name = stack:get_name() - local count = stack:get_count() - local stackmax = stack:get_stack_max() - local empty = stack:is_empty() - local meta = stack:get_meta():to_table() - local wear = stack:get_wear() > 0 - - if not empty then - if next(meta.fields) or wear or count >= stackmax then - special[#special + 1] = stack - else - new_inv[name] = new_inv[name] or 0 - new_inv[name] = new_inv[name] + count - end - end - end - - for name, count in pairs(new_inv) do - local stackmax = ItemStack(name):get_stack_max() - local iter = ceil(count / stackmax) - local leftover = count - - for _ = 1, iter do - _new_inv[#_new_inv + 1] = fmt("%s %u", name, min(stackmax, leftover)) - leftover = leftover - stackmax - end - end - - for i = 1, #special do - _new_inv[#_new_inv + 1] = special[i] - end - - __sort(_new_inv) - inv:set_list("main", _new_inv) -end - -local function spawn_item(player, stack) - local dir = player:get_look_dir() - local ppos = player:get_pos() - ppos.y = ppos.y + 1.625 - local look_at = vec_add(ppos, vec_mul(dir, 1)) - - core.add_item(look_at, stack) -end - -local function get_stack(player, stack) - local inv = player:get_inventory() - - if inv:room_for_item("main", stack) then - inv:add_item("main", stack) - else - spawn_item(player, stack) - end -end - -local function craft_stack(player, data, craft_rcp) - local inv = player:get_inventory() - local rcp_usg = craft_rcp and "recipe" or "usage" - local output = craft_rcp and data.recipes[data.rnum].output or data.usages[data.unum].output - output = ItemStack(output) - local stackname, stackcount, stackmax = output:get_name(), output:get_count(), output:get_stack_max() - local scrbar_val = data[fmt("scrbar_%s", craft_rcp and "rcp" or "usg")] or 1 - - for name, count in pairs(data.export_counts[rcp_usg].rcp) do - local items = {[name] = count} - - if is_group(name) then - items = {} - local groups = extract_groups(name) - local item_groups = groups_to_items(groups, true) - local remaining = count - - for _, item in ipairs(item_groups) do - for _name, _count in pairs(data.export_counts[rcp_usg].inv) do - if item == _name and remaining > 0 then - local c = min(remaining, _count) - items[item] = c - remaining = remaining - c - end - - if remaining == 0 then break end - end - end - end - - for k, v in pairs(items) do - inv:remove_item("main", fmt("%s %s", k, v * scrbar_val)) - end - end - - local count = stackcount * scrbar_val - local iter = ceil(count / stackmax) - local leftover = count - - for _ = 1, iter do - local c = min(stackmax, leftover) - local stack = ItemStack(fmt("%s %s", stackname, c)) - get_stack(player, stack) - leftover = leftover - stackmax - end -end - -local function select_item(player, name, data, _f) - local item - - for field in pairs(_f) do - if find(field, ":") then - item = field - break - end - end - - if not item then return end - - if compressible(item, data) then - local idx - - for i = 1, #data.items do - local it = data.items[i] - if it == item then - idx = i - break - end - end - - if data.expand ~= "" then - data.alt_items = nil - - if item == data.expand then - data.expand = nil - return - end - end - - if idx and item ~= data.expand then - data.alt_items = copy(data.items) - data.expand = item - - if i3.compress_groups[item] then - local items = copy(i3.compress_groups[item]) - insert(items, fmt("_%s", item)) - - sort(items, function(a, b) - if a:sub(1, 1) == "_" then - a = a:sub(2) - end - - return a < b - end) - - local i = 1 - - for _, v in ipairs(items) do - if show_item(reg_items[clean_name(v)]) then - insert(data.alt_items, idx + i, v) - i = i + 1 - end - end - end - end - else - if sub(item, 1, 1) == "_" then - item = sub(item, 2) - elseif sub(item, 1, 6) == "group|" then - item = match(item, "([%w:_]+)$") - end - - item = reg_aliases[item] or item - if not reg_items[item] then return end - - if core.is_creative_enabled(name) then - local stack = ItemStack(item) - local stackmax = stack:get_stack_max() - stack = fmt("%s %s", item, stackmax) - - return get_stack(player, stack) - end - - if item == data.query_item then return end - local recipes, usages = get_recipes(player, item) - - data.query_item = item - data.recipes = recipes - data.usages = usages - data.rnum = 1 - data.unum = 1 - data.scrbar_rcp = 1 - data.scrbar_usg = 1 - data.export_rcp = nil - data.export_usg = nil - end -end - -function i3.set_fs(player, _fs) - if not player or player.is_fake_player then return end - local name = player:get_player_name() - local data = i3.data[name] - if not data then return end - - local fs = fmt("%s%s", make_fs(player, data), _fs or "") - player:set_inventory_formspec(fs) -end - -local set_fs = i3.set_fs - -function i3.new_tab(def) - if not is_table(def) or not next(def) then - return err "i3.new_tab: tab definition missing" - end - - if not true_str(def.name) then - return err "i3.new_tab: tab name missing" - end - - if not true_str(def.description) then - return err "i3.new_tab: description missing" - end - - if #i3.tabs == 6 then - return err(fmt("i3.new_tab: cannot add '%s' tab. Limit reached (6).", def.name)) - end - - i3.tabs[#i3.tabs + 1] = def -end - -function i3.get_tabs() - return i3.tabs -end - -function i3.remove_tab(tabname) - if not true_str(tabname) then - return err "i3.remove_tab: tab name missing" - end - - for i, def in ipairs(i3.tabs) do - if tabname == def.name then - remove(i3.tabs, i) - end - end -end - -function i3.get_current_tab(player) - local name = player:get_player_name() - local data = i3.data[name] - - return data.current_tab -end - -function i3.set_tab(player, tabname) - local name = player:get_player_name() - local data = i3.data[name] - - if not tabname or tabname == "" then - data.current_tab = 0 - return - end - - local found - - for i, def in ipairs(i3.tabs) do - if not found and def.name == tabname then - data.current_tab = i - found = true - end - end - - if not found then - return err(fmt("i3.set_tab: tab name '%s' does not exist", tabname)) - end -end - -local set_tab = i3.set_tab - -function i3.override_tab(tabname, newdef) - if not is_table(newdef) or not next(newdef) then - return err "i3.override_tab: tab definition missing" - end - - if not true_str(newdef.name) then - return err "i3.override_tab: tab name missing" - end - - if not true_str(newdef.description) then - return err "i3.override_tab: description missing" - end - - for i, def in ipairs(i3.tabs) do - if def.name == tabname then - i3.tabs[i] = newdef - end - end -end - local function init_data(player, info) local name = player:get_player_name() i3.data[name] = i3.data[name] or {} @@ -943,283 +132,9 @@ local function init_data(player, info) data.lang_code = get_lang_code(info) data.fs_version = info.formspec_version - after(0, set_fs, player) + core.after(0, set_fs, player) end -local function reset_data(data) - data.filter = "" - data.expand = "" - data.pagenum = 1 - data.rnum = 1 - data.unum = 1 - data.scrbar_rcp = 1 - data.scrbar_usg = 1 - data.query_item = nil - data.recipes = nil - data.usages = nil - data.export_rcp = nil - data.export_usg = nil - data.alt_items = nil - data.confirm_trash = nil - data.items = data.items_raw - - if data.current_itab > 1 then - sort_by_category(data) - end -end - -local function rcp_fields(player, data, fields) - local name = player:get_player_name() - local sb_rcp, sb_usg = fields.scrbar_rcp, fields.scrbar_usg - - if fields.cancel then - reset_data(data) - - elseif fields.exit then - data.query_item = nil - - elseif fields.key_enter_field == "filter" or fields.search then - if fields.filter == "" then - reset_data(data) - return set_fs(player) - end - - local str = lower(fields.filter) - if data.filter == str then return end - - data.filter = str - data.pagenum = 1 - - search(data) - - if data.current_itab > 1 then - sort_by_category(data) - end - - elseif fields.prev_page or fields.next_page then - if data.pagemax == 1 then return end - data.pagenum = data.pagenum - (fields.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 - - elseif fields.prev_recipe or fields.next_recipe then - local num = data.rnum + (fields.prev_recipe and -1 or 1) - data.rnum = data.recipes[num] and num or (fields.prev_recipe and #data.recipes or 1) - data.export_rcp = nil - data.scrbar_rcp = 1 - - elseif fields.prev_usage or fields.next_usage then - local num = data.unum + (fields.prev_usage and -1 or 1) - data.unum = data.usages[num] and num or (fields.prev_usage and #data.usages or 1) - data.export_usg = nil - data.scrbar_usg = 1 - - elseif fields.fav then - local fav, i = is_fav(data.favs, data.query_item) - local total = #data.favs - - if total < i3.MAX_FAVS and not fav then - data.favs[total + 1] = data.query_item - elseif fav then - remove(data.favs, i) - end - - elseif fields.export_rcp or fields.export_usg then - if fields.export_rcp then - data.export_rcp = not data.export_rcp - - if not data.export_rcp then - data.scrbar_rcp = 1 - end - else - data.export_usg = not data.export_usg - - if not data.export_usg then - data.scrbar_usg = 1 - end - end - - elseif (sb_rcp and sub(sb_rcp, 1, 3) == "CHG") or (sb_usg and sub(sb_usg, 1, 3) == "CHG") then - data.scrbar_rcp = sb_rcp and tonum(match(sb_rcp, "%d+")) - data.scrbar_usg = sb_usg and tonum(match(sb_usg, "%d+")) - - elseif fields.craft_rcp or fields.craft_usg then - craft_stack(player, data, fields.craft_rcp) - - if fields.craft_rcp then - data.export_rcp = nil - data.scrbar_rcp = 1 - else - data.export_usg = nil - data.scrbar_usg = 1 - end - else - select_item(player, name, data, fields) - end -end - -i3.new_tab { - name = "inventory", - description = S"Inventory", - formspec = get_inventory_fs, - - fields = function(player, data, fields) - local name = player:get_player_name() - local sb_inv = fields.scrbar_inv - - if fields.skins then - local id = tonum(fields.skins) - local _skins = skins.get_skinlist_for_player(name) - skins.set_player_skin(player, _skins[id]) - end - - for field in pairs(fields) do - if sub(field, 1, 4) == "btn_" then - data.subcat = indexof(i3.SUBCAT, sub(field, 5)) - break - - elseif find(field, "waypoint_%d+") then - local id, action = match(field, "_(%d+)_(%w+)$") - id = tonum(id) - local waypoint = data.waypoints[id] - if not waypoint then return end - - if action == "delete" then - player:hud_remove(waypoint.id) - remove(data.waypoints, id) - - elseif action == "teleport" then - local pos = vec_new(waypoint.pos) - pos.y = pos.y + 0.5 - - local vel = player:get_velocity() - player:add_velocity(vec_mul(vel, -1)) - player:set_pos(pos) - - msg(name, fmt("Teleported to %s", clr("#ff0", waypoint.name))) - - elseif action == "refresh" then - local color = random(0xffffff) - waypoint.color = color - player:hud_change(waypoint.id, "number", color) - - elseif action == "hide" then - if waypoint.hide then - local new_id = player:hud_add { - hud_elem_type = "waypoint", - name = waypoint.name, - text = " m", - world_pos = waypoint.pos, - number = waypoint.color, - z_index = -300, - } - - waypoint.id = new_id - waypoint.hide = nil - else - player:hud_remove(waypoint.id) - waypoint.hide = true - end - end - - break - end - end - - if fields.trash then - data.confirm_trash = true - - elseif fields.confirm_trash_yes or fields.confirm_trash_no then - if fields.confirm_trash_yes then - local inv = player:get_inventory() - inv:set_list("main", {}) - inv:set_list("craft", {}) - end - - data.confirm_trash = nil - - elseif fields.compress then - compress_items(player) - - elseif fields.sort_az or fields.sort_za then - sort_itemlist(player, fields.sort_az) - - elseif sb_inv and sub(sb_inv, 1, 3) == "CHG" then - data.scrbar_inv = tonum(match(sb_inv, "%d+")) - return - - elseif fields.waypoint_add then - local pos = player:get_pos() - - for _, v in ipairs(data.waypoints) do - if vec_eq(vec_round(pos), vec_round(v.pos)) then - return msg(name, "You already set a waypoint at this position") - end - end - - local waypoint = fields.waypoint_name - - if fields.waypoint_name == "" then - waypoint = "Waypoint" - end - - local color = random(0xffffff) - - local id = player:hud_add { - hud_elem_type = "waypoint", - name = waypoint, - text = " m", - world_pos = pos, - number = color, - z_index = -300, - } - - insert(data.waypoints, {name = waypoint, pos = pos, color = color, id = id}) - data.scrbar_inv = data.scrbar_inv + 1000 - end - - return set_fs(player) - end, -} - -local trash = create_inventory("i3_trash", { - allow_put = function(_, _, _, stack) - return stack:get_count() - end, - on_put = function(inv, listname, _, _, player) - inv:set_list(listname, {}) - - local name = player:get_player_name() - - if not core.is_creative_enabled(name) then - set_fs(player) - end - end, -}) - -trash:set_size("main", 1) - -local output_rcp = create_inventory("i3_output_rcp", {}) -output_rcp:set_size("main", 1) - -local output_usg = create_inventory("i3_output_usg", {}) -output_usg:set_size("main", 1) - -core.register_on_player_inventory_action(function(player, _, _, info) - local name = player:get_player_name() - - if not core.is_creative_enabled(name) and - ((info.from_list == "main" and info.to_list == "craft") or - (info.from_list == "craft" and info.to_list == "main") or - (info.from_list == "craftresult" and info.to_list == "main")) then - set_fs(player) - end -end) - if rawget(_G, "armor") then i3.modules.armor = true armor:register_on_update(set_fs) @@ -1250,136 +165,6 @@ if rawget(_G, "awards") then end) end -core.register_on_chatcommand(function(name) - local player = core.get_player_by_name(name) - after(0, set_fs, player) -end) - -core.register_on_priv_grant(function(name, _, priv) - if priv == "creative" or priv == "all" then - local data = i3.data[name] - reset_data(data) - data.favs = {} - - local player = core.get_player_by_name(name) - after(0, set_fs, player) - end -end) - -i3.register_craft_type("digging", { - description = S"Digging", - icon = "i3_steelpick.png", -}) - -i3.register_craft_type("digging_chance", { - description = S"Digging (by chance)", - icon = "i3_mesepick.png", -}) - -i3.add_search_filter("groups", function(item, groups) - local def = reg_items[item] - local has_groups = true - - for _, group in ipairs(groups) do - 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 fuel, replacements and toolrepair recipes, we have to - override `core.register_craft` and do some reverse engineering. - See engine's issues #4901, #5745 and #8920. ]] - -local old_register_craft = core.register_craft -local rcp_num = {} - -core.register_craft = function(def) - old_register_craft(def) - - if def.type == "toolrepair" then - i3.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 item = output[i] - rcp_num[item] = (rcp_num[item] or 0) + 1 - - if def.replacements then - if def.type == "fuel" then - replacements.fuel[item] = def.replacements - else - replacements[item] = replacements[item] or {} - replacements[item][rcp_num[item]] = def.replacements - end - end - end -end - -local old_clear_craft = core.clear_craft - -core.clear_craft = function(def) - old_clear_craft(def) - - if true_str(def) then - return -- TODO - elseif is_table(def) then - return -- TODO - end -end - -local function resolve_aliases(hash) - for oldname, newname in pairs(reg_aliases) do - cache_recipes(oldname) - local recipes = i3.recipes_cache[oldname] - - if recipes then - if not i3.recipes_cache[newname] then - i3.recipes_cache[newname] = {} - end - - local similar - - for i = 1, #i3.recipes_cache[oldname] do - local rcp_old = i3.recipes_cache[oldname][i] - - for j = 1, #i3.recipes_cache[newname] do - local rcp_new = copy(i3.recipes_cache[newname][j]) - rcp_new.output = oldname - - if table_eq(rcp_old, rcp_new) then - similar = true - break - end - end - - if not similar then - insert(i3.recipes_cache[newname], rcp_old) - end - end - end - - if newname ~= "" and i3.recipes_cache[oldname] and not hash[newname] then - i3.init_items[#i3.init_items + 1] = newname - end - end -end - local function get_init_items() local _select, _preselect = {}, {} @@ -1403,13 +188,13 @@ local function get_init_items() resolve_aliases(_select) sort(i3.init_items) - if http and true_str(i3.export_url) then + if i3.http and type(i3.export_url) == "string" then local post_data = { recipes = i3.recipes_cache, usages = i3.usages_cache, } - http.fetch_async { + i3.http.fetch_async { url = i3.export_url, post_data = core.write_json(post_data), } @@ -1429,72 +214,6 @@ core.register_on_mods_loaded(function() end end) -local function init_backpack(player) - local name = player:get_player_name() - local data = i3.data[name] - local inv = player:get_inventory() - - -- Legacy compat - if data.bag_size and type(data.bag_size) == "string" then - local convert = { - small = 1, - medium = 2, - large = 3, - } - - data.bag_item = fmt("i3:bag_%s", data.bag_size) - data.bag_size = convert[data.bag_size] - end - - inv:set_size("main", data.bag_size and i3.BAG_SIZES[data.bag_size] or i3.INV_SIZE) - - data.bag = create_inventory(fmt("%s_backpack", name), { - allow_put = function(_inv, listname, _, stack) - local empty = _inv:get_stack(listname, 1):is_empty() - local item_group = minetest.get_item_group(stack:get_name(), "bag") - - if empty and item_group > 0 and item_group <= #i3.BAG_SIZES then - return 1 - end - - msg(name, S"This is not a backpack") - - return 0 - end, - - on_put = function(_, _, _, stack) - local stackname = stack:get_name() - data.bag_item = stackname - data.bag_size = minetest.get_item_group(stackname, "bag") - - inv:set_size("main", i3.BAG_SIZES[data.bag_size]) - set_fs(player) - end, - - on_take = function() - for i = i3.INV_SIZE + 1, i3.BAG_SIZES[data.bag_size] do - local stack = inv:get_stack("main", i) - - if not stack:is_empty() then - spawn_item(player, stack) - end - end - - data.bag_item = nil - data.bag_size = nil - - inv:set_size("main", i3.INV_SIZE) - set_fs(player) - end, - }) - - data.bag:set_size("main", 1) - - if data.bag_item then - data.bag:set_stack("main", 1, data.bag_item) - end -end - local function init_waypoints(player) local name = player:get_player_name() local data = i3.data[name] @@ -1529,29 +248,12 @@ core.register_on_joinplayer(function(player) init_backpack(player) init_waypoints(player) - after(0, function() + core.after(0, function() player:hud_set_hotbar_itemcount(i3.HOTBAR_LEN) player:hud_set_hotbar_image("i3_hotbar.png") end) end) -core.register_on_dieplayer(function(player) - local name = player:get_player_name() - local data = i3.data[name] - if not data then return end - - if data.bag_size then - data.bag_item = nil - data.bag_size = nil - data.bag:set_list("main", {}) - - local inv = player:get_inventory() - inv:set_size("main", i3.INV_SIZE) - end - - set_fs(player) -end) - local function save_data(player_name) local _data = copy(i3.data) @@ -1579,99 +281,14 @@ core.register_on_shutdown(save_data) local function routine() save_data() - after(i3.SAVE_INTERVAL, routine) + core.after(i3.SAVE_INTERVAL, routine) end -after(i3.SAVE_INTERVAL, routine) +core.after(i3.SAVE_INTERVAL, routine) -core.register_on_player_receive_fields(function(player, formname, fields) - local name = player:get_player_name() - - if formname == "i3_outdated" then - return false, core.kick_player(name, "Come back when your client is up-to-date.") - elseif formname ~= "" then - return false - end - - local data = i3.data[name] - if not data then return end - - for f in pairs(fields) do - if sub(f, 1, 4) == "tab_" then - local tabname = sub(f, 5) - set_tab(player, tabname) - break - elseif sub(f, 1, 5) == "itab_" then - data.pagenum = 1 - data.current_itab = tonum(f:sub(-1)) - sort_by_category(data) - end - end - - rcp_fields(player, data, fields) - - local tab = i3.tabs[data.current_tab] - - if tab and tab.fields then - return true, tab.fields(player, data, fields) - end - - return true, set_fs(player) -end) - -core.register_on_player_hpchange(function(player, hpchange) - local name = player:get_player_name() - local data = i3.data[name] - if not data then return end - - local hp_max = player:get_properties().hp_max - data.hp = min(hp_max, player:get_hp() + hpchange) - - set_fs(player) -end) - -if progressive_mode then +if i3.progressive_mode then i3.files.progressive() end -local bag_recipes = { - small = { - rcp = { - {"", "farming:string", ""}, - {"group:wool", "group:wool", "group:wool"}, - {"group:wool", "group:wool", "group:wool"}, - }, - size = 1, - }, - medium = { - rcp = { - {"farming:string", "i3:bag_small", "farming:string"}, - {"farming:string", "i3:bag_small", "farming:string"}, - }, - size = 2, - }, - large = { - rcp = { - {"farming:string", "i3:bag_medium", "farming:string"}, - {"farming:string", "i3:bag_medium", "farming:string"}, - }, - size = 3, - }, -} - -for size, item in pairs(bag_recipes) do - local bagname = fmt("i3:bag_%s", size) - - core.register_craftitem(bagname, { - description = fmt("%s Backpack", size:gsub("^%l", upper)), - inventory_image = fmt("i3_bag_%s.png", size), - stack_max = 1, - groups = {bag = item.size} - }) - - core.register_craft {output = bagname, recipe = item.rcp} - core.register_craft {type = "fuel", recipe = bagname, burntime = 3} -end - --dofile(modpath .. "/tests/test_tabs.lua") --dofile(modpath .. "/tests/test_custom_recipes.lua")