15 Commits

20 changed files with 500 additions and 800 deletions

157
api.lua
View File

@ -2,49 +2,40 @@ local S = minetest.get_translator("unified_inventory")
local F = minetest.formspec_escape
local ui = unified_inventory
local function is_recipe_craftable(recipe)
-- Ensure the ingedients exist
for _, itemname in pairs(recipe.items) do
local groups = string.find(itemname, "group:")
if groups then
if not ui.get_group_item(string.sub(groups, 8)).item then
return false
end
else
-- Possibly an item
if not minetest.registered_items[itemname]
or minetest.get_item_group(itemname, "not_in_craft_guide") ~= 0 then
return false
end
end
end
return true
end
-- Create detached creative inventory after loading all mods
minetest.after(0.01, function()
local rev_aliases = {}
for original, newname in pairs(minetest.registered_aliases) do
if not rev_aliases[newname] then
rev_aliases[newname] = {}
end
table.insert(rev_aliases[newname], original)
for source, target in pairs(minetest.registered_aliases) do
if not rev_aliases[target] then rev_aliases[target] = {} end
table.insert(rev_aliases[target], source)
end
-- Filtered item list
ui.items_list = {}
for name, def in pairs(minetest.registered_items) do
if ui.is_itemdef_listable(def) then
if (not def.groups.not_in_creative_inventory or
def.groups.not_in_creative_inventory == 0) and
def.description and def.description ~= "" then
table.insert(ui.items_list, name)
-- Alias processing: Find recipes that belong to the current item name
local all_names = rev_aliases[name] or {}
table.insert(all_names, name)
for _, itemname in ipairs(all_names) do
local recipes = minetest.get_all_craft_recipes(itemname)
for _, recipe in ipairs(recipes or {}) do
if is_recipe_craftable(recipe) then
ui.register_craft(recipe)
for _, player_name in ipairs(all_names) do
local recipes = minetest.get_all_craft_recipes(player_name)
if recipes then
for _, recipe in ipairs(recipes) do
local unknowns
for _,chk in pairs(recipe.items) do
local groupchk = string.find(chk, "group:")
if (not groupchk and not minetest.registered_items[chk])
or (groupchk and not ui.get_group_item(string.gsub(chk, "group:", "")).item)
or minetest.get_item_group(chk, "not_in_craft_guide") ~= 0 then
unknowns = true
end
end
if not unknowns then
ui.register_craft(recipe)
end
end
end
end
@ -52,9 +43,7 @@ minetest.after(0.01, function()
end
table.sort(ui.items_list)
ui.items_list_size = #ui.items_list
print("Unified Inventory. Inventory size: "..ui.items_list_size)
-- Analyse dropped items -> custom "digging" recipes
print("Unified Inventory. inventory size: "..ui.items_list_size)
for _, name in ipairs(ui.items_list) do
local def = minetest.registered_items[name]
-- Simple drops
@ -144,51 +133,29 @@ minetest.after(0.01, function()
end
end
end
-- Step 1: Initialize cache for looking up groups
unified_inventory.init_matching_cache()
-- Step 2: Find all matching items for the given spec (groups)
local get_matching_spec_items = unified_inventory.get_matching_items
for outputitemname, recipes in pairs(ui.crafts_for.recipe) do
-- List of crafts that return this item string (variable "_")
-- Problem: The group cache must be initialized after all mods finished loading
-- thus, invalid recipes might be indexed. Hence perform filtering with `new_recipe_list`
local new_recipe_list = {}
for _, recipes in pairs(ui.crafts_for.recipe) do
for _, recipe in ipairs(recipes) do
local ingredient_items = {}
for _, spec in pairs(recipe.items) do
-- Get items that fit into this spec (group or item name)
local specname = ItemStack(spec):get_name()
for item_name, _ in pairs(get_matching_spec_items(specname)) do
ingredient_items[item_name] = true
local matches_spec = ui.canonical_item_spec_matcher(spec)
for _, name in ipairs(ui.items_list) do
if matches_spec(name) then
ingredient_items[name] = true
end
end
end
for name, _ in pairs(ingredient_items) do
if not ui.crafts_for.usage[name] then
if ui.crafts_for.usage[name] == nil then
ui.crafts_for.usage[name] = {}
end
table.insert(ui.crafts_for.usage[name], recipe)
end
if next(ingredient_items) then
-- There's at least one known ingredient: mark as good recipe
-- PS: What whatll be done about partially incomplete recipes?
table.insert(new_recipe_list, recipe)
end
end
ui.crafts_for.recipe[outputitemname] = new_recipe_list
end
for _, callback in ipairs(ui.initialized_callbacks) do
callback()
end
end)
---------------- Home API ----------------
-- load_home
local function load_home()
local input = io.open(ui.home_filename, "r")
if not input then
@ -205,7 +172,6 @@ local function load_home()
end
io.close(input)
end
load_home()
function ui.set_home(player, pos)
@ -234,8 +200,7 @@ function ui.go_home(player)
return false
end
---------------- Crafting API ----------------
-- register_craft
function ui.register_craft(options)
if not options.output then
return
@ -247,23 +212,20 @@ function ui.register_craft(options)
if options.type == "normal" and options.width == 0 then
options = { type = "shapeless", items = options.items, output = options.output, width = 0 }
end
local item_name = itemstack:get_name()
if not ui.crafts_for.recipe[item_name] then
ui.crafts_for.recipe[item_name] = {}
end
table.insert(ui.crafts_for.recipe[item_name],options)
for _, callback in ipairs(ui.craft_registered_callbacks) do
callback(item_name, options)
if not ui.crafts_for.recipe[itemstack:get_name()] then
ui.crafts_for.recipe[itemstack:get_name()] = {}
end
table.insert(ui.crafts_for.recipe[itemstack:get_name()],options)
end
local craft_type_defaults = {
width = 3,
height = 3,
uses_crafting_grid = false,
}
function ui.craft_type_defaults(name, options)
if not options.description then
options.description = name
@ -274,7 +236,8 @@ end
function ui.register_craft_type(name, options)
ui.registered_craft_types[name] = ui.craft_type_defaults(name, options)
ui.registered_craft_types[name] =
ui.craft_type_defaults(name, options)
end
@ -331,8 +294,6 @@ ui.register_craft_type("digging_chance", {
height = 1,
})
---------------- GUI registrations ----------------
function ui.register_page(name, def)
ui.pages[name] = def
end
@ -348,45 +309,11 @@ function ui.register_button(name, def)
table.insert(ui.buttons, def)
end
---------------- Callback registrations ----------------
function ui.register_on_initialized(callback)
if type(callback) ~= "function" then
error(("Initialized callback must be a function, %s given."):format(type(callback)))
end
table.insert(ui.initialized_callbacks, callback)
end
function ui.register_on_craft_registered(callback)
if type(callback) ~= "function" then
error(("Craft registered callback must be a function, %s given."):format(type(callback)))
end
table.insert(ui.craft_registered_callbacks, callback)
end
---------------- List getters ----------------
function ui.get_recipe_list(output)
return ui.crafts_for.recipe[output]
end
function ui.get_registered_outputs()
local outputs = {}
for item_name, _ in pairs(ui.crafts_for.recipe) do
table.insert(outputs, item_name)
end
return outputs
end
---------------- Player utilities ----------------
function ui.is_creative(playername)
return minetest.check_player_privs(playername, {creative=true})
or minetest.settings:get_bool("creative_mode")
end
---------------- Formspec helpers ----------------
function ui.single_slot(xpos, ypos, bright)
return string.format("background9[%f,%f;%f,%f;ui_single_slot%s.png;false;16]",
xpos, ypos, ui.imgscale, ui.imgscale, (bright and "_bright" or "") )

View File

@ -114,8 +114,6 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end
end)
-- Player slots are preserved when unified_inventory is disabled. Do not allow modification.
-- Fix: use a detached inventory and store the data separately.
local function save_bags_metadata(player, bags_inv)
local is_empty = true
local bags = {}
@ -165,7 +163,7 @@ local function load_bags_metadata(player, bags_inv)
save_bags_metadata(player, bags_inv)
end
-- Legacy: Clean up old player lists
-- Clean up deprecated garbage after saving
for i = 1, 4 do
local bag = "bag" .. i
player_inv:set_size(bag, 0)
@ -174,29 +172,46 @@ end
minetest.register_on_joinplayer(function(player)
local player_name = player:get_player_name()
local bags_inv = minetest.create_detached_inventory(player_name .. "_bags", {
allow_put = function(inv, listname, index, stack, player)
local new_slots = stack:get_definition().groups.bagslots
if not new_slots then
return 0 -- ItemStack is not a bag.
end
-- The execution order of `allow_put`/`allow_take` is not defined.
-- We do not know the replacement ItemStack if the items are swapped.
-- Hence, bag slot upgrades and downgrades are not possible with the
-- current API.
if not player:get_inventory():is_empty(listname .. "contents") then
-- Legacy: in case `allow_take` is not executed on old Minetest versions.
return 0
end
return 1
end,
local bags_inv = minetest.create_detached_inventory(player_name .. "_bags",{
on_put = function(inv, listname, index, stack, player)
player:get_inventory():set_size(listname .. "contents",
stack:get_definition().groups.bagslots)
save_bags_metadata(player, inv)
end,
allow_put = function(inv, listname, index, stack, player)
local new_slots = stack:get_definition().groups.bagslots
if not new_slots then
return 0
end
local player_inv = player:get_inventory()
local old_slots = player_inv:get_size(listname .. "contents")
if new_slots >= old_slots then
return 1
end
-- using a smaller bag, make sure it fits
local old_list = player_inv:get_list(listname .. "contents")
local new_list = {}
local slots_used = 0
local use_new_list = false
for i, v in ipairs(old_list) do
if v and not v:is_empty() then
slots_used = slots_used + 1
use_new_list = i > new_slots
new_list[slots_used] = v
end
end
if new_slots >= slots_used then
if use_new_list then
player_inv:set_list(listname .. "contents", new_list)
end
return 1
end
-- New bag is smaller: Disallow inserting
return 0
end,
allow_take = function(inv, listname, index, stack, player)
if player:get_inventory():is_empty(listname .. "contents") then
return stack:get_count()
@ -215,20 +230,6 @@ minetest.register_on_joinplayer(function(player)
load_bags_metadata(player, bags_inv)
end)
minetest.register_allow_player_inventory_action(function(player, action, inventory, info)
-- From detached inventory -> player inventory: put & take callbacks
if action ~= "put" or not info.listname:find("bag%dcontents") then
return
end
if info.stack:get_definition().groups.bagslots then
-- Problem 1: empty bags could be moved into their own slots
-- Problem 2: cannot reliably keep track of ItemStack ownership due to
--> Disallow all external bag movements into this list
return 0
end
end)
-- register bag tools
minetest.register_tool("unified_inventory:bag_small", {
description = S("Small Bag"),

View File

@ -1,5 +1,3 @@
local ui = unified_inventory
local function default_refill(stack)
stack:set_count(stack:get_stack_max())
local itemdef = minetest.registered_items[stack:get_name()]
@ -14,17 +12,20 @@ end
minetest.register_on_joinplayer(function(player)
local player_name = player:get_player_name()
unified_inventory.players[player_name] = {}
unified_inventory.current_index[player_name] = 1 -- Item (~page) index
unified_inventory.current_index[player_name] = 1
unified_inventory.filtered_items_list[player_name] =
unified_inventory.items_list
unified_inventory.items_list
unified_inventory.activefilter[player_name] = ""
unified_inventory.active_search_direction[player_name] = "nochange"
unified_inventory.apply_filter(player, "", "nochange")
unified_inventory.current_searchbox[player_name] = ""
unified_inventory.current_category[player_name] = "all"
unified_inventory.current_category_scroll[player_name] = 0
unified_inventory.alternate[player_name] = 1
unified_inventory.current_item[player_name] = nil
unified_inventory.current_craft_direction[player_name] = "recipe"
unified_inventory.set_inventory_formspec(player,
unified_inventory.default)
-- Refill slot
local refill = minetest.create_detached_inventory(player_name.."refill", {
@ -46,21 +47,13 @@ minetest.register_on_joinplayer(function(player)
refill:set_size("main", 1)
end)
minetest.register_on_mods_loaded(function()
minetest.register_on_joinplayer(function(player)
-- After everything is initialized, set up the formspec
ui.apply_filter(player, "", "nochange")
ui.set_inventory_formspec(player, unified_inventory.default)
end)
end)
local function apply_new_filter(player, search_text, new_dir)
local player_name = player:get_player_name()
minetest.sound_play("click", {to_player=player_name, gain = 0.1})
ui.apply_filter(player, search_text, new_dir)
ui.current_searchbox[player_name] = search_text
ui.set_inventory_formspec(player, ui.current_page[player_name])
unified_inventory.apply_filter(player, search_text, new_dir)
unified_inventory.current_searchbox[player_name] = search_text
unified_inventory.set_inventory_formspec(player,
unified_inventory.current_page[player_name])
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
@ -73,12 +66,9 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end
-- always take new search text, even if not searching on it yet
local dirty_search_filter = false
if fields.searchbox
and fields.searchbox ~= unified_inventory.current_searchbox[player_name] then
unified_inventory.current_searchbox[player_name] = fields.searchbox
dirty_search_filter = true
end
@ -99,14 +89,19 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
unified_inventory.current_page[player_name])
end
if fields.next_category or fields.prev_category then
local step = fields.next_category and 1 or -1
local scroll_old = ui.current_category_scroll[player_name]
local scroll_new = math.max(0, math.min(#ui.category_list - ui_peruser.pagecols, scroll_old + step))
if scroll_old ~= scroll_new then
ui.current_category_scroll[player_name] = scroll_new
ui.set_inventory_formspec(player,
if fields.next_category then
local scroll = math.min(#unified_inventory.category_list-ui_peruser.pagecols, unified_inventory.current_category_scroll[player_name] + 1)
if scroll ~= unified_inventory.current_category_scroll[player_name] then
unified_inventory.current_category_scroll[player_name] = scroll
unified_inventory.set_inventory_formspec(player,
unified_inventory.current_page[player_name])
end
end
if fields.prev_category then
local scroll = math.max(0, unified_inventory.current_category_scroll[player_name] - 1)
if scroll ~= unified_inventory.current_category_scroll[player_name] then
unified_inventory.current_category_scroll[player_name] = scroll
unified_inventory.set_inventory_formspec(player,
unified_inventory.current_page[player_name])
end
end
@ -163,7 +158,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
-- Check clicked item image button
local clicked_item
for name, value in pairs(fields) do
local new_dir, mangled_item = string.match(name, "^[0-9]*_?item_button_([a-z]+)_(.*)$")
local new_dir, mangled_item = string.match(name, "^item_button_([a-z]+)_(.*)$")
if new_dir and mangled_item then
clicked_item = unified_inventory.demangle_for_formspec(mangled_item)
if string.sub(clicked_item, 1, 6) == "group:" then
@ -203,16 +198,13 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
if fields.searchbutton
or fields.key_enter_field == "searchbox" then
if dirty_search_filter then
ui.apply_filter(player, ui.current_searchbox[player_name], "nochange")
ui.set_inventory_formspec(player, ui.current_page[player_name])
minetest.sound_play("paperflip2",
{to_player=player_name, gain = 1.0})
end
unified_inventory.apply_filter(player, unified_inventory.current_searchbox[player_name], "nochange")
unified_inventory.set_inventory_formspec(player,
unified_inventory.current_page[player_name])
minetest.sound_play("paperflip2",
{to_player=player_name, gain = 1.0})
elseif fields.searchresetbutton then
if ui.activefilter[player_name] ~= "" then
apply_new_filter(player, "", "nochange")
end
apply_new_filter(player, "", "nochange")
end
-- alternate buttons
@ -250,8 +242,11 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
unified_inventory.current_page[player_name])
end)
minetest.register_on_leaveplayer(function(player)
local player_name = player:get_player_name()
minetest.remove_detached_inventory(player_name.."_bags")
minetest.remove_detached_inventory(player_name.."refill")
end)
if minetest.delete_detached_inventory then
minetest.register_on_leaveplayer(function(player)
local player_name = player:get_player_name()
minetest.delete_detached_inventory(player_name.."_bags")
minetest.delete_detached_inventory(player_name.."craftrecipe")
minetest.delete_detached_inventory(player_name.."refill")
end)
end

View File

@ -96,9 +96,6 @@ function unified_inventory.register_category(category_name, config)
end
update_category_list()
end
-- TODO: Mark these for removal. They are pretty much useless
function unified_inventory.set_category_symbol(category_name, symbol)
ensure_category_exists(category_name)
unified_inventory.registered_categories[category_name].symbol = symbol

View File

@ -1,8 +1,7 @@
unified_inventory API
=====================
This file provides information about the API of unified_inventory
and can be viewed in Markdown readers.
This file provides information about the API of unified_inventory.
API revisions within unified_inventory can be checked using:
@ -21,66 +20,6 @@ Grouped by use-case, afterwards sorted alphabetically.
* Checks whether creative is enabled or the player has `creative`
Callbacks
---------
Register a callback that will be run whenever a craft is registered via unified_inventory.register_craft.
This callback is run before any recipe ingredients checks, hence it is also executed on recipes that are
purged after all mods finished loading.
unified_inventory.register_on_craft_registered(
function (item_name, options)
-- item_name (string): name of the output item, equivalent to `ItemStack:get_name()`
-- options (table): definition table of crafts registered by `unified_inventory.register_craft`
end
)
Register a callback that will be run after all mods have loaded and after the unified_inventory mod has initialised all its internal structures:
unified_inventory.register_on_initialized(callback)
-- The callback is passed no arguments
Accessing Data
--------------
These methods should be used instead of accessing the unified_inventory data structures directly - this will ensure your code survives any potential restructuring of the mod.
Get a list of recipes for a particular output item:
unified_inventory.get_recipe_list(output_item)
Returns a list of tables, each holding a recipe definition, like:
{
{
type = "normal",
items = { "default:stick", "default:stick", "default:stick", "default:stick" },
output = "default:wood",
width = 2
},
{
type = "shapeless",
items = { "default:tree" },
output = "default:wood 4",
width = 0
},
...
}
Get a list of all the output items crafts have been registered for:
unified_inventory.get_registered_outputs()
Returns a list of item names, like:
{
"default:stone",
"default:chest",
"default:brick",
"doors:door_wood",
...
}
Pages
-----
@ -166,57 +105,68 @@ Register a non-standard craft recipe:
Categories
----------
* `unified_inventory.register_category(name, def)`
* Registers a new category
* `name` (string): internal category name
* `def` (optional, table): also its fields are optional
Register a new category:
The config table (second argument) is optional, and all its members are optional
See the unified_inventory.set_category_* functions for more details on the members of the config table
unified_inventory.register_category("category_name", {
symbol = source,
-- ^ Can be in the format "mod_name:item_name" or "texture.png",
symbol = "mod_name:item_name" or "texture.png",
label = "Human Readable Label",
index = 5,
-- ^ Categories are sorted by index. Lower numbers appear before higher ones.
-- By default, the name is translated to a number: AA -> 0.0101, ZZ -> 0.2626
--- Predefined category indices: "all" = -2, "uncategorized" = -1
items = {
"mod_name:item_name",
"another_mod:different_item"
}
-- ^ List of items within this category
})
* `unified_inventory.remove_category(name)`
* Removes an entire category
Modifier functions (to be removed)
Add / override the symbol for a category:
The category does not need to exist first
The symbol can be an item name or a texture image
If unset this will default to "default:stick"
* `unified_inventory.set_category_symbol(name, source)`
* Changes the symbol of the category. The category does not need to exist yet.
* `name` (string): internal category name
* `source` (string, optional): `"mod_name:item_name"` or `"texture.png"`.
Defaults to `"default:stick"` if not specified.
* `unified_inventory.set_category_label(name, label)`
* Changes the human readable label of the category.
* `name` (string): internal category name
* `label` (string): human readable label. Defaults to the category name.
* `unified_inventory.set_category_index(name, index)`
* Changes the sorting index of the category.
* `name` (string): internal category name
* `index` (numeric): any real number
unified_inventory.set_category_symbol("category_name", "mod_name:item_name" or "texture.png")
Item management
Add / override the human readable label for a category:
If unset this will default to the category name
* ` unified_inventory.add_category_item(name, itemname)`
* Adds a single item to the category
* `itemname` (string): self-explanatory
* `unified_inventory.add_category_items(name, { itemname1, itemname2, ... }`
* Same as above but with multiple items
* `unified_inventory.remove_category_item(name, itemname)`
* Removes an item from the category
* `unified_inventory.find_category(itemname)`
* Looks up the first category containing this item
* Returns: category name (string) or nil
* `unified_inventory.find_categories(itemname)`
* Looks up the item name within all registered categories
* Returns: array of category names (table)
unified_inventory.set_category_label("category_name", "Human Readable Label")
Add / override the sorting index of the category:
Must be a number, can also be negative (-5) or fractional (2.345)
This determines the position the category appears in the list of categories
The "all" meta-category has index -2, the "misc"/"uncategorized" meta-category has index -1, use a negative number smaller than these to make a category appear before these in the list
By default categories are sorted alphabetically with an index between 0.0101(AA) and 0.2626(ZZ)
unified_inventory.set_category_index("category_name", 5)
Add a single item to a category:
unified_inventory.add_category_item("category_name", "mod_name:item_name")
Add multiple items to a category:
unified_inventory.add_category_items("category_name", {
"mod_name:item_name",
"another_mod:different_item"
})
Remove an item from a category:
unified_inventory.remove_category_item("category_name", "mod_name:item_name")
Remove a category entirely:
unified_inventory.remove_category("category_name")
Finding existing items in categories:
This will find the first category an item exists in
It should be used for checking if an item is catgorised
Returns "category_name" or nil
unified_inventory.find_category("mod_name:item_name")
This will find all the categories an item exists in
Returns a number indexed table (list) of category names
unified_inventory.find_categories("mod_name:item_name")

101
group.lua
View File

@ -1,5 +1,29 @@
local S = minetest.get_translator("unified_inventory")
local ui = unified_inventory
function unified_inventory.canonical_item_spec_matcher(spec)
local specname = ItemStack(spec):get_name()
if specname:sub(1, 6) ~= "group:" then
return function (itemname)
return itemname == specname
end
end
local group_names = specname:sub(7):split(",")
return function (itemname)
local itemdef = minetest.registered_items[itemname]
for _, group_name in ipairs(group_names) do
if (itemdef.groups[group_name] or 0) == 0 then
return false
end
end
return true
end
end
function unified_inventory.item_matches_spec(item, spec)
local itemname = ItemStack(item):get_name()
return unified_inventory.canonical_item_spec_matcher(spec)(itemname)
end
function unified_inventory.extract_groupnames(groupname)
local specname = ItemStack(groupname):get_name()
@ -10,6 +34,22 @@ function unified_inventory.extract_groupnames(groupname)
return table.concat(group_names, S(" and ")), #group_names
end
unified_inventory.registered_group_items = {
mesecon_conductor_craftable = "mesecons:wire_00000000_off",
stone = "default:cobble",
wood = "default:wood",
book = "default:book",
sand = "default:sand",
leaves = "default:leaves",
tree = "default:tree",
vessel = "vessels:glass_bottle",
wool = "wool:white",
}
function unified_inventory.register_group_item(groupname, itemname)
unified_inventory.registered_group_items[groupname] = itemname
end
-- This is used when displaying craft recipes, where an ingredient is
-- specified by group rather than as a specific item. A single-item group
@ -27,7 +67,6 @@ end
-- It may be a comma-separated list of group names. This is really a
-- "group:..." ingredient specification, minus the "group:" prefix.
-- TODO Replace this with the more efficient spec matcher (below)
local function compute_group_item(group_name_list)
local group_names = group_name_list:split(",")
local candidate_items = {}
@ -86,61 +125,3 @@ function unified_inventory.get_group_item(group_name)
return group_item_cache[group_name]
end
--[[
This is for filtering known items by groups
e.g. find all items that match "group:flower,yellow" (flower AND yellow groups)
]]
local spec_matcher = {}
function unified_inventory.init_matching_cache()
for _, name in ipairs(ui.items_list) do
-- we only need to care about groups, exact items are handled separately
for group, value in pairs(minetest.registered_items[name].groups) do
if value and value ~= 0 then
if not spec_matcher[group] then
spec_matcher[group] = {}
end
spec_matcher[group][name] = true
end
end
end
end
--[[
Retrieves all matching items
Arguments:
specname (string): Item name or group(s) to filter
Output:
{
matchingitem1 = true,
...
}
]]
function unified_inventory.get_matching_items(specname)
if specname:sub(1,6) ~= "group:" then
return { [specname] = true }
end
local accepted = {}
for i, group in ipairs(specname:sub(7):split(",")) do
if i == 1 then
-- First step: Copy all possible item names in this group
for name, _ in pairs(spec_matcher[group] or {}) do
accepted[name] = true
end
else
-- Perform filtering
if spec_matcher[group] then
for name, _ in pairs(accepted) do
accepted[name] = spec_matcher[group][name]
end
else
-- No matching items
return {}
end
end
end
return accepted
end

View File

@ -1,11 +1,5 @@
-- Unified Inventory
if not minetest.features.formspec_version_element then
-- At least formspec_version[] is the minimal feature requirement
error("Unified Inventory requires Minetest version 5.4.0 or newer.\n" ..
" Please update Minetest or use an older version of Unified Inventory.")
end
local modpath = minetest.get_modpath(minetest.get_current_modname())
local worldpath = minetest.get_worldpath()
@ -30,8 +24,6 @@ unified_inventory = {
filtered_items_list = {},
pages = {},
buttons = {},
initialized_callbacks = {},
craft_registered_callbacks = {},
-- Homepos stuff
home_pos = {},
@ -50,11 +42,8 @@ unified_inventory = {
trash_enabled = (minetest.settings:get_bool("unified_inventory_trash") ~= false),
imgscale = 1.25,
list_img_offset = 0.13,
standard_background = "bgcolor[#0000]background9[0,0;1,1;ui_formbg_9_sliced.png;true;16]",
hide_disabled_buttons = minetest.settings:get_bool("unified_inventory_hide_disabled_buttons", false),
version = 4
standard_background = "background9[0,0;1,1;ui_formbg_9_sliced.png;true;16]",
version = 2
}
local ui = unified_inventory
@ -67,16 +56,10 @@ ui.style_full = {
formspec_y = 1,
formw = 17.75,
formh = 12.25,
-- Item browser size, pos
pagecols = 8,
pagerows = 9,
page_x = 10.75,
page_y = 2.30,
-- Item browser controls
page_buttons_x = 11.60,
page_buttons_y = 10.15,
searchwidth = 3.4,
-- Crafting grid positions
craft_x = 2.8,
craft_y = 1.15,
craftresult_x = 7.8,
@ -88,15 +71,13 @@ ui.style_full = {
craft_guide_resultstr_x = 0.3,
craft_guide_resultstr_y = 0.6,
give_btn_x = 0.25,
-- Tab switching buttons
main_button_x = 0.4,
main_button_y = 11.0,
main_button_cols = 12,
main_button_rows = 1,
-- Tab title position
page_buttons_x = 11.60,
page_buttons_y = 10.15,
searchwidth = 3.4,
form_header_x = 0.4,
form_header_y = 0.4,
-- Generic sizes
btn_spc = 0.85,
btn_size = 0.75,
std_inv_x = 0.3,
@ -108,16 +89,10 @@ ui.style_lite = {
formspec_y = 0.6,
formw = 14,
formh = 9.75,
-- Item browser size, pos
pagecols = 4,
pagerows = 5,
page_x = 10.5,
page_y = 2.15,
-- Item browser controls
page_buttons_x = 10.5,
page_buttons_y = 6.15,
searchwidth = 1.6,
-- Crafting grid positions
craft_x = 2.6,
craft_y = 0.75,
craftresult_x = 5.75,
@ -129,15 +104,13 @@ ui.style_lite = {
craft_guide_resultstr_x = 0.15,
craft_guide_resultstr_y = 0.35,
give_btn_x = 0.15,
-- Tab switching buttons
main_button_x = 10.5,
main_button_y = 8.15,
main_button_cols = 4,
main_button_rows = 2,
-- Tab title position
page_buttons_x = 10.5,
page_buttons_y = 6.15,
searchwidth = 1.6,
form_header_x = 0.2,
form_header_y = 0.2,
-- Generic sizes
btn_spc = 0.8,
btn_size = 0.7,
std_inv_x = 0.1,
@ -194,4 +167,4 @@ end
dofile(modpath.."/item_names.lua")
dofile(modpath.."/waypoints.lua")
dofile(modpath.."/legacy.lua") -- mod compatibility
minetest.log("action", "[unified_inventory] loaded.")

View File

@ -18,16 +18,13 @@ function ui.demangle_for_formspec(str)
return string.gsub(str, "_([0-9]+)_", function (v) return string.char(v) end)
end
-- Get the player-specific unified_inventory style
function ui.get_per_player_formspec(player_name)
local draw_lite_mode = ui.lite_mode and not minetest.check_player_privs(player_name, {ui_full=true})
local style = table.copy(draw_lite_mode and ui.style_lite or ui.style_full)
style.is_lite_mode = draw_lite_mode
return style
return table.copy(draw_lite_mode and ui.style_lite or ui.style_full), draw_lite_mode
end
-- Creates an item image or regular image button with a tooltip
local function formspec_button(ui_peruser, name, image, offset, pos, scale, label)
local element = 'image_button'
if minetest.registered_items[image] then
@ -44,103 +41,118 @@ local function formspec_button(ui_peruser, name, image, offset, pos, scale, labe
string.format("tooltip[%s;%s]", name, F(label or name))
end
-- Add registered buttons (tabs)
local function formspec_tab_buttons(player, formspec, style)
local n = #formspec + 1
function ui.get_formspec(player, page)
if not player then
return ""
end
local player_name = player:get_player_name()
local ui_peruser,draw_lite_mode = ui.get_per_player_formspec(player_name)
ui.current_page[player_name] = page
local pagedef = ui.pages[page]
if not pagedef then
return "" -- Invalid page name
end
local formspec = {
"formspec_version[4]",
"size["..ui_peruser.formw..","..ui_peruser.formh.."]",
pagedef.formspec_prepend and "" or "no_prepend[]",
ui.standard_background
}
local n = 5
local perplayer_formspec = ui.get_per_player_formspec(player_name)
local fsdata = pagedef.get_formspec(player, perplayer_formspec)
formspec[n] = fsdata.formspec
n = n+1
local button_row = 0
local button_col = 0
-- Main buttons
local filtered_inv_buttons = {}
for _, def in pairs(ui.buttons) do
if not (style.is_lite_mode and def.hide_lite) then
if def.condition == nil or def.condition(player) or not ui.hide_disabled_buttons then
table.insert(filtered_inv_buttons, def)
end
for i, def in pairs(ui.buttons) do
if not (draw_lite_mode and def.hide_lite) then
table.insert(filtered_inv_buttons, def)
end
end
local needs_scrollbar = #filtered_inv_buttons > style.main_button_cols * style.main_button_rows
formspec[n] = ("scroll_container[%g,%g;%g,%g;tabbtnscroll;vertical]"):format(
style.main_button_x, style.main_button_y, -- position
style.main_button_cols * style.btn_spc, style.main_button_rows -- size
)
n = n + 1
local j = 1 --Modif NALC (sys4 20/11/2018) 12 buttons max by row
for i, def in pairs(filtered_inv_buttons) do
local pos_x = ((i - 1) % style.main_button_cols) * style.btn_spc
local pos_y = math.floor((i - 1) / style.main_button_cols) * style.btn_spc
if draw_lite_mode and i > 4 then
button_row = 1
button_col = 1
elseif not draw_lite_mode and j > 12 then
button_row = 1
j = 1
end
if def.type == "image" then
if (def.condition == nil or def.condition(player)) then
formspec[n] = string.format("image_button[%g,%g;%g,%g;%s;%s;]",
pos_x, pos_y, style.btn_size, style.btn_size,
if (def.condition == nil or def.condition(player) == true) then
formspec[n] = string.format("image_button[%f,%f;%f,%f;%s;%s;]",
ui_peruser.main_button_x + ui_peruser.btn_spc * (j - 1) - button_col * ui_peruser.btn_spc * 4, -- Modif NALC
ui_peruser.main_button_y + button_row * ui_peruser.btn_spc,
ui_peruser.btn_size,ui_peruser.btn_size,
F(def.image),
F(def.name))
formspec[n+1] = "tooltip["..F(def.name)..";"..(def.tooltip or "").."]"
n = n+2
else
formspec[n] = string.format("image[%g,%g;%g,%g;%s^[colorize:#808080:alpha]",
pos_x, pos_y, style.btn_size, style.btn_size,
def.image)
formspec[n] = string.format("image[%f,%f;%f,%f;%s^[colorize:#808080:alpha]",
ui_peruser.main_button_x + ui_peruser.btn_spc * (j - 1) - button_col * ui_peruser.btn_spc * 4, -- Modif NALC
ui_peruser.main_button_y + button_row * ui_peruser.btn_spc,
ui_peruser.btn_size,ui_peruser.btn_size,def.image)
n = n+1
end
end
j = j + 1 -- Modif NALC
end
formspec[n] = "scroll_container_end[]"
if needs_scrollbar then
local total_rows = math.ceil(#filtered_inv_buttons / style.main_button_cols)
formspec[n+1] = ("scrollbaroptions[max=%i;arrows=hide]"):format(
-- This calculation is not 100% accurate but "good enough"
(total_rows - style.main_button_rows) * style.btn_spc * 10
)
formspec[n+2] = ("scrollbar[%g,%g;0.4,%g;vertical;tabbtnscroll;0]"):format(
style.main_button_x + style.main_button_cols * style.btn_spc - 0.1, -- x pos
style.main_button_y, -- y pos
style.main_button_rows * style.btn_spc -- height
)
formspec[n+3] = "scrollbaroptions[max=1000;arrows=default]"
if fsdata.draw_inventory ~= false then
-- Player inventory
formspec[n] = "listcolors[#00000000;#00000000]"
formspec[n+1] = ui_peruser.standard_inv
n = n+2
end
end
-- Add category GUI elements (top right)
local function formspec_add_categories(player, formspec, ui_peruser)
local player_name = player:get_player_name()
local n = #formspec + 1
if fsdata.draw_item_list == false then
return table.concat(formspec, "")
end
local categories_pos = {
ui_peruser.page_x,
ui_peruser.page_y-ui_peruser.btn_spc-0.5
}
local categories_scroll_pos = {
ui_peruser.page_x,
ui_peruser.form_header_y - (ui_peruser.is_lite_mode and 0 or 0.2)
}
-- Category filters
formspec[n] = string.format("background9[%f,%f;%f,%f;%s;false;16]",
ui_peruser.page_x-0.15, categories_scroll_pos[2],
(ui_peruser.btn_spc * ui_peruser.pagecols) + 0.2, 1.4 + (ui_peruser.is_lite_mode and 0 or 0.2),
local categories_pos = { ui_peruser.page_x, ui_peruser.page_y-ui_peruser.btn_spc-0.5 }
local categories_scroll_pos = { ui_peruser.page_x, ui_peruser.form_header_y-(draw_lite_mode and 0 or 0.2) }
formspec[n] = string.format("background9[%f,%f;%f,%f;%s;false;3]",
ui_peruser.page_x-0.1, categories_scroll_pos[2],
(ui_peruser.btn_spc * ui_peruser.pagecols) + 0.13, 1.4+(draw_lite_mode and 0 or 0.2),
"ui_smallbg_9_sliced.png")
n = n + 1
formspec[n] = string.format("label[%f,%f;%s]",
ui_peruser.page_x,
ui_peruser.form_header_y + (ui_peruser.is_lite_mode and 0.3 or 0.2), F(S("Category:")))
formspec[n] = string.format("label[%f,%f;%s]", ui_peruser.page_x, ui_peruser.form_header_y+(draw_lite_mode and 0.3 or 0.2), "Category:")
n = n + 1
local scroll_offset = 0
local category_count = #ui.category_list
local category_count = #unified_inventory.category_list
if category_count > ui_peruser.pagecols then
scroll_offset = ui.current_category_scroll[player_name]
scroll_offset = unified_inventory.current_category_scroll[player_name]
end
for index, category in ipairs(ui.category_list) do
for index, category in ipairs(unified_inventory.category_list) do
local column = index - scroll_offset
if column > 0 and column <= ui_peruser.pagecols then
local scale = 0.8
if ui.current_category[player_name] == category.name then
if unified_inventory.current_category[player_name] == category.name then
scale = 1
end
formspec[n] = formspec_button(ui_peruser, "category_"..category.name, category.symbol, categories_pos, {column-1, 0}, scale, category.label)
@ -155,13 +167,10 @@ local function formspec_add_categories(player, formspec, ui_peruser)
if category_count > ui_peruser.pagecols and category_count - scroll_offset > ui_peruser.pagecols then
-- next
formspec[n] = formspec_button(ui_peruser, "next_category", "ui_right_icon.png", categories_scroll_pos, {ui_peruser.pagecols - 1, 0}, 0.8, S("Scroll categories right"))
n = n + 1
end
end
local function formspec_add_search_box(player, formspec, ui_peruser)
local player_name = player:get_player_name()
local n = #formspec + 1
-- Search box
formspec[n] = "field_close_on_enter[searchbox;false]"
formspec[n+1] = string.format("field[%f,%f;%f,%f;searchbox;;%s]",
@ -178,16 +187,7 @@ local function formspec_add_search_box(player, formspec, ui_peruser)
ui_peruser.btn_size, ui_peruser.btn_size)
formspec[n+5] = "tooltip[searchresetbutton;"..F(S("Reset search and display everything")).."]"
if ui.activefilter[player_name] ~= "" then
formspec[n+6] = string.format("label[%f,%f;%s: %s]",
ui_peruser.page_x, ui_peruser.page_y - 0.25,
F(S("Filter")), F(ui.activefilter[player_name]))
end
end
local function formspec_add_item_browser(player, formspec, ui_peruser)
local player_name = player:get_player_name()
local n = #formspec + 1
n = n + 6
-- Controls to flip items pages
@ -200,9 +200,9 @@ local function formspec_add_item_browser(player, formspec, ui_peruser)
{ "ui_skip_forward_icon.png", "end_list", S("Last page") },
}
if ui_peruser.is_lite_mode then
btnlist[2] = nil
if draw_lite_mode then
btnlist[5] = nil
btnlist[2] = nil
end
local bn = 0
@ -217,114 +217,69 @@ local function formspec_add_item_browser(player, formspec, ui_peruser)
n = n + 2
end
local no_matches = S("No matching items")
if draw_lite_mode then
no_matches = S("No matches.")
end
-- Items list
if #ui.filtered_items_list[player_name] == 0 then
local no_matches = S("No matching items")
if ui_peruser.is_lite_mode then
no_matches = S("No matches.")
end
formspec[n] = "label["..ui_peruser.page_x..","..(ui_peruser.page_y+0.15)..";" .. F(no_matches) .. "]"
return
end
local dir = ui.active_search_direction[player_name]
local list_index = ui.current_index[player_name]
local page2 = math.floor(list_index / (ui_peruser.items_per_page) + 1)
local pagemax = math.floor(
(#ui.filtered_items_list[player_name] - 1)
/ (ui_peruser.items_per_page) + 1)
for y = 0, ui_peruser.pagerows - 1 do
for x = 0, ui_peruser.pagecols - 1 do
local name = ui.filtered_items_list[player_name][list_index]
local item = minetest.registered_items[name]
if item then
-- Clicked on current item: Flip crafting direction
if name == ui.current_item[player_name] then
local cdir = ui.current_craft_direction[player_name]
if cdir == "recipe" then
dir = "usage"
elseif cdir == "usage" then
dir = "recipe"
else
local dir = ui.active_search_direction[player_name]
local list_index = ui.current_index[player_name]
local page2 = math.floor(list_index / (ui_peruser.items_per_page) + 1)
local pagemax = math.floor(
(#ui.filtered_items_list[player_name] - 1)
/ (ui_peruser.items_per_page) + 1)
for y = 0, ui_peruser.pagerows - 1 do
for x = 0, ui_peruser.pagecols - 1 do
local name = ui.filtered_items_list[player_name][list_index]
local item = minetest.registered_items[name]
if item then
-- Clicked on current item: Flip crafting direction
if name == ui.current_item[player_name] then
local cdir = ui.current_craft_direction[player_name]
if cdir == "recipe" then
dir = "usage"
elseif cdir == "usage" then
dir = "recipe"
end
else
-- Default: use active search direction by default
dir = ui.active_search_direction[player_name]
end
else
-- Default: use active search direction by default
dir = ui.active_search_direction[player_name]
end
local button_name = "item_button_" .. dir .. "_"
.. ui.mangle_for_formspec(name)
formspec[n] = ("item_image_button[%f,%f;%f,%f;%s;%s;]"):format(
ui_peruser.page_x + x * ui_peruser.btn_spc,
ui_peruser.page_y + y * ui_peruser.btn_spc,
ui_peruser.btn_size, ui_peruser.btn_size,
name, button_name
)
local tooltip = item.description
if item.mod_origin then
-- "mod_origin" may not be specified for items that were
-- registered in a callback (during or before ServerEnv init)
tooltip = tooltip .. " [" .. item.mod_origin .. "]"
local button_name = "item_button_" .. dir .. "_"
.. ui.mangle_for_formspec(name)
formspec[n] = ("item_image_button[%f,%f;%f,%f;%s;%s;]"):format(
ui_peruser.page_x + x * ui_peruser.btn_spc,
ui_peruser.page_y + y * ui_peruser.btn_spc,
ui_peruser.btn_size, ui_peruser.btn_size,
name, button_name
)
formspec[n + 1] = ("tooltip[%s;%s \\[%s\\]]"):format(
button_name, minetest.formspec_escape(item.description),
item.mod_origin or "??"
)
n = n + 2
list_index = list_index + 1
end
formspec[n + 1] = ("tooltip[%s;%s]"):format(
button_name, minetest.formspec_escape(tooltip)
)
n = n + 2
list_index = list_index + 1
end
end
formspec[n] = string.format("label[%f,%f;%s: %s]",
ui_peruser.page_buttons_x + ui_peruser.btn_spc * (draw_lite_mode and 1 or 2),
ui_peruser.page_buttons_y + 0.1 + ui_peruser.btn_spc * 2,
F(S("Page")), S("@1 of @2",page2,pagemax))
end
formspec[n] = string.format("label[%f,%f;%s: %s]",
ui_peruser.page_buttons_x + ui_peruser.btn_spc * (ui_peruser.is_lite_mode and 1 or 2),
ui_peruser.page_buttons_y + 0.1 + ui_peruser.btn_spc * 2,
F(S("Page")), S("@1 of @2",page2,pagemax))
end
n= n+1
function ui.get_formspec(player, page)
if not player then
return ""
if ui.activefilter[player_name] ~= "" then
formspec[n] = string.format("label[%f,%f;%s: %s]",
ui_peruser.page_x, ui_peruser.page_y - 0.25,
F(S("Filter")), F(ui.activefilter[player_name]))
end
local player_name = player:get_player_name()
local ui_peruser = ui.get_per_player_formspec(player_name)
ui.current_page[player_name] = page
local pagedef = ui.pages[page]
if not pagedef then
return "" -- Invalid page name
end
local fs = {
"formspec_version[4]",
"size["..ui_peruser.formw..","..ui_peruser.formh.."]",
pagedef.formspec_prepend and "" or "no_prepend[]",
ui.standard_background
}
local perplayer_formspec = ui.get_per_player_formspec(player_name)
local fsdata = pagedef.get_formspec(player, perplayer_formspec)
fs[#fs + 1] = fsdata.formspec
formspec_tab_buttons(player, fs, ui_peruser)
if fsdata.draw_inventory ~= false then
-- Player inventory
fs[#fs + 1] = "listcolors[#00000000;#00000000]"
fs[#fs + 1] = ui_peruser.standard_inv
end
if fsdata.draw_item_list == false then
return table.concat(fs, "")
end
formspec_add_categories(player, fs, ui_peruser)
formspec_add_search_box(player, fs, ui_peruser)
formspec_add_item_browser(player, fs, ui_peruser)
return table.concat(fs)
return table.concat(formspec, "")
end
function ui.set_inventory_formspec(player, page)
@ -333,7 +288,7 @@ function ui.set_inventory_formspec(player, page)
end
end
function ui.is_itemdef_listable(def)
local function valid_def(def)
return (not def.groups.not_in_creative_inventory
or def.groups.not_in_creative_inventory == 0)
and def.description
@ -346,11 +301,9 @@ function ui.apply_filter(player, filter, search_dir)
return false
end
local player_name = player:get_player_name()
local lfilter = string.lower(filter)
local ffilter
if lfilter:sub(1, 6) == "group:" then
-- Group filter: all groups of the item must match
local groups = lfilter:sub(7):split(",")
ffilter = function(name, def)
for _, group in ipairs(groups) do
@ -362,10 +315,7 @@ function ui.apply_filter(player, filter, search_dir)
return true
end
else
-- Name filter: fuzzy match item names and descriptions
local player_info = minetest.get_player_information(player_name)
local lang = player_info and player_info.lang_code or ""
local lang = minetest.get_player_information(player_name).lang_code
ffilter = function(name, def)
local lname = string.lower(name)
local ldesc = string.lower(def.description)
@ -375,53 +325,95 @@ function ui.apply_filter(player, filter, search_dir)
or llocaldesc and string.find(llocaldesc, lfilter, 1, true)
end
end
local is_itemdef_listable = ui.is_itemdef_listable
local filtered_items = {}
ui.filtered_items_list[player_name]={}
local category = ui.current_category[player_name] or 'all'
if category == 'all' then
for name, def in pairs(minetest.registered_items) do
if is_itemdef_listable(def)
if valid_def(def)
and ffilter(name, def) then
table.insert(filtered_items, name)
table.insert(ui.filtered_items_list[player_name], name)
end
end
elseif category == 'uncategorized' then
for name, def in pairs(minetest.registered_items) do
if is_itemdef_listable(def)
and not ui.find_category(name)
if (not ui.find_category(name))
and valid_def(def)
and ffilter(name, def) then
table.insert(filtered_items, name)
table.insert(ui.filtered_items_list[player_name], name)
end
end
else
-- Any other category is selected
for name, exists in pairs(ui.registered_category_items[category]) do
for name,exists in pairs(ui.registered_category_items[category]) do
local def = minetest.registered_items[name]
if exists and def
and is_itemdef_listable(def)
and valid_def(def)
and ffilter(name, def) then
table.insert(filtered_items, name)
table.insert(ui.filtered_items_list[player_name], name)
end
end
end
table.sort(filtered_items)
ui.filtered_items_list_size[player_name] = #filtered_items
ui.filtered_items_list[player_name] = filtered_items
table.sort(ui.filtered_items_list[player_name])
ui.filtered_items_list_size[player_name] = #ui.filtered_items_list[player_name]
ui.current_index[player_name] = 1
ui.activefilter[player_name] = filter
ui.active_search_direction[player_name] = search_dir
ui.set_inventory_formspec(player, ui.current_page[player_name])
ui.set_inventory_formspec(player,
ui.current_page[player_name])
end
-- Inform players about potential visual issues
minetest.register_on_joinplayer(function(player)
local player_name = player:get_player_name()
local info = minetest.get_player_information(player_name)
if info and (info.formspec_version or 0) < 4 then
minetest.chat_send_player(player_name, S("Unified Inventory: Your game version is too old"
.. " and does not support the GUI requirements. You might experience visual issues."))
function ui.items_in_group(groups)
local items = {}
for name, item in pairs(minetest.registered_items) do
for _, group in pairs(groups:split(',')) do
if item.groups[group] then
table.insert(items, name)
end
end
end
end)
return items
end
function ui.sort_inventory(inv)
local inlist = inv:get_list("main")
local typecnt = {}
local typekeys = {}
for _, st in ipairs(inlist) do
if not st:is_empty() then
local n = st:get_name()
local w = st:get_wear()
local m = st:get_metadata()
local k = string.format("%s %05d %s", n, w, m)
if not typecnt[k] then
typecnt[k] = {
name = n,
wear = w,
metadata = m,
stack_max = st:get_stack_max(),
count = 0,
}
table.insert(typekeys, k)
end
typecnt[k].count = typecnt[k].count + st:get_count()
end
end
table.sort(typekeys)
local outlist = {}
for _, k in ipairs(typekeys) do
local tc = typecnt[k]
while tc.count > 0 do
local c = math.min(tc.count, tc.stack_max)
table.insert(outlist, ItemStack({
name = tc.name,
wear = tc.wear,
metadata = tc.metadata,
count = c,
}))
tc.count = tc.count - c
end
end
if #outlist > #inlist then return end
while #outlist < #inlist do
table.insert(outlist, ItemStack(nil))
end
inv:set_list("main", outlist)
end

View File

@ -1,55 +0,0 @@
-- Inefficient pattern matching
local warned_funcs = {}
local function LOG_ONCE(funcname)
if warned_funcs[funcname] then return end
warned_funcs[funcname] = true
minetest.log("error", "Call to undocumented, deprecated API '" .. funcname .. "'."
.. " In a future version of Unified Inventory this will result in a real error.")
end
function unified_inventory.canonical_item_spec_matcher(spec)
LOG_ONCE("canonical_item_spec_matcher")
local specname = ItemStack(spec):get_name()
if specname:sub(1, 6) ~= "group:" then
return function (itemname)
return itemname == specname
end
end
local group_names = specname:sub(7):split(",")
return function (itemname)
local itemdef = minetest.registered_items[itemname]
for _, group_name in ipairs(group_names) do
if (itemdef.groups[group_name] or 0) == 0 then
return false
end
end
return true
end
end
function unified_inventory.item_matches_spec(item, spec)
LOG_ONCE("item_matches_spec")
local itemname = ItemStack(item):get_name()
return unified_inventory.canonical_item_spec_matcher(spec)(itemname)
end
unified_inventory.registered_group_items = {
mesecon_conductor_craftable = "mesecons:wire_00000000_off",
stone = "default:cobble",
wood = "default:wood",
book = "default:book",
sand = "default:sand",
leaves = "default:leaves",
tree = "default:tree",
vessel = "vessels:glass_bottle",
wool = "wool:white",
}
function unified_inventory.register_group_item(groupname, itemname)
LOG_ONCE("register_group_item")
unified_inventory.registered_group_items[groupname] = itemname
end

View File

@ -7,17 +7,17 @@ Bag @1=Tasche @1
Small Bag=Kleine Tasche
Medium Bag=Mittelgroße Tasche
Large Bag=Große Tasche
All Items=Alle Gegenstände
Misc. Items=Sonstige Gegenstände
Plant Life=Pfanzenwelt
Building Materials=Baumaterialien
Tools=Werkzeuge
Minerals and Metals=Minerale und Metalle
Environment and Worldgen=Umwelt und Welterstellung
Lighting=Beleuchtung
All Items=
Misc. Items=
Plant Life=
Building Materials=
Tools=
Minerals and Metals=
Environment and Worldgen=
Lighting=
and = und
Scroll categories left=Kategorien nach links blättern
Scroll categories right=Kategorien nach rechts blättern
Scroll categories left=
Scroll categories right=
Search=Suchen
Reset search and display everything=Suche zurücksetzen und alles anzeigen
First page=Erste Seite
@ -76,10 +76,10 @@ Waypoints=Wegpunkte
Select Waypoint #@1=Wegpunkt Nr. @1 auswählen
Waypoint @1=Wegpunkt Nr. @1
Set waypoint to current location=Setze Wegpunkt zur derzeitigen Position
Hide waypoint=Wegpunkt verstecken
Show waypoint=Wegpunkt zeigen
Hide coordinates=Koordinaten verstecken
Show coordinates=Koordinaten zeigen
Hide waypoint=
Show waypoint=
Hide coordinates=
Show coordinates=
Change color of waypoint display=Farbe der Darstellung der Wegpunkte ändern
Edit waypoint name=Name des Wegpunkts ändern
Waypoint active=Wegpunkt aktiv

View File

@ -7,17 +7,17 @@ Bag @1=背包@1
Small Bag=小背包
Medium Bag=中背包
Large Bag=大背包
All Items=所有物品
Misc. Items=杂项
Plant Life=植物
Building Materials=建材
Tools=工具
Minerals and Metals=矿物与金属
Environment and Worldgen=自然环境
Lighting=光源
All Items=
Misc. Items=
Plant Life=
Building Materials=
Tools=
Minerals and Metals=
Environment and Worldgen=
Lighting=
and = 和
Scroll categories left=向左滚动分类栏
Scroll categories right=向右滚动分类栏
Scroll categories left=
Scroll categories right=
Search=搜索
Reset search and display everything=重置搜索并显示所有物品
First page=第一页
@ -32,7 +32,7 @@ Page=页面
@1 of @2=第@1页共@2页
Filter=过滤器
Can use the creative inventory=可以使用创造背包
Forces Unified Inventory to be displayed in Full mode if Lite mode is configured globally=如果轻量模式被全局配置强迫Unified Inventory以完全模式展现。
Forces Unified Inventory to be displayed in Full mode if Lite mode is configured globally=
Crafting Grid=合成表
Crafting Guide=合成指南
Set home position=设置家的位置
@ -45,7 +45,7 @@ You don't have the settime privilege!=你没有“settime”权限
Set time to night=设置时间到晚上
Time of day set to 9pm=时间设置到晚上9点
Clear inventory=清空背包
This button has been disabled outside of creative mode to prevent accidental inventory trashing.@nUse the trash slot instead.=此按钮已在非创造模式中禁用以防止意外的背包清空。@n请使用垃圾桶栏。
This button has been disabled outside of creative mode to prevent accidental inventory trashing.@nUse the trash slot instead.=
Inventory cleared!=清空背包
Trash:=丢弃:
Refill:=填满:
@ -57,13 +57,13 @@ No recipes=没有配方
No usages=没有用法
Result=结果
Ingredient=原料
Show next recipe=显示下一个配方
Show next usage=显示下一个用法
Show previous recipe=显示前一个配方
Show previous usage=显示前一个用法
@1 (@2)=@1 (@2)
Show next recipe=
Show next usage=
Show previous recipe=
Show previous usage=
@1 (@2)=
Give me:=给予:
This recipe is too@@large to be displayed.=该配方太@@大,不能显示。
This recipe is too@@large to be displayed.=
To craft grid:=填充物品到合成表
All=全部
Crafting=合成
@ -76,10 +76,10 @@ Waypoints=航路点
Select Waypoint #@1=查询航路点 #@1
Waypoint @1=航路点 @1
Set waypoint to current location=将航路点设置到当前位置
Hide waypoint=隐藏航路点
Show waypoint=显示航路点
Hide coordinates=隐藏坐标
Show coordinates=显示坐标
Hide waypoint=
Show waypoint=
Hide coordinates=
Show coordinates=
Change color of waypoint display=改变航路点显示的颜色
Edit waypoint name=编辑航路点名称
Waypoint active=航路点已激活

View File

@ -7,17 +7,17 @@ Bag @1=揹包@1
Small Bag=小揹包
Medium Bag=中揹包
Large Bag=大揹包
All Items=所有物品
Misc. Items=雜項
Plant Life=植物
Building Materials=建材
Tools=工具
Minerals and Metals=礦物與金屬
Environment and Worldgen=自然環境
Lighting=光源
All Items=
Misc. Items=
Plant Life=
Building Materials=
Tools=
Minerals and Metals=
Environment and Worldgen=
Lighting=
and = 和
Scroll categories left=向左滾動分類欄
Scroll categories right=向右滾動分類欄
Scroll categories left=
Scroll categories right=
Search=搜索
Reset search and display everything=重置搜索並顯示所有物品
First page=第一頁
@ -32,7 +32,7 @@ Page=頁面
@1 of @2=第@1頁共@2頁
Filter=過濾器
Can use the creative inventory=可以使用創造揹包
Forces Unified Inventory to be displayed in Full mode if Lite mode is configured globally=如果輕量模式被全局配置強迫Unified Inventory以完全模式展現。
Forces Unified Inventory to be displayed in Full mode if Lite mode is configured globally=
Crafting Grid=合成表
Crafting Guide=合成指南
Set home position=設置家的位置
@ -45,7 +45,7 @@ You don't have the settime privilege!=你沒有“settime”權限
Set time to night=設置時間到晚上
Time of day set to 9pm=時間設置到晚上9點
Clear inventory=清空揹包
This button has been disabled outside of creative mode to prevent accidental inventory trashing.@nUse the trash slot instead.=此按鈕已在非創造模式中禁用以防止意外的背包清空。@n請使用垃圾桶欄。
This button has been disabled outside of creative mode to prevent accidental inventory trashing.@nUse the trash slot instead.=
Inventory cleared!=清空揹包
Trash:=丟棄:
Refill:=填滿:
@ -57,13 +57,13 @@ No recipes=沒有配方
No usages=沒有用法
Result=結果
Ingredient=原料
Show next recipe=顯示下一個配方
Show next usage=顯示下一個用法
Show previous recipe=顯示上一個配方
Show previous usage=顯示上一個用法
@1 (@2)=@1 (@2)
Show next recipe=
Show next usage=
Show previous recipe=
Show previous usage=
@1 (@2)=
Give me:=給予:
This recipe is too@@large to be displayed.=該配方太@@大,不能顯示。
This recipe is too@@large to be displayed.=
To craft grid:=填充物品到合成表
All=全部
Crafting=合成
@ -76,10 +76,10 @@ Waypoints=航路點
Select Waypoint #@1=查詢航路點 #@1
Waypoint @1=航路點 @1
Set waypoint to current location=將航路點設置到當前位置
Hide waypoint=隱藏航路點
Show waypoint=顯示航路點
Hide coordinates=隱藏坐標
Show coordinates=顯示坐標
Hide waypoint=
Show waypoint=
Hide coordinates=
Show coordinates=
Change color of waypoint display=改變航路點顯示的顏色
Edit waypoint name=編輯航路點名稱
Waypoint active=航路點已激活

View File

@ -126,18 +126,25 @@ Example output:
}
--]]
function unified_inventory.find_usable_items(inv_items, craft_items)
local get_group = minetest.get_item_group
local result = {}
for craft_item in pairs(craft_items) do
-- may specify group:type1,type2
local items = unified_inventory.get_matching_items(craft_item)
local group = craft_item:match("^group:(.+)")
local found = {}
for itemname, _ in pairs(items) do
if inv_items[itemname] then
found[itemname] = true
if group ~= nil then
for inv_item in pairs(inv_items) do
if get_group(inv_item, group) > 0 then
found[inv_item] = true
end
end
else
if inv_items[craft_item] ~= nil then
found[craft_item] = true
end
end
result[craft_item] = found
end

View File

@ -1,6 +1,6 @@
name = unified_inventory
optional_depends = default, creative, sfinv, datastorage
optional_depends = default, creative, sfinv, datastorage, farming
description = """
Unified Inventory replaces the default survival and creative inventory.
It adds a nicer interface and a number of features, such as a crafting guide.

View File

@ -41,55 +41,6 @@ ui.register_button("craftguide", {
tooltip = S("Crafting Guide")
})
ui.register_button("home_gui_set", {
type = "image",
image = "ui_sethome_icon.png",
tooltip = S("Set home position"),
hide_lite=true,
action = function(player)
local player_name = player:get_player_name()
if minetest.check_player_privs(player_name, {home=true}) then
ui.set_home(player, player:get_pos())
local home = ui.home_pos[player_name]
if home ~= nil then
minetest.sound_play("dingdong",
{to_player=player_name, gain = 1.0})
minetest.chat_send_player(player_name,
S("Home position set to: @1", minetest.pos_to_string(home)))
end
else
minetest.chat_send_player(player_name,
S("You don't have the \"home\" privilege!"))
ui.set_inventory_formspec(player, ui.current_page[player_name])
end
end,
condition = function(player)
return minetest.check_player_privs(player:get_player_name(), {home=true})
end,
})
ui.register_button("home_gui_go", {
type = "image",
image = "ui_gohome_icon.png",
tooltip = S("Go home"),
hide_lite=true,
action = function(player)
local player_name = player:get_player_name()
if minetest.check_player_privs(player_name, {home=true}) then
if ui.go_home(player) then
minetest.sound_play("teleport", {to_player = player_name})
end
else
minetest.chat_send_player(player_name,
S("You don't have the \"home\" privilege!"))
ui.set_inventory_formspec(player, ui.current_page[player_name])
end
end,
condition = function(player)
return minetest.check_player_privs(player:get_player_name(), {home=true})
end,
})
ui.register_button("misc_set_day", {
type = "image",
image = "ui_sun_icon.png",
@ -207,9 +158,9 @@ ui.register_page("craft", {
local function stack_image_button(x, y, w, h, buttonname_prefix, item)
local name = item:get_name()
local description = item:get_meta():get_string("description")
local count = item:get_count()
local show_is_group = false
local displayitem = item:to_string()
local displayitem = name.." "..count
local selectitem = name
if name:sub(1, 6) == "group:" then
local group_name = name:sub(7)
@ -219,9 +170,7 @@ local function stack_image_button(x, y, w, h, buttonname_prefix, item)
selectitem = group_item.sole and displayitem or name
end
local label = show_is_group and "G" or ""
-- Unique id to prevent tooltip being overridden
local id = string.format("%i%i_", x*10, y*10)
local buttonname = F(id..buttonname_prefix..ui.mangle_for_formspec(selectitem))
local buttonname = F(buttonname_prefix..ui.mangle_for_formspec(selectitem))
local button = string.format("item_image_button[%f,%f;%f,%f;%s;%s;%s]",
x, y, w, h,
F(displayitem), buttonname, label)
@ -237,15 +186,10 @@ local function stack_image_button(x, y, w, h, buttonname_prefix, item)
if andcount >= 1 then
button = button .. string.format("tooltip[%s;%s]", buttonname, grouptip)
end
elseif description ~= "" then
button = button .. string.format("tooltip[%s;%s]", buttonname, F(description))
end
return button
end
-- The recipe text contains parameters, hence they can yet not be translated.
-- Instead, use a dummy translation call so that it can be picked up by the
-- static parsing of the translation string update script
local recipe_text = {
recipe = NS("Recipe @1 of @2"),
usage = NS("Usage @1 of @2"),
@ -500,14 +444,6 @@ local function craftguide_craft(player, formname, fields)
local alternate = ui.alternate[player_name]
local craft = crafts[alternate]
if not craft.width then
if not craft.output then
minetest.log("warning", "[unified_inventory] Craft has no output.")
else
minetest.log("warning", ("[unified_inventory] Craft for '%s' has no width."):format(craft.output))
end
return
end
if craft.width > 3 then return end
ui.craftguide_match_craft(player, "main", "craft", craft, amount)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 KiB

After

Width:  |  Height:  |  Size: 134 KiB

View File

@ -10,8 +10,5 @@ unified_inventory_bags (Enable bags) bool true
#and the give privilege.
unified_inventory_trash (Enable trash) bool true
#If enabled, disabled buttons will be hidden instead of grayed out.
unified_inventory_hide_disabled_buttons (Hide disabled buttons) bool false
unified_inventory_automatic_categorization (Items automatically added to categories) bool true
unified_inventory_automatic_categorization (Items automatically added to categories) bool true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 510 B

After

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 551 B

After

Width:  |  Height:  |  Size: 139 B

View File

@ -330,9 +330,8 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end
end)
-- waypoints_temp must be initialized before the general unified_inventory
-- joinplayer callback is run for updating the inventory
table.insert(minetest.registered_on_joinplayers, 1, function(player)
minetest.register_on_joinplayer(function(player)
local player_name = player:get_player_name()
local waypoints = get_waypoint_data(player)