diff --git a/.luacheckrc b/.luacheckrc index 1ef8061..78f01ca 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -23,6 +23,7 @@ globals = { } exclude_files = { + "tests/test_compression.lua", "tests/test_custom_recipes.lua", "tests/test_tabs.lua", } diff --git a/API.md b/API.md index b61ead1..7f11870 100644 --- a/API.md +++ b/API.md @@ -196,8 +196,8 @@ They can be used like so: ` +=,,<... Example usages: -- `+groups=cracky,crumbly`: search for groups `cracky` and `crumbly` in all items. -- `wood +groups=flammable`: search for group `flammable` amongst items which contain +- `+groups=cracky,crumbly` -> search for groups `cracky` and `crumbly` in all items. +- `wood +groups=flammable` -> search for group `flammable` amongst items which contain `wood` in their names. Notes: @@ -252,9 +252,9 @@ i3.compress("default:diamondblock", { ``` -#### `i3.get_compress_groups()` +#### `i3.compress_groups` -Returns a map of all compressed item groups, indexed by stereotypes. +A map of all compressed item groups, indexed by stereotypes. --- diff --git a/etc/api.lua b/etc/api.lua index 45edbf3..aca7662 100644 --- a/etc/api.lua +++ b/etc/api.lua @@ -1,17 +1,10 @@ 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 S, err, fmt, reg_items = i3.need("S", "err", "fmt", "reg_items") 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 +local true_str, true_table, is_str, is_func, is_table, clean_name = + i3.need("true_str", "true_table", "is_str", "is_func", "is_table", "clean_name") function i3.register_craft_type(name, def) if not true_str(name) then @@ -47,7 +40,7 @@ function i3.register_craft(def) return end - if not is_table(def) or not next(def) then + if not true_table(def) then return err "i3.register_craft: craft definition missing" end @@ -180,7 +173,7 @@ end local set_fs = i3.set_fs function i3.new_tab(def) - if not is_table(def) or not next(def) then + if not true_table(def) then return err "i3.new_tab: tab definition missing" end @@ -242,7 +235,7 @@ function i3.set_tab(player, tabname) end function i3.override_tab(tabname, newdef) - if not is_table(newdef) or not next(newdef) then + if not true_table(newdef) then return err "i3.override_tab: tab definition missing" end @@ -290,7 +283,7 @@ function i3.compress(item, def) return err "i3.compress: item name missing" end - if not is_table(def) then + if not true_table(def) then return err "i3.compress: replace definition missing" end @@ -315,8 +308,4 @@ function i3.compress(item, def) end end -function i3.get_compress_groups() - return i3.compress_groups -end - return set_fs, i3.set_tab diff --git a/etc/common.lua b/etc/common.lua index 65ff741..a154416 100644 --- a/etc/common.lua +++ b/etc/common.lua @@ -1,9 +1,38 @@ local translate = core.get_translated_string +local insert, remove, floor, vec_add, vec_mul = + table.insert, table.remove, math.floor, vector.add, vector.mul local fmt, find, gmatch, match, sub, split, 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 S = core.get_translator "i3" +local ES = function(...) return core.formspec_escape(S(...)) end + +local function is_num(x) + return type(x) == "number" +end + +local function is_str(x) + return type(x) == "string" +end + +local function is_table(x) + return type(x) == "table" +end + +local function is_func(x) + return type(x) == "function" +end + +local function true_str(str) + return is_str(str) and str ~= "" +end + +local function true_table(x) + return is_table(x) and next(x) +end + local function reset_compression(data) data.alt_items = nil data.expand = "" @@ -76,6 +105,14 @@ local function search(data) data.items = filtered_list 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_merge(t1, t2, hash) t1 = t1 or {} t2 = t2 or {} @@ -96,6 +133,86 @@ local function table_merge(t1, t2, hash) return t1 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 is_group(item) return sub(item, 1, 6) == "group:" end @@ -162,16 +279,25 @@ local function compressible(item, data) return compression_active(data) and i3.compress_groups[item] end -local function is_str(x) - return type(x) == "string" +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 is_table(x) - return type(x) == "table" +local function msg(name, str) + return core.chat_send_player(name, fmt("[i3] %s", str)) end -local function true_str(str) - return is_str(str) and str ~= "" +local function err(str) + return core.log("error", str) +end + +local function round(num, decimal) + local mul = 10 ^ decimal + return floor(num * mul + 0.5) / mul end local function is_fav(favs, query_item) @@ -216,118 +342,98 @@ local function sort_by_category(data) 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)) + local look_at = vec_add(ppos, vec_mul(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 { - groups = { - is_group = is_group, - extract_groups = extract_groups, - item_has_groups = item_has_groups, - groups_to_items = groups_to_items, - }, + -- Groups + is_group = is_group, + extract_groups = extract_groups, + item_has_groups = item_has_groups, + groups_to_items = groups_to_items, - compression = { - compressible = compressible, - compression_active = compression_active, - }, + -- Compression + compressible = compressible, + compression_active = compression_active, - sorting = { - search = search, - sort_by_category = sort_by_category, - apply_recipe_filters = apply_recipe_filters, - }, + -- 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, - }, + -- Misc. functions + err = err, + msg = msg, + is_fav = is_fav, + is_str = is_str, + is_num = is_num, + is_func = is_func, + show_item = show_item, + spawn_item = spawn_item, + true_str = true_str, + true_table = true_table, + clean_name = clean_name, - core = { - clr = core.colorize, - ESC = core.formspec_escape, - check_privs = core.check_player_privs, - }, + -- Core functions + 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, - }, + -- Registered items + reg_items = core.registered_items, + reg_nodes = core.registered_nodes, + reg_tools = core.registered_tools, + reg_aliases = core.registered_aliases, + reg_entities = core.registered_entities, + reg_craftitems = core.registered_craftitems, - i18n = { - S = S, - ES = ES, - translate = core.get_translated_string, - }, + -- 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, + -- String + sub = string.sub, + find = string.find, + fmt = string.format, + upper = string.upper, + lower = string.lower, + split = string.split, + match = string.match, + gmatch = string.gmatch, - is_str = is_str, - true_str = true_str, - clean_name = clean_name, - }, + -- Table + maxn = table.maxn, + sort = table.sort, + copy = table.copy, + concat = table.concat, + insert = table.insert, + remove = table.remove, + indexof = table.indexof, + is_table = is_table, + table_eq = table_eq, + table_merge = table_merge, + table_replace = table_replace, + array_diff = array_diff, - table = { - maxn = table.maxn, - sort = table.sort, - concat = table.concat, - copy = table.copy, - insert = table.insert, - remove = table.remove, - indexof = table.indexof, + -- Math + round = round, + min = math.min, + max = math.max, + ceil = math.ceil, + floor = math.floor, + random = math.random, - 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, - }, + -- Vectors + vec_new = vector.new, + vec_add = vector.add, + vec_round = vector.round, + vec_eq = vector.equals, + vec_mul = vector.multiply, } diff --git a/etc/gui.lua b/etc/gui.lua index 2415170..2421ee2 100644 --- a/etc/gui.lua +++ b/etc/gui.lua @@ -3,25 +3,17 @@ local damage_enabled = core.settings:get_bool "enable_damage" local model_aliases = i3.files.model_alias() local PNG, styles, fs_elements = i3.files.styles() -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 clr, ESC, check_privs = i3.need("clr", "ESC", "check_privs") +local min, max, floor, ceil, round = i3.need("min", "max", "floor", "ceil", "round") 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 true_str, is_fav, is_num = i3.need("true_str", "is_fav", "is_num") 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" -end - -local function round(num, decimal) - local mul = 10 ^ decimal - return floor(num * mul + 0.5) / mul -end +local groups_to_items, compression_active, compressible = + i3.need("groups_to_items", "compression_active", "compressible") local function fmt(elem, ...) if not fs_elements[elem] then @@ -290,7 +282,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 core.check_player_privs(player, {teleport = true}) then + if check_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 index 1792b69..5cd36d5 100644 --- a/etc/inventory.lua +++ b/etc/inventory.lua @@ -8,10 +8,13 @@ local fmt, find, match, sub, lower = i3.need("fmt", "find", "match", "sub", "low 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 msg, is_str, is_fav = i3.need("msg", "is_str", "is_fav") +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 show_item, spawn_item, clean_name, compressible, check_privs = + i3.need("show_item", "spawn_item", "clean_name", "compressible", "check_privs") local old_is_creative_enabled = core.is_creative_enabled @@ -20,7 +23,7 @@ function core.is_creative_enabled(name) return old_is_creative_enabled(name) end - return core.check_player_privs(name, {creative = true}) or old_is_creative_enabled(name) + return check_privs(name, {creative = true}) or old_is_creative_enabled(name) end local function reset_data(data) diff --git a/etc/progressive.lua b/etc/progressive.lua index 4fc72fa..4076b31 100644 --- a/etc/progressive.lua +++ b/etc/progressive.lua @@ -1,38 +1,13 @@ 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 fmt, search, table_merge, array_diff = i3.need("fmt", "search", "table_merge", "array_diff") +local is_group, extract_groups, item_has_groups, apply_recipe_filters = + i3.need("is_group", "extract_groups", "item_has_groups", "apply_recipe_filters") local POLL_FREQ = 0.25 local HUD_TIMER_MAX = 1.5 -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 get_filtered_items(player, data) local items, known, c = {}, 0, 0 diff --git a/etc/recipes.lua b/etc/recipes.lua index 1054fde..0780952 100644 --- a/etc/recipes.lua +++ b/etc/recipes.lua @@ -2,73 +2,12 @@ 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 maxn, copy, insert = i3.need("maxn", "copy", "insert") -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 true_str, is_table, show_item, table_merge, table_replace, table_eq = + i3.need("true_str", "is_table", "show_item", "table_merge", "table_replace", "table_eq") local function get_burntime(item) return core.get_craft_result{method = "fuel", items = {item}}.time diff --git a/init.lua b/init.lua index 7d50377..e2e1326 100644 --- a/init.lua +++ b/init.lua @@ -71,14 +71,12 @@ function i3.need(...) local t = {} for _, var in ipairs {...} do - for _, cat in pairs(common) do - for name, func in pairs(cat) do + for name, func in pairs(common) do if var == name then t[#t + 1] = func break end end - end end return unpack(t) @@ -114,27 +112,6 @@ local function outdated(name) core.show_formspec(name, "i3_outdated", fs) end -local function init_data(player, info) - local name = player:get_player_name() - i3.data[name] = i3.data[name] or {} - local data = i3.data[name] - - data.filter = "" - data.pagenum = 1 - data.items = i3.init_items - data.items_raw = i3.init_items - data.favs = {} - data.export_counts = {} - data.current_tab = 1 - data.current_itab = 1 - data.subcat = 1 - data.scrbar_inv = 0 - data.lang_code = get_lang_code(info) - data.fs_version = info.formspec_version - - core.after(0, set_fs, player) -end - if rawget(_G, "armor") then i3.modules.armor = true armor:register_on_update(set_fs) @@ -201,9 +178,7 @@ local function get_init_items() end end -core.register_on_mods_loaded(function() - get_init_items() - +local function disable_inventories() if rawget(_G, "sfinv") then function sfinv.set_player_inventory_formspec() return end sfinv.enabled = false @@ -212,7 +187,28 @@ core.register_on_mods_loaded(function() if rawget(_G, "unified_inventory") then function unified_inventory.set_inventory_formspec() return end end -end) +end + +local function init_data(player, info) + local name = player:get_player_name() + i3.data[name] = i3.data[name] or {} + local data = i3.data[name] + + data.filter = "" + data.pagenum = 1 + data.items = i3.init_items + data.items_raw = i3.init_items + data.favs = {} + data.export_counts = {} + data.current_tab = 1 + data.current_itab = 1 + data.subcat = 1 + data.scrbar_inv = 0 + data.lang_code = get_lang_code(info) + data.fs_version = info.formspec_version + + core.after(0, set_fs, player) +end local function init_waypoints(player) local name = player:get_player_name() @@ -235,24 +231,12 @@ local function init_waypoints(player) end end -core.register_on_joinplayer(function(player) - local name = player:get_player_name() - local info = core.get_player_information and core.get_player_information(name) - - if not info or get_formspec_version(info) < i3.MIN_FORMSPEC_VERSION then - i3.data[name] = nil - return outdated(name) - end - - init_data(player, info) - init_backpack(player) - init_waypoints(player) - +local function init_hudbar(player) core.after(0, function() player:hud_set_hotbar_itemcount(i3.HOTBAR_LEN) player:hud_set_hotbar_image("i3_hotbar.png") end) -end) +end local function save_data(player_name) local _data = copy(i3.data) @@ -272,6 +256,26 @@ local function save_data(player_name) storage:set_string("data", slz(_data)) end +core.register_on_mods_loaded(function() + get_init_items() + disable_inventories() +end) + +core.register_on_joinplayer(function(player) + local name = player:get_player_name() + local info = core.get_player_information and core.get_player_information(name) + + if not info or get_formspec_version(info) < i3.MIN_FORMSPEC_VERSION then + i3.data[name] = nil + return outdated(name) + end + + init_data(player, info) + init_backpack(player) + init_waypoints(player) + init_hudbar(player) +end) + core.register_on_leaveplayer(function(player) local name = player:get_player_name() save_data(name)