From 9276598e3eebe9393ffe87014466f765ca7b5806 Mon Sep 17 00:00:00 2001 From: Jean-Patrick Guerrero Date: Sun, 31 Oct 2021 22:11:41 +0100 Subject: [PATCH] Add new buttons to main inventory, add inventory sorting methods + API --- API.md | 34 +++++++ README.md | 4 +- etc/api.lua | 67 ++++++++++++++ etc/common.lua | 112 ++++++++++++++++++++++- etc/gui.lua | 107 ++++++++++++++++++---- etc/inventory.lua | 192 ++++++++++++++++----------------------- etc/styles.lua | 15 +-- init.lua | 46 ++++++---- textures/i3_home.png | Bin 0 -> 6195 bytes textures/i3_settings.png | Bin 0 -> 8425 bytes textures/i3_sort.png | Bin 0 -> 6393 bytes textures/i3_sort_az.png | Bin 3168 -> 0 bytes textures/i3_sort_za.png | Bin 3564 -> 0 bytes 13 files changed, 414 insertions(+), 163 deletions(-) create mode 100644 textures/i3_home.png create mode 100644 textures/i3_settings.png create mode 100644 textures/i3_sort.png delete mode 100644 textures/i3_sort_az.png delete mode 100644 textures/i3_sort_za.png diff --git a/API.md b/API.md index a1056db..7c7538a 100644 --- a/API.md +++ b/API.md @@ -235,6 +235,40 @@ A map of search filters, indexed by name. --- +### Sorting methods + +Sorting methods are used to filter the player's main inventory. + +#### `i3.add_sorting_method(def)` + +Adds a player inventory sorting method. + +- `def` is the method definition. + +Example: + +```Lua +i3.add_sorting_method { + name = "test", + description = "Cool sorting method", + func = function(player, data) + local inv = player:get_inventory() + local list = inv:get_list("main") + table.sort(list) + + -- An array of items must be returned + return list + end, +} + +``` + +#### `i3.sorting_methods` + +A table containing all sorting methods. + +--- + ### Item list compression `i3` can reduce the item list size by compressing a group of items. diff --git a/README.md b/README.md index 409dfed..5f2f4f5 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,10 @@ This mod requires **Minetest 5.4+** - Quick Crafting - Backpacks - 3D Player Model Preview - - Inventory Sorting (alphabetical + item stack compression) + - Inventory Sorting (with optional compression) - Item Bookmarks - Waypoints - - Item List Compression (**`moreblocks`** supported) + - Item List Compression (**`moreblocks`** is supported) **ยน** *This mode is a Terraria-like system that shows recipes you can craft from items you ever had in your inventory. To enable it: `i3_progressive_mode = true` in `minetest.conf`.* diff --git a/etc/api.lua b/etc/api.lua index 9749cf2..44e8af2 100644 --- a/etc/api.lua +++ b/etc/api.lua @@ -2,6 +2,7 @@ local make_fs = i3.files.gui() local gmatch, match, split = i3.get("gmatch", "match", "split") local S, err, fmt, reg_items = i3.get("S", "err", "fmt", "reg_items") +local name_sort, count_sort, sort_inventory = i3.get("name_sort", "count_sort", "sort_inventory") local sort, concat, copy, insert, remove = i3.get("sort", "concat", "copy", "insert", "remove") local true_str, true_table, is_str, is_func, is_table, clean_name = i3.get("true_str", "true_table", "is_str", "is_func", "is_table", "clean_name") @@ -166,6 +167,10 @@ function i3.set_fs(player, _fs) local data = i3.data[name] if not data then return end + if data.auto_sorting then + sort_inventory(player, data) + end + local fs = fmt("%s%s", make_fs(player, data), _fs or "") player:set_inventory_formspec(fs) end @@ -289,3 +294,65 @@ function i3.compress(item, def) i3.compressed[it] = true end end + +function i3.add_sorting_method(def) + if not true_table(def) then + return err "i3.add_sorting_method: definition missing" + elseif not true_str(def.name) then + return err "i3.add_sorting_method: name missing" + elseif not is_func(def.func) then + return err "i3.add_sorting_method: function missing" + end + + insert(i3.sorting_methods, def) +end + +local function pre_sorting(player) + 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] = ItemStack(fmt("%s %u", name, count)) + end + end + end + + for i = 1, #stack_meta do + new_inv[#new_inv + 1] = stack_meta[i] + end + + return new_inv +end + +i3.add_sorting_method { + name = "alphabetical", + description = S"Sort items by name (A-Z)", + func = function(player, data) + local new_inv = pre_sorting(player) + name_sort(new_inv, data.reverse_sorting) + return new_inv + end +} + +i3.add_sorting_method { + name = "numerical", + description = S"Sort items by number of items per stack", + func = function(player, data) + local new_inv = pre_sorting(player) + count_sort(new_inv, data.reverse_sorting) + return new_inv + end, +} diff --git a/etc/common.lua b/etc/common.lua index 52d69d4..ca8e4ea 100644 --- a/etc/common.lua +++ b/etc/common.lua @@ -1,6 +1,6 @@ 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 insert, remove, sort, vec_add, vec_mul = + table.insert, table.remove, table.sort, 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 = @@ -297,7 +297,7 @@ end local function round(num, decimal) local mul = 10 ^ decimal - return floor(num * mul + 0.5) / mul + return math.floor(num * mul + 0.5) / mul end local function is_fav(favs, query_item) @@ -351,9 +351,107 @@ local function spawn_item(player, stack) core.add_item(look_at, stack) end +local function name_sort(inv, reverse) + return sort(inv, function(a, b) + a, b = a:get_name(), b:get_name() + + if reverse then + return a > b + end + + return a < b + end) +end + +local function count_sort(inv, reverse) + return sort(inv, function(a, b) + a, b = a:get_count(), b:get_count() + + if reverse then + return a > b + end + + return a < b + end) +end + +local function get_sorting_idx(name) + local idx = 1 + + for i, def in ipairs(i3.sorting_methods) do + if name == def.name then + idx = i + end + end + + return idx +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 = math.ceil(count / stackmax) + local leftover = count + + for _ = 1, iter do + _new_inv[#_new_inv + 1] = fmt("%s %u", name, math.min(stackmax, leftover)) + leftover = leftover - stackmax + end + end + + for i = 1, #special do + _new_inv[#_new_inv + 1] = special[i] + end + + inv:set_list("main", _new_inv) +end + +local function sort_inventory(player, data) + if data.inv_compress then + compress_items(player) + end + + local sorts = {} + + for _, def in ipairs(i3.sorting_methods) do + sorts[def.name] = def.func + end + + local new_inv = sorts[data.sort](player, data) + + if new_inv then + local inv = player:get_inventory() + inv:set_list("main", new_inv) + end +end + ------------------------------------------------------------------------------- -local registry = { +local _ = { -- Groups is_group = is_group, extract_groups = extract_groups, @@ -366,6 +464,10 @@ local registry = { -- Sorting search = search, + name_sort = name_sort, + count_sort = count_sort, + sort_inventory = sort_inventory, + get_sorting_idx = get_sorting_idx, sort_by_category = sort_by_category, apply_recipe_filters = apply_recipe_filters, @@ -446,7 +548,7 @@ function i3.get(...) local t = {} for i, var in ipairs{...} do - t[i] = registry[var] + t[i] = _[var] end return unpack(t) diff --git a/etc/gui.lua b/etc/gui.lua index 0bed973..da67edb 100644 --- a/etc/gui.lua +++ b/etc/gui.lua @@ -8,13 +8,14 @@ local clr, ESC, check_privs = i3.get("clr", "ESC", "check_privs") local min, max, floor, ceil, round = i3.get("min", "max", "floor", "ceil", "round") local sprintf, find, match, sub, upper = i3.get("fmt", "find", "match", "sub", "upper") local reg_items, reg_tools, reg_entities = i3.get("reg_items", "reg_tools", "reg_entities") -local maxn, sort, concat, copy, insert, remove = i3.get("maxn", "sort", "concat", "copy", "insert", "remove") +local maxn, sort, concat, copy, insert, remove = + i3.get("maxn", "sort", "concat", "copy", "insert", "remove") local true_str, is_fav, is_num = i3.get("true_str", "is_fav", "is_num") -local is_group, extract_groups, item_has_groups = - i3.get("is_group", "extract_groups", "item_has_groups") local groups_to_items, compression_active, compressible = i3.get("groups_to_items", "compression_active", "compressible") +local get_sorting_idx, is_group, extract_groups, item_has_groups = + i3.get("get_sorting_idx", "is_group", "extract_groups", "item_has_groups") local function fmt(elem, ...) if not fs_elements[elem] then @@ -404,6 +405,84 @@ local function get_container(fs, data, player, yoffset, ctn_len, award_list, awa end end +local function show_popup(fs, data) + if data.confirm_trash then + fs("style_type[box;colors=#999,#999,#808080,#808080]") + + for _ = 1, 3 do + fs("box", 2.97, 10.75, 4.3, 0.5, "") + end + + fs("label", 3.12, 11, "Confirm trash?") + fs("image_button", 5.17, 10.75, 1, 0.5, "", "confirm_trash_yes", "Yes") + fs("image_button", 6.27, 10.75, 1, 0.5, "", "confirm_trash_no", "No") + + elseif data.show_settings then + fs("style_type[box;colors=#999,#999,#808080,#808080]") + + for _ = 1, 3 do + fs("box", 2.1, 9.25, 6, 2, "") + end + + for _ = 1, 3 do + fs("box", 2.1, 9.25, 6, 0.5, "#707070") + end + + fs("image_button", 7.75, 9.35, 0.25, 0.25, PNG.cancel_hover .. "^\\[brighten", "close_settings", "") + + local show_home = data.show_setting == "home" + local show_sorting = data.show_setting == "sorting" + local show_misc = data.show_setting == "misc" + + fs(fmt("style[setting_home;textcolor=%s;sound=i3_click]", show_home and "#ff0" or "#fff")) + fs(fmt("style[setting_sorting;textcolor=%s;sound=i3_click]", show_sorting and "#ff0" or "#fff")) + fs(fmt("style[setting_misc;textcolor=%s;sound=i3_click]", show_misc and "#ff0" or "#fff")) + + fs("button", 2.2, 9.25, 1.8, 0.55, "setting_home", "Home") + fs("button", 4, 9.25, 1.8, 0.55, "setting_sorting", "Sorting") + fs("button", 5.8, 9.25, 1.8, 0.55, "setting_misc", "Misc.") + + if show_home then + local home_pos = data.home or "" + home_pos = home_pos:gsub(",", ", "):gsub("%(", ""):gsub("%)", "") + local home_str = fmt("Home position: %s", home_pos) + home_str = data.home and home_str or ES"No home set" + + fs("button", 2.1, 9.7, 6, 0.8, "", home_str) + fs("image_button", 4.2, 10.4, 1.8, 0.7, "", "set_home", "Set home") + + elseif show_sorting then + fs("button", 2.1, 9.7, 6, 0.8, "", ES"Select the inventory sorting method:") + + fs(fmt("style[prev_sort;fgimg=%s;fgimg_hovered=%s]", PNG.prev, PNG.prev_hover)) + fs(fmt("style[next_sort;fgimg=%s;fgimg_hovered=%s]", PNG.next, PNG.next_hover)) + + fs("image_button", 2.2, 10.6, 0.35, 0.35, "", "prev_sort", "") + fs("image_button", 7.65, 10.6, 0.35, 0.35, "", "next_sort", "") + + fs("style[sort_method;font=bold;font_size=20]") + fs("button", 2.55, 10.35, 5.1, 0.8, "sort_method", data.sort:gsub("^%l", upper)) + + local idx = get_sorting_idx(data.sort) + local desc = i3.sorting_methods[idx].description + + if desc then + fs(fmt("tooltip[%s;%s]", "sort_method", desc)) + end + + elseif show_misc then + fs("checkbox", 2.4, 10.05, + "inv_compress", ES"Inventory compression", tostring(data.inv_compress)) + + fs("checkbox", 2.4, 10.5, + "auto_sorting", ES"Automatic sorting", tostring(data.auto_sorting)) + + fs("checkbox", 2.4, 10.95, + "reverse_sorting", ES"Reverse sorting", tostring(data.reverse_sorting)) + end + end +end + local function get_inventory_fs(player, data, fs) fs("listcolors[#bababa50;#bababa99]") @@ -481,10 +560,10 @@ local function get_inventory_fs(player, data, fs) fs("scroll_container_end[]") local btn = { - {"trash", ES"Clear inventory"}, - {"sort_az", ES"Sort items (A-Z)"}, - {"sort_za", ES"Sort items (Z-A)"}, - {"compress", ES"Compress items"}, + {"trash", ES"Clear inventory"}, + {"sort", ES"Sort items"}, + {"settings", ES"Settings"}, + {"home", ES"Go home"}, } for i, v in ipairs(btn) do @@ -493,21 +572,11 @@ local function get_inventory_fs(player, data, fs) fs(fmt("style[%s;fgimg=%s;fgimg_hovered=%s;content_offset=0]", btn_name, PNG[btn_name], PNG[fmt("%s_hover", btn_name)])) - fs("image_button", i + 3.447 - (i * 0.4), 11.43, 0.35, 0.35, "", btn_name, "") + fs("image_button", i + 3.43 - (i * 0.4), 11.43, 0.35, 0.35, "", btn_name, "") fs(fmt("tooltip[%s;%s]", btn_name, tooltip)) end - if data.confirm_trash then - fs("style_type[box;colors=#999,#999,#808080,#808080]") - - for _ = 1, 3 do - fs("box", 2.97, 10.75, 4.3, 0.5, "") - end - - fs("label", 3.12, 11, "Confirm trash?") - fs("image_button", 5.17, 10.75, 1, 0.5, "", "confirm_trash_yes", "Yes") - fs("image_button", 6.27, 10.75, 1, 0.5, "", "confirm_trash_no", "No") - end + show_popup(fs, data) end local function get_tooltip(item, info, pos) diff --git a/etc/inventory.lua b/etc/inventory.lua index c9652a8..7452ae4 100644 --- a/etc/inventory.lua +++ b/etc/inventory.lua @@ -7,11 +7,11 @@ local fmt, find, match, sub, lower = i3.get("fmt", "find", "match", "sub", "lowe local vec_new, vec_mul, vec_eq, vec_round = i3.get("vec_new", "vec_mul", "vec_eq", "vec_round") local sort, copy, insert, remove, indexof = i3.get("sort", "copy", "insert", "remove", "indexof") -local msg, is_str, is_fav = i3.get("msg", "is_str", "is_fav") +local msg, is_fav = i3.get("msg", "is_fav") local is_group, extract_groups, groups_to_items = i3.get("is_group", "extract_groups", "groups_to_items") -local search, sort_by_category, apply_recipe_filters = - i3.get("search", "sort_by_category", "apply_recipe_filters") +local search, get_sorting_idx, sort_inventory, sort_by_category, apply_recipe_filters = + i3.get("search", "get_sorting_idx", "sort_inventory", "sort_by_category", "apply_recipe_filters") local show_item, spawn_item, clean_name, compressible, check_privs = i3.get("show_item", "spawn_item", "clean_name", "compressible", "check_privs") @@ -40,6 +40,8 @@ local function reset_data(data) data.export_usg = nil data.alt_items = nil data.confirm_trash = nil + data.show_settings = nil + data.show_setting = "home" data.items = data.items_raw if data.current_itab > 1 then @@ -66,104 +68,6 @@ local function get_recipes(player, item) 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() @@ -174,6 +78,13 @@ local function get_stack(player, stack) end end +local function safe_teleport(player, pos) + pos.y = pos.y + 0.5 + local vel = player:get_velocity() + player:add_velocity(vec_mul(vel, -1)) + player:set_pos(pos) +end + i3.new_tab { name = "inventory", description = S"Inventory", @@ -181,6 +92,7 @@ i3.new_tab { fields = function(player, data, fields) local name = player:get_player_name() + local inv = player:get_inventory() local sb_inv = fields.scrbar_inv if fields.skins then @@ -194,6 +106,9 @@ i3.new_tab { data.subcat = indexof(i3.SUBCAT, sub(field, 5)) break + elseif sub(field, 1, 8) == "setting_" then + data.show_setting = match(field, "_(%w+)$") + elseif find(field, "waypoint_%d+") then local id, action = match(field, "_(%d+)_(%w+)$") id = tonumber(id) @@ -206,12 +121,7 @@ i3.new_tab { 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) - + safe_teleport(player, pos) msg(name, fmt("Teleported to %s", clr("#ff0", waypoint.name))) elseif action == "refresh" then @@ -242,23 +152,79 @@ i3.new_tab { end end - if fields.trash then + if fields.quit then + data.confirm_trash = nil + data.show_settings = nil + + elseif fields.trash then + data.show_settings = nil data.confirm_trash = true + elseif fields.settings then + data.confirm_trash = nil + data.show_settings = 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.close_settings then + data.show_settings = nil - elseif fields.sort_az or fields.sort_za then - sort_itemlist(player, fields.sort_az) + elseif fields.sort then + sort_inventory(player, data) + + elseif fields.prev_sort or fields.next_sort then + local idx = get_sorting_idx(data.sort) + local tot = #i3.sorting_methods + + idx = idx - (fields.prev_sort and 1 or -1) + + if idx > tot then + idx = 1 + elseif idx == 0 then + idx = tot + end + + data.sort = i3.sorting_methods[idx].name + + elseif fields.inv_compress then + data.inv_compress = false + + if fields.inv_compress == "true" then + data.inv_compress = true + end + + elseif fields.auto_sorting then + data.auto_sorting = false + + if fields.auto_sorting == "true" then + data.auto_sorting = true + end + + elseif fields.reverse_sorting then + data.reverse_sorting = false + + if fields.reverse_sorting == "true" then + data.reverse_sorting = true + end + + elseif fields.home then + if not data.home then + return msg(name, "No home set") + elseif not check_privs(name, {home = true}) then + return msg(name, "'home' privilege missing") + end + + safe_teleport(player, core.string_to_pos(data.home)) + msg(name, S"Welcome back home!") + + elseif fields.set_home then + data.home = core.pos_to_string(player:get_pos(), 1) elseif sb_inv and sub(sb_inv, 1, 3) == "CHG" then data.scrbar_inv = tonumber(match(sb_inv, "%d+")) diff --git a/etc/styles.lua b/etc/styles.lua index a083afc..dc56d92 100644 --- a/etc/styles.lua +++ b/etc/styles.lua @@ -11,8 +11,8 @@ local PNG = { next = "i3_next.png", arrow = "i3_arrow.png", trash = "i3_trash.png", - sort_az = "i3_sort_az.png", - sort_za = "i3_sort_za.png", + sort = "i3_sort.png", + settings = "i3_settings.png", compress = "i3_compress.png", fire = "i3_fire.png", fire_anim = "i3_fire_anim.png", @@ -36,14 +36,15 @@ local PNG = { visible = "i3_visible.png^\\[brighten", nonvisible = "i3_non_visible.png", exit = "i3_exit.png", + home = "i3_home.png", cancel_hover = "i3_cancel.png^\\[brighten", search_hover = "i3_search.png^\\[brighten", export_hover = "i3_export.png^\\[brighten", trash_hover = "i3_trash.png^\\[brighten^\\[colorize:#f00:100", compress_hover = "i3_compress.png^\\[brighten", - sort_az_hover = "i3_sort_az.png^\\[brighten", - sort_za_hover = "i3_sort_za.png^\\[brighten", + sort_hover = "i3_sort.png^\\[brighten", + settings_hover = "i3_settings.png^\\[brighten", prev_hover = "i3_next_hover.png^\\[transformFX", next_hover = "i3_next_hover.png", tab_hover = "i3_tab_hover.png", @@ -58,13 +59,14 @@ local PNG = { add_hover = "i3_add.png^\\[brighten", refresh_hover = "i3_refresh.png^\\[brighten", exit_hover = "i3_exit.png^\\[brighten", + home_hover = "i3_home.png^\\[brighten", } 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] - style_type[image_button,item_image_button;border=false;sound=i3_click] + style_type[image_button,item_image_button,checkbox;border=false;sound=i3_click] style_type[item_image_button;bgimg_hovered=%s] style[pagenum,no_item,no_rcp;font=bold;font_size=18] @@ -82,7 +84,7 @@ local styles = string.format([[ style[craft_rcp,craft_usg;noclip=true;font_size=16;sound=i3_craft; bgimg=i3_btn9.png;bgimg_hovered=i3_btn9_hovered.png; bgimg_pressed=i3_btn9_pressed.png;bgimg_middle=4,6] - style[confirm_trash_yes,confirm_trash_no;noclip=true;font_size=16; + style[confirm_trash_yes,confirm_trash_no,set_home;noclip=true;font_size=16; bgimg=i3_btn9.png;bgimg_hovered=i3_btn9_hovered.png; bgimg_pressed=i3_btn9_pressed.png;bgimg_middle=4,6] ]], @@ -104,6 +106,7 @@ local fs_elements = { image = "image[%f,%f;%f,%f;%s]", tooltip = "tooltip[%f,%f;%f,%f;%s]", button = "button[%f,%f;%f,%f;%s;%s]", + checkbox = "checkbox[%f,%f;%s;%s;%s]", item_image = "item_image[%f,%f;%f,%f;%s]", hypertext = "hypertext[%f,%f;%f,%f;%s;%s]", bg9 = "background9[%f,%f;%f,%f;%s;false;%u]", diff --git a/init.lua b/init.lua index 99ace14..aec119a 100644 --- a/init.lua +++ b/init.lua @@ -30,6 +30,7 @@ i3 = { }, META_SAVES = { + home = true, bag_item = true, bag_size = true, waypoints = true, @@ -38,14 +39,17 @@ i3 = { }, -- Caches - init_items = {}, - recipes_cache = {}, - usages_cache = {}, - fuel_cache = {}, + init_items = {}, + fuel_cache = {}, + usages_cache = {}, + recipes_cache = {}, + + tabs = {}, + craft_types = {}, + recipe_filters = {}, search_filters = {}, - craft_types = {}, - tabs = {}, + sorting_methods = {}, files = { api = lf("/etc/api.lua"), @@ -140,18 +144,24 @@ local function init_data(player, info) 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 + data.filter = "" + data.pagenum = 1 + data.items = i3.init_items + data.items_raw = i3.init_items + data.favs = {} + data.sort = "alphabetical" + data.show_setting = "home" + data.auto_sorting = false + data.reverse_sorting = false + data.inv_compress = true + data.export_counts = {} + data.current_tab = 1 + data.current_itab = 1 + data.subcat = 1 + data.scrbar_inv = 0 + data.compress = true + data.lang_code = get_lang_code(info) + data.fs_version = info.formspec_version core.after(0, i3.set_fs, player) end diff --git a/textures/i3_home.png b/textures/i3_home.png new file mode 100644 index 0000000000000000000000000000000000000000..a8d49a7e900ac20f7a767fabb8d4e4db9f227113 GIT binary patch literal 6195 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 G`TqfYNfH47 literal 0 HcmV?d00001 diff --git a/textures/i3_sort.png b/textures/i3_sort.png new file mode 100644 index 0000000000000000000000000000000000000000..767a10a51dd44bd32887232cb66106633aa731e7 GIT binary patch literal 6393 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 literal 0 HcmV?d00001 diff --git a/textures/i3_sort_az.png b/textures/i3_sort_az.png deleted file mode 100644 index 54d519781dd37b597e208912ecd7cb134777c275..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3168 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