diff --git a/API.md b/API.md index a3fc9bd..0b0f95a 100644 --- a/API.md +++ b/API.md @@ -7,8 +7,7 @@ Custom tabs can be added to the `i3` inventory as follow (example): ```Lua -i3.new_tab { - name = "stuff", +i3.new_tab("stuff", { description = "Stuff", image = "image.png", -- Optional, adds an image next to the tab description @@ -28,16 +27,16 @@ i3.new_tab { fields = function(player, data, fields) end, -} +}) ``` - `player` is an `ObjectRef` to the user. - `data` are the user data. - `fs` is the formspec table which is callable with a metamethod. Each call adds a new entry. -#### `i3.set_fs(player[, extra_formspec])` +#### `i3.set_fs(player)` -Updates the current formspec. `extra_formspec` adds an additional formspec string. +Updates the current formspec. #### `i3.remove_tab(tabname)` diff --git a/README.md b/README.md index 7275178..293d6cb 100644 --- a/README.md +++ b/README.md @@ -10,20 +10,20 @@ This mod features a modern, powerful inventory menu with a good user experience. This mod requires **Minetest 5.4+** #### List of features: - - Crafting Guide (only in survival mode) + - Crafting Guide (survival mode only) - Progressive Mode¹ - Quick Crafting - - Backpacks - - 3D Player Model Preview - - Inventory Sorting (with optional compression) + - 3D Player Model Real-Time Preview + - Inventory Sorting (+ options: compression, reverse mode, automation, etc.) + - Item List Compression (**`moreblocks`** is supported) - Item Bookmarks - Waypoints - - Item List Compression (**`moreblocks`** is supported) + - Bags + - Home **¹** *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`.* - #### This mod officially supports the following mods: - [**`3d_armor`**](https://content.minetest.net/packages/stu/3d_armor/) - [**`skinsdb`**](https://content.minetest.net/packages/bell07/skinsdb/) @@ -46,12 +46,10 @@ value of the setting `display_density_factor` in your `minetest.conf`. Note that `i3` uses a larger inventory than the usual inventories in Minetest games. Thus, most chests will be unadapted to this inventory size. -The `i3` inventory is 9 slots wide by default (without backpack), such as Minecraft. +The `i3` inventory is 9 slots wide by default, such as Minecraft. -Report any bug on the [**Bug Tracker**](https://github.com/minetest-mods/i3/issues). +Report bugs on the [**Bug Tracker**](https://github.com/minetest-mods/i3/issues). Love this mod? Donations are appreciated: https://www.paypal.me/jpg84240 -Demo video (outdated): https://www.youtube.com/watch?v=25nCAaqeacU - ![Preview](https://user-images.githubusercontent.com/7883281/140816791-693a5c8a-a7d1-47d4-a45d-883cc008ae8a.png) diff --git a/init.lua b/init.lua index 5176231..633defc 100644 --- a/init.lua +++ b/init.lua @@ -15,13 +15,6 @@ i3 = { MIN_FORMSPEC_VERSION = 4, SAVE_INTERVAL = 600, -- Player data save interval (in seconds) - BAG_SIZES = { - 4*9 + 3, - 4*9 + 6, - 4*9 + 9, - 4*9 + 25, - }, - SUBCAT = { "bag", "armor", @@ -36,7 +29,7 @@ i3 = { bag_size = true, waypoints = true, inv_items = true, - reject_items = true, + drop_items = true, known_recipes = true, }, @@ -56,18 +49,19 @@ i3 = { files = { api = lf("/src/api.lua"), bags = lf("/src/bags.lua"), + callbacks = lf("/src/callbacks.lua"), common = lf("/src/common.lua"), compress = lf("/src/compress.lua"), + detached = lf("/src/detached_inv.lua"), groups = lf("/src/groups.lua"), gui = lf("/src/gui.lua"), - inventory = lf("/src/inventory.lua"), model_alias = lf("/src/model_aliases.lua"), progressive = lf("/src/progressive.lua"), recipes = lf("/src/recipes.lua"), styles = lf("/src/styles.lua"), }, - progressive_mode = core.settings:get_bool "i3_progressive_mode", + progressive_mode = core.settings:get_bool"i3_progressive_mode", item_compression = core.settings:get_bool("i3_item_compression", true), } @@ -75,14 +69,16 @@ i3.files.common() i3.files.api() i3.files.compress() i3.files.groups() -i3.files.inventory() +i3.files.callbacks() local storage = core.get_mod_storage() -local slz, dslz, str_to_pos, add_hud_waypoint = i3.get("slz", "dslz", "str_to_pos", "add_hud_waypoint") +local slz, dslz, ESC, str_to_pos, add_hud_waypoint = + i3.get("slz", "dslz", "ESC", "str_to_pos", "add_hud_waypoint") i3.data = dslz(storage:get_string "data") or {} -local init_backpack = i3.files.bags() +local init_bags = i3.files.bags() +local init_inventories = i3.files.detached() local init_recipes = i3.files.recipes() local function get_lang_code(info) @@ -146,6 +142,7 @@ local function init_data(player, info) i3.data[name] = i3.data[name] or {} local data = i3.data[name] + data.player_name = ESC(name) data.filter = "" data.pagenum = 1 data.items = i3.init_items @@ -165,6 +162,9 @@ local function init_data(player, info) data.lang_code = get_lang_code(info) data.fs_version = info.formspec_version + local inv = player:get_inventory() + inv:set_size("main", i3.INV_SIZE) + core.after(0, i3.set_fs, player) end @@ -216,12 +216,12 @@ core.register_on_joinplayer(function(player) 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_bags(player) + init_inventories(player) init_waypoints(player) init_hudbar(player) end) diff --git a/sounds/i3_cannot.ogg b/sounds/i3_cannot.ogg new file mode 100644 index 0000000..8f97186 Binary files /dev/null and b/sounds/i3_cannot.ogg differ diff --git a/src/api.lua b/src/api.lua index eba7ab4..87c1c1b 100644 --- a/src/api.lua +++ b/src/api.lua @@ -161,7 +161,7 @@ function i3.get_recipes(item) } end -function i3.set_fs(player, _fs) +function i3.set_fs(player) if not player or player.is_fake_player then return end local name = player:get_player_name() local data = i3.data[name] @@ -171,32 +171,34 @@ function i3.set_fs(player, _fs) sort_inventory(player, data) end - local fs = fmt("%s%s", make_fs(player, data), _fs or "") + local fs = make_fs(player, data) player:set_inventory_formspec(fs) end -function i3.new_tab(def) - if not true_table(def) then - return err "i3.new_tab: tab definition missing" - elseif not true_str(def.name) then +function i3.new_tab(name, def) + if not true_str(name) then return err "i3.new_tab: tab name missing" + elseif not true_table(def) then + return err "i3.new_tab: tab definition missing" elseif not true_str(def.description) then return err "i3.new_tab: description missing" elseif #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 + def.name = name + insert(i3.tabs, def) end -function i3.remove_tab(tabname) - if not true_str(tabname) then +function i3.remove_tab(name) + if not true_str(name) then return err "i3.remove_tab: tab name missing" end for i, def in ipairs(i3.tabs) do - if tabname == def.name then + if name == def.name then remove(i3.tabs, i) + break end end end @@ -217,32 +219,31 @@ function i3.set_tab(player, tabname) return end - local found - for i, def in ipairs(i3.tabs) do - if not found and def.name == tabname then + if def.name == tabname then data.tab = i - found = true + return end end - if not found then - return err(fmt("i3.set_tab: tab name '%s' does not exist", tabname)) - end + err(fmt("i3.set_tab: tab name '%s' does not exist", tabname)) end -function i3.override_tab(tabname, newdef) - if not true_table(newdef) then - return err "i3.override_tab: tab definition missing" - elseif not true_str(newdef.name) then +function i3.override_tab(name, newdef) + if not true_str(name) then return err "i3.override_tab: tab name missing" + elseif not true_table(newdef) then + return err "i3.override_tab: tab definition missing" elseif not true_str(newdef.description) then return err "i3.override_tab: description missing" end + newdef.name = name + for i, def in ipairs(i3.tabs) do - if def.name == tabname then + if def.name == name then i3.tabs[i] = newdef + break end end end diff --git a/src/bags.lua b/src/bags.lua index 375b490..a7a23b2 100644 --- a/src/bags.lua +++ b/src/bags.lua @@ -1,55 +1,161 @@ -local S, fmt, msg, spawn_item = i3.get("S", "fmt", "msg", "spawn_item") +local S, ES, fmt, clr, msg, slz, dslz, play_sound, create_inventory = + i3.get("S", "ES", "fmt", "clr", "msg", "slz", "dslz", "play_sound", "create_inventory") -local function init_backpack(player) +local function get_content_inv(name) + return core.get_inventory { + type = "detached", + name = fmt("i3_bag_content_%s", name) + } +end + +local function get_content(content) + local t = {} + + for i, v in pairs(content) do + local stack = ItemStack(v.name) + local meta, wear = v.meta, v.wear + + if meta then + local m = stack:get_meta() + m:from_table(meta) + end + + if wear then + stack:set_wear(wear) + end + + t[i] = stack + end + + return t +end + +local function safe_format(stack) + local meta = stack:get_meta():to_table() + local wear = stack:get_wear() + local has_meta = next(meta.fields) + + local info = {} + info.name = fmt("%s %u", stack:get_name(), stack:get_count()) + + if has_meta then + info.meta = meta + end + + if wear > 0 then + info.wear = wear + end + + return info +end + +local function init_bags(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) + local bag = create_inventory(fmt("i3_bag_%s", name), { + allow_put = function(inv, _, _, stack) + local empty = inv:is_empty"main" + local item_group = core.get_item_group(stack:get_name(), "bag") - 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 + if empty and item_group > 0 and item_group <= 4 then return 1 end - msg(name, S"This is not a backpack") - return 0 + if not empty then + msg(name, S"There is already a bag") + else + msg(name, S"This is not a bag") + end + + return 0, play_sound(name, "i3_cannot", 0.8) end, on_put = function(_, _, _, stack) - local stackname = stack:get_name() - data.bag_item = stackname - data.bag_size = minetest.get_item_group(stackname, "bag") + data.bag_item = safe_format(stack) + data.bag_size = core.get_item_group(stack:get_name(), "bag") + + local meta = stack:get_meta() + local content = dslz(meta:get_string"content") + + if content then + local inv = get_content_inv(name) + inv:set_list("main", get_content(content)) + end - inv:set_size("main", i3.BAG_SIZES[data.bag_size]) i3.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) + local content = get_content_inv(name) + content:set_list("main", {}) + i3.set_fs(player) end, - }) + }, name) - data.bag:set_size("main", 1) + bag:set_size("main", 1) if data.bag_item then - data.bag:set_stack("main", 1, data.bag_item) + bag:set_list("main", get_content{data.bag_item}) + end + + local function save_content(inv) + local bagstack = bag:get_stack("main", 1) + local meta = bagstack:get_meta() + + if inv:is_empty("main") then + meta:set_string("description", "") + meta:set_string("content", "") + else + local list = inv:get_list"main" + local t = {} + + for i = 1, #list do + local stack = list[i] + + if not stack:is_empty() then + t[i] = safe_format(stack) + end + end + + local function count_items() + local c = 0 + + for _ in pairs(t) do + c = c + 1 + end + + return c + end + + meta:set_string("description", "") + meta:set_string("description", ES("@1 (contains @2 / @3 stacks)", + bagstack:get_short_description(), clr("#ff0", count_items()), data.bag_size * 4)) + meta:set_string("content", slz(t)) + end + + bag:set_stack("main", 1, bagstack) + data.bag_item = safe_format(bagstack) + + i3.set_fs(player) + end + + local bag_content = create_inventory(fmt("i3_bag_content_%s", name), { + on_move = save_content, + on_put = save_content, + on_take = save_content, + }, name) + + bag_content:set_size("main", 4*4) + + if data.bag_item then + local meta = bag:get_stack("main", 1):get_meta() + local content = dslz(meta:get_string"content") or {} + bag_content:set_list("main", get_content(content)) end end @@ -60,21 +166,21 @@ local bag_recipes = { {"group:wool", "group:wool", "group:wool"}, {"group:wool", "group:wool", "group:wool"}, }, - size = 1, + size = 2, }, medium = { rcp = { {"farming:string", "i3:bag_small", "farming:string"}, {"farming:string", "i3:bag_small", "farming:string"}, }, - size = 2, + size = 3, }, large = { rcp = { {"farming:string", "i3:bag_medium", "farming:string"}, {"farming:string", "i3:bag_medium", "farming:string"}, }, - size = 3, + size = 4, }, } @@ -84,12 +190,12 @@ for size, item in pairs(bag_recipes) do core.register_craftitem(bagname, { description = fmt("%s Backpack", size:gsub("^%l", string.upper)), inventory_image = fmt("i3_bag_%s.png", size), + groups = {bag = item.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} + core.register_craft{output = bagname, recipe = item.rcp} + core.register_craft{type = "fuel", recipe = bagname, burntime = 3} end -return init_backpack +return init_bags diff --git a/src/inventory.lua b/src/callbacks.lua similarity index 77% rename from src/inventory.lua rename to src/callbacks.lua index e597286..3bb34fd 100644 --- a/src/inventory.lua +++ b/src/callbacks.lua @@ -1,20 +1,18 @@ local _, get_inventory_fs = i3.files.gui() local S, clr = i3.get("S", "clr") -local min, ceil, random = i3.get("min", "ceil", "random") +local min, random = i3.get("min", "random") local reg_items, reg_aliases = i3.get("reg_items", "reg_aliases") local fmt, find, match, sub, lower, split = i3.get("fmt", "find", "match", "sub", "lower", "split") local vec_new, vec_eq, vec_round = i3.get("vec_new", "vec_eq", "vec_round") local sort, copy, insert, remove, indexof = i3.get("sort", "copy", "insert", "remove", "indexof") -local is_group, extract_groups, groups_to_items = - i3.get("is_group", "extract_groups", "groups_to_items") -local msg, is_fav, pos_to_str, str_to_pos, add_hud_waypoint = - i3.get("msg", "is_fav", "pos_to_str", "str_to_pos", "add_hud_waypoint") +local msg, is_fav, pos_to_str, str_to_pos, add_hud_waypoint, play_sound = + i3.get("msg", "is_fav", "pos_to_str", "str_to_pos", "add_hud_waypoint", "play_sound") local search, get_sorting_idx, sort_inventory, sort_by_category, get_recipes = i3.get("search", "get_sorting_idx", "sort_inventory", "sort_by_category", "get_recipes") -local show_item, get_stack, clean_name, compressible, check_privs, safe_teleport = - i3.get("show_item", "get_stack", "clean_name", "compressible", "check_privs", "safe_teleport") +local show_item, get_stack, craft_stack, clean_name, compressible, check_privs, safe_teleport = + i3.get("show_item", "get_stack", "craft_stack", "clean_name", "compressible", "check_privs", "safe_teleport") local function reset_data(data) data.filter = "" @@ -40,13 +38,12 @@ local function reset_data(data) end end -i3.new_tab { - name = "inventory", +i3.new_tab("inventory", { description = S"Inventory", formspec = get_inventory_fs, fields = function(player, data, fields) - local name = player:get_player_name() + local name = data.player_name local inv = player:get_inventory() local sb_inv = fields.scrbar_inv @@ -56,9 +53,9 @@ i3.new_tab { skins.set_player_skin(player, _skins[id]) end - if fields.reject_items then - local items = split(fields.reject_items, ",") - data.reject_items = items + if fields.drop_items then + local items = split(fields.drop_items, ",") + data.drop_items = items end for field in pairs(fields) do @@ -176,6 +173,7 @@ i3.new_tab { for _, v in ipairs(data.waypoints) do if vec_eq(vec_round(pos), vec_round(str_to_pos(v.pos))) then + play_sound(name, "i3_cannot", 0.8) return msg(name, "You already set a waypoint at this position") end end @@ -201,9 +199,9 @@ i3.new_tab { return i3.set_fs(player) end, -} +}) -local function select_item(player, name, data, _f) +local function select_item(player, data, _f) local item for field in pairs(_f) do @@ -271,7 +269,7 @@ local function select_item(player, name, data, _f) item = reg_aliases[item] or item if not reg_items[item] then return end - if core.is_creative_enabled(name) then + if core.is_creative_enabled(data.player_name) then local stack = ItemStack(item) local stackmax = stack:get_stack_max() stack = fmt("%s %s", item, stackmax) @@ -294,55 +292,7 @@ local function select_item(player, name, data, _f) 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 @@ -431,7 +381,7 @@ local function rcp_fields(player, data, fields) data.scrbar_usg = 1 end else - select_item(player, name, data, fields) + select_item(player, data, fields) end end @@ -439,11 +389,12 @@ 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.") + return false, core.kick_player(name, S"Come back when your client is up-to-date.") elseif formname ~= "" then return false end + --print(dump(fields)) local data = i3.data[name] if not data then return end @@ -456,6 +407,7 @@ core.register_on_player_receive_fields(function(player, formname, fields) data.pagenum = 1 data.itab = tonumber(f:sub(-1)) sort_by_category(data) + break end end @@ -486,15 +438,6 @@ core.register_on_dieplayer(function(player) 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 - i3.set_fs(player) end) @@ -524,27 +467,3 @@ core.register_on_player_inventory_action(function(player, _, _, info) i3.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() - core.sound_play("i3_trash", {to_player = name, gain = 1.0}, true) - - if not core.is_creative_enabled(name) then - i3.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/src/common.lua b/src/common.lua index dad5e69..5ea32f0 100644 --- a/src/common.lua +++ b/src/common.lua @@ -349,9 +349,60 @@ local function get_stack(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 = math.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 = math.ceil(count / stackmax) + local leftover = count + + for _ = 1, iter do + local c = math.min(stackmax, leftover) + local stack = ItemStack(fmt("%s %s", stackname, c)) + get_stack(player, stack) + leftover = leftover - stackmax + end +end + +local function play_sound(name, sound, volume) + core.sound_play(sound, {to_player = name, gain = volume}, true) +end + local function safe_teleport(player, pos) local name = player:get_player_name() - core.sound_play("i3_teleport", {to_player = name, gain = 1.0}, true) + play_sound(name, "i3_teleport", 1.0) pos.y = pos.y + 0.5 local vel = player:get_velocity() @@ -446,7 +497,7 @@ local function compress_items(list, start_i) return new_inv end -local function reject_items(player, inv, list, start_i, rej) +local function drop_items(player, inv, list, start_i, rej) for i = start_i, #list do local stack = list[i] local name = stack:get_name() @@ -459,17 +510,17 @@ local function reject_items(player, inv, list, start_i, rej) end end - return inv:get_list("main") + return inv:get_list"main" end local function sort_inventory(player, data) local inv = player:get_inventory() - local list = inv:get_list("main") - local size = inv:get_size("main") + local list = inv:get_list"main" + local size = inv:get_size"main" local start_i = data.ignore_hotbar and 10 or 1 - if true_table(data.reject_items) then - list = reject_items(player, inv, list, start_i, data.reject_items) + if true_table(data.drop_items) then + list = drop_items(player, inv, list, start_i, data.drop_items) end if data.inv_compress then @@ -540,9 +591,11 @@ local _ = { -- Misc. functions get_stack = get_stack, + craft_stack = craft_stack, show_item = show_item, spawn_item = spawn_item, clean_name = clean_name, + play_sound = play_sound, safe_teleport = safe_teleport, add_hud_waypoint = add_hud_waypoint, @@ -554,6 +607,7 @@ local _ = { pos_to_str = core.pos_to_string, str_to_pos = core.string_to_pos, check_privs = core.check_player_privs, + create_inventory = core.create_detached_inventory, -- Registered items reg_items = core.registered_items, diff --git a/src/detached_inv.lua b/src/detached_inv.lua new file mode 100644 index 0000000..9b4ceb2 --- /dev/null +++ b/src/detached_inv.lua @@ -0,0 +1,32 @@ +local fmt, play_sound, create_inventory = i3.get("fmt", "play_sound", "create_inventory") + +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() + play_sound(name, "i3_trash", 1.0) + + if not core.is_creative_enabled(name) then + i3.set_fs(player) + end + end, +}) + +trash:set_size("main", 1) + +local function init_inventories(player) + local name = player:get_player_name() + + local output_rcp = create_inventory(fmt("i3_output_rcp_%s", name), {}, name) + output_rcp:set_size("main", 1) + + local output_usg = create_inventory(fmt("i3_output_usg_%s", name), {}, name) + output_usg:set_size("main", 1) +end + +return init_inventories diff --git a/src/gui.lua b/src/gui.lua index b084a17..c979e12 100644 --- a/src/gui.lua +++ b/src/gui.lua @@ -124,10 +124,8 @@ local function get_stack_max(inv, data, is_recipe, rcp) return max_stacks end -local function get_inv_slots(data, fs) - local inv_x, inv_y = 0.22, 6.9 - local width, size, spacing = i3.HOTBAR_LEN, 1, 0.1 - local bag = data.bag_size +local function get_inv_slots(fs) + local inv_x, inv_y, size, spacing = 0.22, 6.9, 1, 0.1 fs("style_type[box;colors=#77777710,#77777710,#777,#777]") @@ -138,14 +136,9 @@ local function get_inv_slots(data, fs) fs(fmt("style_type[list;size=%f;spacing=%f]", size, spacing), fmt("list[current_player;main;%f,%f;%u,1;]", inv_x, inv_y, i3.HOTBAR_LEN)) - if bag then - local params = {{10, 0.892}, {11, 0.8}, {12, 0.726}, {13, 0.663}} - width, size = unpack(params[bag]) - end - fs(fmt("style_type[list;size=%f;spacing=%f]", size, spacing), fmt("list[current_player;main;%f,%f;%u,%u;%u]", inv_x, inv_y + 1.15, - width, (bag and i3.BAG_SIZES[data.bag_size] or i3.INV_SIZE) / width, i3.HOTBAR_LEN), + i3.HOTBAR_LEN, i3.INV_SIZE / i3.HOTBAR_LEN, i3.HOTBAR_LEN), "style_type[list;size=1;spacing=0.15]") fs("listring[current_player;craft]listring[current_player;main]") @@ -294,8 +287,8 @@ local function get_waypoint_fs(fs, data, player, yextra, ctn_len) end local function get_container(fs, data, player, yoffset, ctn_len, award_list, awards_unlocked, award_list_nb) - local name = player:get_player_name() - add_subtitle(fs, "player_name", 0, ctn_len, 22, true, ESC(name)) + local name = data.player_name + add_subtitle(fs, "player_name", 0, ctn_len, 22, true, name) if damage_enabled then local hp = data.hp or player:get_hp() or 20 @@ -342,20 +335,39 @@ local function get_container(fs, data, player, yoffset, ctn_len, award_list, awa local function not_installed(modname) fs("hypertext", 0, yextra + 0.9, ctn_len, 0.6, "not_installed", - fmt("
not installed
", modname)) + fmt("
not installed
", + modname)) end if data.subcat == 1 then - fs(fmt("list[detached:%s_backpack;main;0,%f;1,1;]", ESC(name), yextra + 0.7)) + fs(fmt("list[detached:i3_bag_%s;main;0,%f;1,1;]", name, yextra + 0.7)) - if not data.bag:get_stack("main", 1):is_empty() then - fs("hypertext", 1.2, yextra + 0.89, ctn_len - 1.9, 0.8, "bpk", - ES("The inventory is extended by @1 slots", i3.BAG_SIZES[data.bag_size] - i3.INV_SIZE)) + local inv = core.get_inventory { + type = "detached", + name = fmt("i3_bag_%s", data.player_name) + } + + if not inv:is_empty"main" then + fs("image", 0.5, yextra + 1.85, 0.6, 0.6, PNG.arrow_content) + fs(fmt("style[content;bgimg=%s;fgimg=i3_blank.png;bgimg_middle=10,10;sound=]", PNG.bg_content)) + fs("image_button", 1.1, yextra + 0.5, 4.75, 4.75, "", "content", "") + fs("hypertext", 1.3, yextra + 0.8, 4.3, 0.6, "content", + fmt("
%s
", ES"Content")) + + local x, size, spacing = 1.45, 0.9, 0.12 + + if data.bag_size == 4 then + x, size, spacing = 1.7, 0.8, 0.1 + end + + fs(fmt("style_type[list;size=%f;spacing=%f]", size, spacing)) + fs(fmt("list[detached:i3_bag_content_%s;main;%f,%f;4,%u;]", name, x, yextra + 1.3, data.bag_size)) + fs("style_type[list;size=1;spacing=0.15]") end elseif data.subcat == 2 then if i3.modules.armor then - fs(fmt("list[detached:%s_armor;armor;0,%f;3,2;]", ESC(name), yextra + 0.7)) + fs(fmt("list[detached:%s_armor;armor;0,%f;3,2;]", name, yextra + 0.7)) local armor_def = armor.def[name] @@ -438,13 +450,18 @@ local function show_popup(fs, data) 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(",", ", "):sub(2,-2):gsub("%.%d", ""):gsub( - "(%-?%d+)", clr("#dbeeff", "%1")) - local home_str = fmt("Home position: %s", home_pos) - home_str = data.home and home_str or ES"No home set" + local coords, c, str = {"X", "Y", "Z"}, 0, ES"No home set" - fs("button", 2.1, 9.7, 6, 0.8, "", home_str) + if data.home then + str = data.home:gsub(",", " "):sub(2,-2):gsub("%.%d", ""):gsub( + "(%-?%d+)", function(a) + c = c + 1 + return fmt("%s: ", + coords[c], a) + end) + end + + fs("hypertext", 2.1, 9.9, 6, 0.6, "home_pos", fmt("
%s
", str)) fs("image_button", 4.2, 10.4, 1.8, 0.7, "", "set_home", "Set home") elseif show_sorting then @@ -476,11 +493,11 @@ local function show_popup(fs, data) fs("box", 5.4, 10.68, 2.4, 0.45, "#707070") end - fs("style[reject_items;font_size=15;font=mono;textcolor=#dbeeff]") - fs(fmt("field[5.4,10.68;2.4,0.45;reject_items;Reject items:;%s]", - ESC(concat(data.reject_items or {}, ",")))) - fs("field_close_on_enter[reject_items;false]") - fs(fmt("tooltip[reject_items;%s;#707070;#fff]", + fs("style[drop_items;font_size=15;font=mono;textcolor=#dbeeff]") + fs(fmt("field[5.4,10.68;2.4,0.45;drop_items;Drop items:;%s]", + ESC(concat(data.drop_items or {}, ",")))) + fs("field_close_on_enter[drop_items;false]") + fs(fmt("tooltip[drop_items;%s;#707070;#fff]", ES"Format:" .. "\n" .. ("mod:item,mod:item, ..."):gsub("(%a+:%a+)", clr("#bddeff", "%1")))) end @@ -490,11 +507,9 @@ end local function get_inventory_fs(player, data, fs) fs("listcolors[#bababa50;#bababa99]") - get_inv_slots(data, fs) + get_inv_slots(fs) local props = player:get_properties() - local name = player:get_player_name() - local ctn_len, ctn_hgt = 5.7, 6.3 local yoffset = 0 @@ -522,7 +537,10 @@ local function get_inventory_fs(player, data, fs) local awards_unlocked = 0 local max_val = damage_enabled and 12 or 7 - if i3.modules.armor and data.subcat == 2 then + if data.subcat == 1 and data.bag_size then + max_val = max_val + 32 + + elseif i3.modules.armor and data.subcat == 2 then if data.scrbar_inv >= max_val then data.scrbar_inv = data.scrbar_inv + 10 end @@ -530,7 +548,7 @@ local function get_inventory_fs(player, data, fs) max_val = max_val + 10 elseif i3.modules.awards and data.subcat == 4 then - award_list = awards.get_award_states(name) + award_list = awards.get_award_states(data.player_name) award_list_nb = #award_list for i = 1, award_list_nb do @@ -710,19 +728,18 @@ local function get_output_fs(fs, data, rcp, is_recipe, shapeless, right, btn_siz fs(fmt("style_type[list;size=%f]", i3.ITEM_BTN_SIZE)) fs("listcolors[#bababa50;#bababa99]") - fs(fmt("list[detached:i3_output_%s;main;%f,%f;1,1;]", rcp_usg, X + 0.11, Y)) + fs(fmt("list[detached:i3_output_%s_%s;main;%f,%f;1,1;]", rcp_usg, data.player_name, X + 0.11, Y)) fs("button", X + 0.11, Y, i3.ITEM_BTN_SIZE, i3.ITEM_BTN_SIZE, _name, "") local inv = core.get_inventory { type = "detached", - name = fmt("i3_output_%s", rcp_usg) + name = fmt("i3_output_%s_%s", rcp_usg, data.player_name) } inv:set_stack("main", 1, item) pos = {x = X + 0.11, y = Y} else fs("image", X, Y - 0.11, bt_s, bt_s, PNG.slot) - fs("item_image_button", X + 0.11, Y, i3.ITEM_BTN_SIZE, i3.ITEM_BTN_SIZE, fmt("%s %u", name, count * (is_recipe and data.scrbar_rcp or data.scrbar_usg or 1)), diff --git a/src/styles.lua b/src/styles.lua index 3218592..ad4b17e 100644 --- a/src/styles.lua +++ b/src/styles.lua @@ -1,6 +1,7 @@ local PNG = { bg = "i3_bg.png", bg_full = "i3_bg_full.png", + bg_content = "i3_bg_content.png", bar = "i3_bar.png", hotbar = "i3_hotbar.png", search = "i3_search.png", @@ -10,6 +11,7 @@ local PNG = { prev = "i3_next.png^\\[transformFX", next = "i3_next.png", arrow = "i3_arrow.png", + arrow_content = "i3_arrow_content.png", trash = "i3_trash.png", sort = "i3_sort.png", settings = "i3_settings.png", @@ -66,9 +68,10 @@ 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,checkbox;border=false;sound=i3_click] + style_type[image_button,item_image_button,checkbox,dropdown;border=false;sound=i3_click] style_type[item_image_button;bgimg_hovered=%s] + style[nofav;sound=i3_cannot] style[pagenum,no_item,no_rcp;font=bold;font_size=18] style[exit;fgimg=%s;fgimg_hovered=%s;content_offset=0] style[cancel;fgimg=%s;fgimg_hovered=%s;content_offset=0] diff --git a/tests/test_tabs.lua b/tests/test_tabs.lua index 7d0b5ec..207fd6a 100644 --- a/tests/test_tabs.lua +++ b/tests/test_tabs.lua @@ -1,25 +1,22 @@ -i3.new_tab { - name = "test1", +i3.new_tab("test1", { description = "Test 1 Test 1", image = "i3_heart.png", formspec = function(player, data, fs) fs("label[3,1;Test 1]") end, -} +}) -i3.new_tab { - name = "test2", +i3.new_tab("test2", { description = "Test 2", image = "i3_mesepick.png", formspec = function(player, data, fs) fs("label[3,1;Test 2]") end, -} +}) -i3.new_tab { - name = "test3", +i3.new_tab("test3", { description = "Test 3", access = function(player, data) @@ -36,10 +33,9 @@ i3.new_tab { fields = function(player, data, fields) i3.set_fs(player, "label[3,2;Test extra_fs]") end, -} +}) i3.override_tab("test2", { - name = "test2", description = "Test override", image = "i3_mesepick.png", diff --git a/textures/i3_arrow_content.png b/textures/i3_arrow_content.png new file mode 100644 index 0000000..e907493 Binary files /dev/null and b/textures/i3_arrow_content.png differ diff --git a/textures/i3_bg_content.png b/textures/i3_bg_content.png new file mode 100644 index 0000000..9550725 Binary files /dev/null and b/textures/i3_bg_content.png differ diff --git a/textures/i3_blank.png b/textures/i3_blank.png new file mode 100644 index 0000000..5a76666 Binary files /dev/null and b/textures/i3_blank.png differ