Add files via upload

This commit is contained in:
thunderdog1138 2019-12-15 16:14:23 -05:00 committed by GitHub
parent 959098dff8
commit d92a9a567e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 2560 additions and 0 deletions

2
mods/unified_inventory/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*~

View File

@ -0,0 +1,19 @@
Unified Inventory for Minetest
Copyright (C) 2012-2014 Maciej Kasatkin (RealBadAngel)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Contact information:
Examine a git patch to get the contributor's email address.

View File

@ -0,0 +1,96 @@
# Unified Inventory
Unified Inventory replaces the default survival and creative inventory.
## Features
* Node, item and tool browser
* Crafting guide
* Can copy the recipe to the crafting grid
* Recipe search function by ingredients
* Up to four bags with up to 24 slots each
* Home function to teleport
* Trash slot
* Lite mode: reduces the item browser width
* Mod API for modders: see [mod_api.txt](doc/mod_api.txt)
* Setting-determinated features: see [settingtypes.txt](settingtypes.txt)
## Requirements
* Minetest 5.0.0+ since commit 4403b69
* Minetest 0.4.16+ prior commit 4403b69
# Licenses
Copyright (C) 2012-2014 Maciej Kasatkin (RealBadAngel)
Copyright (C) 2012-? Various minetest-mods contributors
## Code
GNU LGPLv2+, see [license notice](LICENSE.txt)
## Textures
VanessaE: (CC-BY-4.0)
* `ui_group.png`
Tango Project: (Public Domain, CC-BY-4.0)
* [`ui_reset_icon.png`](https://commons.wikimedia.org/wiki/File:Edit-clear.svg)
* [`ui_doubleleft_icon.png`](http://commons.wikimedia.org/wiki/File:Media-seek-backward.svg)
* [`ui_doubleright_icon.png`](http://commons.wikimedia.org/wiki/File:Media-seek-forward.svg)
* [`ui_left_icon.png` / `ui_right_icon.png`](http://commons.wikimedia.org/wiki/File:Media-playback-start.svg)
* [`ui_skip_backward_icon.png`](http://commons.wikimedia.org/wiki/File:Media-skip-backward.svg)
* [`ui_skip_forward_icon.png`](http://commons.wikimedia.org/wiki/File:Media-skip-forward.svg)
From http://www.clker.com (Public Domain, CC-BY-4.0):
* [`bags_small.png`](http://www.clker.com/clipart-moneybag-empty.html)
* [`bags_medium.png`](http://www.clker.com/clipart-backpack-1.html)
* [`bags_large.png` / `ui_bags_icon.png`](http://www.clker.com/clipart-backpack-green-brown.html)
* `ui_trash_icon.png`: <http://www.clker.com/clipart-29090.html> and <http://www.clker.com/clipart-trash.html>
* [`ui_search_icon.png`](http://www.clker.com/clipart-24887.html)
* [`ui_off_icon.png` / `ui_on_icon.png`](http://www.clker.com/clipart-on-off-switches.html)
* [`ui_waypoints_icon.png`](http://www.clker.com/clipart-map-pin-red.html)
* [`ui_circular_arrows_icon.png`](http://www.clker.com/clipart-circular-arrow-pattern.html)
* [`ui_pencil_icon.pnc`](http://www.clker.com/clipart-2256.html)
* [`ui_waypoint_set_icon.png`](http://www.clker.com/clipart-larger-flag.html)
Everaldo Coelho (YellowIcon) (LGPL v2.1+):
* [`ui_craftguide_icon.png` / `ui_craft_icon.png`](http://commons.wikimedia.org/wiki/File:Advancedsettings.png)
Gregory H. Revera: (CC-BY-SA 3.0)
* [`ui_moon_icon.png`](http://commons.wikimedia.org/wiki/File:FullMoon2010.jpg)
Thomas Bresson: (CC-BY 3.0)
* [`ui_sun_icon.png`](http://commons.wikimedia.org/wiki/File:2012-10-13_15-29-35-sun.jpg)
Fibonacci: (Public domain, CC-BY 4.0)
* [`ui_xyz_off_icon.png`](http://commons.wikimedia.org/wiki/File:No_sign.svg)
Gregory Maxwell: (Public domain, CC-BY 4.0)
* [`ui_ok_icon.png`](http://commons.wikimedia.org/wiki/File:Yes_check.svg)
Adrien Facélina: (LGPL v2.1+)
* [`inventory_plus_worldedit_gui.png`](http://commons.wikimedia.org/wiki/File:Erioll_world_2.svg)
Other files from Wikimedia Commons:
* [`ui_gohome_icon.png` / `ui_home_icon.png` / `ui_sethome_icon.png`](http://commons.wikimedia.org/wiki/File:Home_256x256.png) (GPL v2+)
RealBadAngel: (CC-BY-4.0)
* Everything else.

View File

@ -0,0 +1,307 @@
local S = minetest.get_translator("unified_inventory")
local F = minetest.formspec_escape
-- Create detached creative inventory after loading all mods
minetest.after(0.01, function()
local rev_aliases = {}
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
unified_inventory.items_list = {}
for name, def in pairs(minetest.registered_items) do
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(unified_inventory.items_list, name)
local all_names = rev_aliases[name] or {}
table.insert(all_names, name)
for _, name in ipairs(all_names) do
local recipes = minetest.get_all_craft_recipes(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 unified_inventory.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
unified_inventory.register_craft(recipe)
end
end
end
end
end
end
table.sort(unified_inventory.items_list)
unified_inventory.items_list_size = #unified_inventory.items_list
print("Unified Inventory. inventory size: "..unified_inventory.items_list_size)
for _, name in ipairs(unified_inventory.items_list) do
local def = minetest.registered_items[name]
-- Simple drops
if type(def.drop) == "string" then
local dstack = ItemStack(def.drop)
if not dstack:is_empty() and dstack:get_name() ~= name then
unified_inventory.register_craft({
type = "digging",
items = {name},
output = def.drop,
width = 0,
})
end
-- Complex drops. Yes, it's really complex!
elseif type(def.drop) == "table" then
--[[ Extract single items from the table and save them into dedicated tables
to register them later, in order to avoid duplicates. These tables counts
the total number of guaranteed drops and drops by chance (maybes) for each item.
For maybes, the final count is the theoretical maximum number of items, not
neccessarily the actual drop count. ]]
local drop_guaranteed = {}
local drop_maybe = {}
-- This is for catching an obscure corner case: If the top items table has
-- only items with rarity = 1, but max_items is set, then only the first
-- max_items will be part of the drop, any later entries are logically
-- impossible, so this variable is for keeping track of this
local max_items_left = def.drop.max_items
-- For checking whether we still encountered only guaranteed only so far;
-- for the first “maybe” item it will become false which will cause ALL
-- later items to be considered “maybes”.
-- A common idiom is:
-- { max_items 1, { items = {
-- { items={"example:1"}, rarity = 5 },
-- { items={"example:2"}, rarity = 1 }, }}}
-- example:2 must be considered a “maybe” because max_items is set and it
-- appears after a “maybe”
local max_start = true
-- Let's iterate through the items madness!
-- Handle invalid drop entries gracefully.
local drop_items = def.drop.items or { }
for i=1,#drop_items do
if max_items_left ~= nil and max_items_left <= 0 then break end
local itit = drop_items[i]
for j=1,#itit.items do
local dstack = ItemStack(itit.items[j])
if not dstack:is_empty() and dstack:get_name() ~= name then
local dname = dstack:get_name()
local dcount = dstack:get_count()
-- Guaranteed drops AND we are not yet in “maybe mode”
if #itit.items == 1 and itit.rarity == 1 and max_start then
if drop_guaranteed[dname] == nil then
drop_guaranteed[dname] = 0
end
drop_guaranteed[dname] = drop_guaranteed[dname] + dcount
if max_items_left ~= nil then
max_items_left = max_items_left - 1
if max_items_left <= 0 then break end
end
-- Drop was a “maybe”
else
if max_items_left ~= nil then max_start = false end
if drop_maybe[dname] == nil then
drop_maybe[dname] = 0
end
drop_maybe[dname] = drop_maybe[dname] + dcount
end
end
end
end
for itemstring, count in pairs(drop_guaranteed) do
unified_inventory.register_craft({
type = "digging",
items = {name},
output = itemstring .. " " .. count,
width = 0,
})
end
for itemstring, count in pairs(drop_maybe) do
unified_inventory.register_craft({
type = "digging_chance",
items = {name},
output = itemstring .. " " .. count,
width = 0,
})
end
end
end
for _, recipes in pairs(unified_inventory.crafts_for.recipe) do
for _, recipe in ipairs(recipes) do
local ingredient_items = {}
for _, spec in pairs(recipe.items) do
local matches_spec = unified_inventory.canonical_item_spec_matcher(spec)
for _, name in ipairs(unified_inventory.items_list) do
if matches_spec(name) then
ingredient_items[name] = true
end
end
end
for name, _ in pairs(ingredient_items) do
if unified_inventory.crafts_for.usage[name] == nil then
unified_inventory.crafts_for.usage[name] = {}
end
table.insert(unified_inventory.crafts_for.usage[name], recipe)
end
end
end
end)
-- load_home
local function load_home()
local input = io.open(unified_inventory.home_filename, "r")
if not input then
unified_inventory.home_pos = {}
return
end
while true do
local x = input:read("*n")
if not x then break end
local y = input:read("*n")
local z = input:read("*n")
local name = input:read("*l")
unified_inventory.home_pos[name:sub(2)] = {x = x, y = y, z = z}
end
io.close(input)
end
load_home()
function unified_inventory.set_home(player, pos)
local player_name = player:get_player_name()
unified_inventory.home_pos[player_name] = vector.round(pos)
-- save the home data from the table to the file
local output = io.open(unified_inventory.home_filename, "w")
for k, v in pairs(unified_inventory.home_pos) do
output:write(v.x.." "..v.y.." "..v.z.." "..k.."\n")
end
io.close(output)
end
function unified_inventory.go_home(player)
local pos = unified_inventory.home_pos[player:get_player_name()]
if pos then
player:set_pos(pos)
end
end
-- register_craft
function unified_inventory.register_craft(options)
if not options.output then
return
end
local itemstack = ItemStack(options.output)
if itemstack:is_empty() then
return
end
if options.type == "normal" and options.width == 0 then
options = { type = "shapeless", items = options.items, output = options.output, width = 0 }
end
if not unified_inventory.crafts_for.recipe[itemstack:get_name()] then
unified_inventory.crafts_for.recipe[itemstack:get_name()] = {}
end
table.insert(unified_inventory.crafts_for.recipe[itemstack:get_name()],options)
end
local craft_type_defaults = {
width = 3,
height = 3,
uses_crafting_grid = false,
}
function unified_inventory.craft_type_defaults(name, options)
if not options.description then
options.description = name
end
setmetatable(options, {__index = craft_type_defaults})
return options
end
function unified_inventory.register_craft_type(name, options)
unified_inventory.registered_craft_types[name] =
unified_inventory.craft_type_defaults(name, options)
end
unified_inventory.register_craft_type("normal", {
description = F(S("Crafting")),
icon = "ui_craftgrid_icon.png",
width = 3,
height = 3,
get_shaped_craft_width = function (craft) return craft.width end,
dynamic_display_size = function (craft)
local w = craft.width
local h = math.ceil(table.maxn(craft.items) / craft.width)
local g = w < h and h or w
return { width = g, height = g }
end,
uses_crafting_grid = true,
})
unified_inventory.register_craft_type("shapeless", {
description = F(S("Mixing")),
icon = "ui_craftgrid_icon.png",
width = 3,
height = 3,
dynamic_display_size = function (craft)
local maxn = table.maxn(craft.items)
local g = 1
while g*g < maxn do g = g + 1 end
return { width = g, height = g }
end,
uses_crafting_grid = true,
})
unified_inventory.register_craft_type("cooking", {
description = F(S("Cooking")),
icon = "default_furnace_front.png",
width = 1,
height = 1,
})
unified_inventory.register_craft_type("digging", {
description = F(S("Digging")),
icon = "default_tool_steelpick.png",
width = 1,
height = 1,
})
unified_inventory.register_craft_type("digging_chance", {
description = "Digging (by chance)",
icon = "default_tool_steelpick.png^[transformFY.png",
width = 1,
height = 1,
})
function unified_inventory.register_page(name, def)
unified_inventory.pages[name] = def
end
function unified_inventory.register_button(name, def)
if not def.action then
def.action = function(player)
unified_inventory.set_inventory_formspec(player, name)
end
end
def.name = name
table.insert(unified_inventory.buttons, def)
end
function unified_inventory.is_creative(playername)
return minetest.check_player_privs(playername, {creative=true})
or minetest.settings:get_bool("creative_mode")
end

View File

@ -0,0 +1,276 @@
--[[
Bags for Minetest
Copyright (c) 2012 cornernote, Brett O'Donnell <cornernote@gmail.com>
License: GPLv3
--]]
local S = minetest.get_translator("unified_inventory")
local F = minetest.formspec_escape
unified_inventory.register_page("bags", {
get_formspec = function(player)
local player_name = player:get_player_name()
return { formspec = table.concat({
"background[0.06,0.99;7.92,7.52;ui_bags_main_form.png]",
"label[0,0;" .. F(S("Bags")) .. "]",
"button[0,2;2,0.5;bag1;" .. F(S("Bag @1", 1)) .. "]",
"button[2,2;2,0.5;bag2;" .. F(S("Bag @1", 2)) .. "]",
"button[4,2;2,0.5;bag3;" .. F(S("Bag @1", 3)) .. "]",
"button[6,2;2,0.5;bag4;" .. F(S("Bag @1", 4)) .. "]",
"listcolors[#00000000;#00000000]",
"list[detached:" .. F(player_name) .. "_bags;bag1;0.5,1;1,1;]",
"list[detached:" .. F(player_name) .. "_bags;bag2;2.5,1;1,1;]",
"list[detached:" .. F(player_name) .. "_bags;bag3;4.5,1;1,1;]",
"list[detached:" .. F(player_name) .. "_bags;bag4;6.5,1;1,1;]"
}) }
end,
})
unified_inventory.register_button("bags", {
type = "image",
image = "ui_bags_icon.png",
tooltip = S("Bags"),
hide_lite=true
})
local function get_player_bag_stack(player, i)
return minetest.get_inventory({
type = "detached",
name = player:get_player_name() .. "_bags"
}):get_stack("bag" .. i, 1)
end
for bag_i = 1, 4 do
unified_inventory.register_page("bag" .. bag_i, {
get_formspec = function(player)
local stack = get_player_bag_stack(player, bag_i)
local image = stack:get_definition().inventory_image
local fs = {
"image[7,0;1,1;" .. image .. "]",
"label[0,0;" .. F(S("Bag @1", bag_i)) .. "]",
"listcolors[#00000000;#00000000]",
"list[current_player;bag" .. bag_i .. "contents;0,1;8,3;]",
"listring[current_name;bag" .. bag_i .. "contents]",
"listring[current_player;main]"
}
local slots = stack:get_definition().groups.bagslots
if slots == 8 then
fs[#fs + 1] = "background[0.06,0.99;7.92,7.52;ui_bags_sm_form.png]"
elseif slots == 16 then
fs[#fs + 1] = "background[0.06,0.99;7.92,7.52;ui_bags_med_form.png]"
elseif slots == 24 then
fs[#fs + 1] = "background[0.06,0.99;7.92,7.52;ui_bags_lg_form.png]"
end
local player_name = player:get_player_name() -- For if statement.
if unified_inventory.trash_enabled
or unified_inventory.is_creative(player_name)
or minetest.get_player_privs(player_name).give then
fs[#fs + 1] = "background[6.06,0;0.92,0.92;ui_bags_trash.png]"
.. "list[detached:trash;main;6,0.1;1,1;]"
end
local inv = player:get_inventory()
for i = 1, 4 do
local def = get_player_bag_stack(player, i):get_definition()
if def.groups.bagslots then
local list_name = "bag" .. i .. "contents"
local size = inv:get_size(list_name)
local used = 0
for si = 1, size do
local stk = inv:get_stack(list_name, si)
if not stk:is_empty() then
used = used + 1
end
end
local img = def.inventory_image
local label = F(S("Bag @1", i)) .. "\n" .. used .. "/" .. size
fs[#fs + 1] = string.format("image_button[%i,0;1,1;%s;bag%i;%s]",
i + 1, img, i, label)
end
end
return { formspec = table.concat(fs) }
end,
})
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "" then
return
end
for i = 1, 4 do
if fields["bag" .. i] then
local stack = get_player_bag_stack(player, i)
if not stack:get_definition().groups.bagslots then
return
end
unified_inventory.set_inventory_formspec(player, "bag" .. i)
return
end
end
end)
local function save_bags_metadata(player, bags_inv)
local is_empty = true
local bags = {}
for i = 1, 4 do
local bag = "bag" .. i
if not bags_inv:is_empty(bag) then
-- Stack limit is 1, otherwise use stack:to_string()
bags[i] = bags_inv:get_stack(bag, 1):get_name()
is_empty = false
end
end
local meta = player:get_meta()
if is_empty then
meta:set_string("unified_inventory:bags", nil)
else
meta:set_string("unified_inventory:bags",
minetest.serialize(bags))
end
end
local function load_bags_metadata(player, bags_inv)
local player_inv = player:get_inventory()
local meta = player:get_meta()
local bags_meta = meta:get_string("unified_inventory:bags")
local bags = bags_meta and minetest.deserialize(bags_meta) or {}
local dirty_meta = false
if not bags_meta then
-- Backwards compatiblity
for i = 1, 4 do
local bag = "bag" .. i
if not player_inv:is_empty(bag) then
-- Stack limit is 1, otherwise use stack:to_string()
bags[i] = player_inv:get_stack(bag, 1):get_name()
dirty_meta = true
end
end
end
-- Fill detached slots
for i = 1, 4 do
local bag = "bag" .. i
bags_inv:set_size(bag, 1)
bags_inv:set_stack(bag, 1, bags[i] or "")
end
if dirty_meta then
-- Requires detached inventory to be set up
save_bags_metadata(player, bags_inv)
end
-- Clean up deprecated garbage after saving
for i = 1, 4 do
local bag = "bag" .. i
player_inv:set_size(bag, 0)
end
end
minetest.register_on_joinplayer(function(player)
local player_inv = player:get_inventory()
local player_name = player:get_player_name()
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()
end
return 0
end,
on_take = function(inv, listname, index, stack, player)
player:get_inventory():set_size(listname .. "contents", 0)
save_bags_metadata(player, inv)
end,
allow_move = function()
return 0
end,
}, player_name)
load_bags_metadata(player, bags_inv)
end)
-- register bag tools
minetest.register_tool("unified_inventory:bag_small", {
description = S("Small Bag"),
inventory_image = "bags_small.png",
groups = {bagslots=8},
})
minetest.register_tool("unified_inventory:bag_medium", {
description = S("Medium Bag"),
inventory_image = "bags_medium.png",
groups = {bagslots=16},
})
minetest.register_tool("unified_inventory:bag_large", {
description = S("Large Bag"),
inventory_image = "bags_large.png",
groups = {bagslots=24},
})
-- register bag crafts
if minetest.get_modpath("farming") ~= nil then
minetest.register_craft({
output = "unified_inventory:bag_small",
recipe = {
{"", "farming:cotton", ""},
{"group:wool", "group:wool", "group:wool"},
{"group:wool", "group:wool", "group:wool"},
},
})
minetest.register_craft({
output = "unified_inventory:bag_medium",
recipe = {
{"", "", ""},
{"farming:cotton", "unified_inventory:bag_small", "farming:cotton"},
{"farming:cotton", "unified_inventory:bag_small", "farming:cotton"},
},
})
minetest.register_craft({
output = "unified_inventory:bag_large",
recipe = {
{"", "", ""},
{"farming:cotton", "unified_inventory:bag_medium", "farming:cotton"},
{"farming:cotton", "unified_inventory:bag_medium", "farming:cotton"},
},
})
end

View File

@ -0,0 +1,216 @@
local function default_refill(stack)
stack:set_count(stack:get_stack_max())
local itemdef = minetest.registered_items[stack:get_name()]
if itemdef
and (itemdef.wear_represents or "mechanical_wear") == "mechanical_wear"
and stack:get_wear() ~= 0 then
stack:set_wear(0)
end
return stack
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
unified_inventory.filtered_items_list[player_name] =
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.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", {
allow_put = function(inv, listname, index, stack, player)
local player_name = player:get_player_name()
if unified_inventory.is_creative(player_name) then
return stack:get_count()
else
return 0
end
end,
on_put = function(inv, listname, index, stack, player)
local player_name = player:get_player_name()
local handle_refill = (minetest.registered_items[stack:get_name()] or {}).on_refill or default_refill
stack = handle_refill(stack)
inv:set_stack(listname, index, stack)
minetest.sound_play("electricity",
{to_player=player_name, gain = 1.0})
end,
}, player_name)
refill:set_size("main", 1)
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})
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)
local player_name = player:get_player_name()
local ui_peruser,draw_lite_mode = unified_inventory.get_per_player_formspec(player_name)
if formname ~= "" then
return
end
-- always take new search text, even if not searching on it yet
if fields.searchbox
and fields.searchbox ~= unified_inventory.current_searchbox[player_name] then
unified_inventory.current_searchbox[player_name] = fields.searchbox
end
for i, def in pairs(unified_inventory.buttons) do
if fields[def.name] then
def.action(player)
minetest.sound_play("click",
{to_player=player_name, gain = 0.1})
return
end
end
-- Inventory page controls
local start = math.floor(
unified_inventory.current_index[player_name] / ui_peruser.items_per_page + 1)
local start_i = start
local pagemax = math.floor(
(#unified_inventory.filtered_items_list[player_name] - 1)
/ (ui_peruser.items_per_page) + 1)
if fields.start_list then
start_i = 1
end
if fields.rewind1 then
start_i = start_i - 1
end
if fields.forward1 then
start_i = start_i + 1
end
if fields.rewind3 then
start_i = start_i - 3
end
if fields.forward3 then
start_i = start_i + 3
end
if fields.end_list then
start_i = pagemax
end
if start_i < 1 then
start_i = 1
end
if start_i > pagemax then
start_i = pagemax
end
if start_i ~= start then
minetest.sound_play("paperflip1",
{to_player=player_name, gain = 1.0})
unified_inventory.current_index[player_name] = (start_i - 1) * ui_peruser.items_per_page + 1
unified_inventory.set_inventory_formspec(player,
unified_inventory.current_page[player_name])
end
-- Check clicked item image button
local clicked_item
for name, value in pairs(fields) do
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
-- Change search filter to this group
apply_new_filter(player, clicked_item, new_dir)
return
end
if new_dir == "recipe" or new_dir == "usage" then
unified_inventory.current_craft_direction[player_name] = new_dir
end
break
end
end
if clicked_item then
minetest.sound_play("click",
{to_player=player_name, gain = 0.1})
local page = unified_inventory.current_page[player_name]
local player_creative = unified_inventory.is_creative(player_name)
if not player_creative then
page = "craftguide"
end
if page == "craftguide" then
unified_inventory.current_item[player_name] = clicked_item
unified_inventory.alternate[player_name] = 1
unified_inventory.set_inventory_formspec(player, "craftguide")
elseif player_creative then
-- Creative page: Add entire stack to inventory
local inv = player:get_inventory()
local stack = ItemStack(clicked_item)
stack:set_count(stack:get_stack_max())
if inv:room_for_item("main", stack) then
inv:add_item("main", stack)
end
end
end
if fields.searchbutton
or fields.key_enter_field == "searchbox" then
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
apply_new_filter(player, "", "nochange")
end
-- alternate buttons
if not (fields.alternate or fields.alternate_prev) then
return
end
minetest.sound_play("click",
{to_player=player_name, gain = 0.1})
local item_name = unified_inventory.current_item[player_name]
if not item_name then
return
end
local crafts = unified_inventory.crafts_for[unified_inventory.current_craft_direction[player_name]][item_name]
if not crafts then
return
end
local alternates = #crafts
if alternates <= 1 then
return
end
local alternate
if fields.alternate then
alternate = unified_inventory.alternate[player_name] + 1
if alternate > alternates then
alternate = 1
end
elseif fields.alternate_prev then
alternate = unified_inventory.alternate[player_name] - 1
if alternate < 1 then
alternate = alternates
end
end
unified_inventory.alternate[player_name] = alternate
unified_inventory.set_inventory_formspec(player,
unified_inventory.current_page[player_name])
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

@ -0,0 +1,5 @@
default
creative?
sfinv?
datastorage?
farming?

View File

@ -0,0 +1 @@
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

@ -0,0 +1,127 @@
local S = minetest.get_translator("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()
if specname:sub(1, 6) ~= "group:" then
return nil, 0
end
local group_names = specname:sub(7):split(",")
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
-- is represented by that item, with the single-item status signalled
-- in the "sole" field. If the group contains no items at all, the item
-- field will be nil.
--
-- Within a multiple-item group, we prefer to use an item that has the
-- same specific name as the group, and if there are more than one of
-- those items we prefer the one registered for the group by a mod.
-- Among equally-preferred items, we just pick the one with the
-- lexicographically earliest name.
--
-- The parameter to this function isn't just a single group name.
-- It may be a comma-separated list of group names. This is really a
-- "group:..." ingredient specification, minus the "group:" prefix.
local function compute_group_item(group_name_list)
local group_names = group_name_list:split(",")
local candidate_items = {}
for itemname, itemdef in pairs(minetest.registered_items) do
if (itemdef.groups.not_in_creative_inventory or 0) == 0 then
local all = true
for _, group_name in ipairs(group_names) do
if (itemdef.groups[group_name] or 0) == 0 then
all = false
end
end
if all then table.insert(candidate_items, itemname) end
end
end
local num_candidates = #candidate_items
if num_candidates == 0 then
return {sole = true}
elseif num_candidates == 1 then
return {item = candidate_items[1], sole = true}
end
local is_group = {}
local registered_rep = {}
for _, group_name in ipairs(group_names) do
is_group[group_name] = true
local rep = unified_inventory.registered_group_items[group_name]
if rep then registered_rep[rep] = true end
end
local bestitem = ""
local bestpref = 0
for _, item in ipairs(candidate_items) do
local pref
if registered_rep[item] then
pref = 4
elseif string.sub(item, 1, 8) == "default:" and is_group[string.sub(item, 9)] then
pref = 3
elseif is_group[item:gsub("^[^:]*:", "")] then
pref = 2
else
pref = 1
end
if pref > bestpref or (pref == bestpref and item < bestitem) then
bestitem = item
bestpref = pref
end
end
return {item = bestitem, sole = false}
end
local group_item_cache = {}
function unified_inventory.get_group_item(group_name)
if not group_item_cache[group_name] then
group_item_cache[group_name] = compute_group_item(group_name)
end
return group_item_cache[group_name]
end

View File

@ -0,0 +1,69 @@
bags_small.png:
http://www.clker.com/clipart-moneybag-empty.html
bags_medium.png:
http://www.clker.com/clipart-backpack-1.html
bags_large.png / ui_bags_icon.png:
http://www.clker.com/clipart-backpack-green-brown.html
ui_craftguide_icon.png / ui_craft_icon.png
http://commons.wikimedia.org/wiki/File:Advancedsettings.png
ui_doubleleft_icon.png
http://commons.wikimedia.org/wiki/File:Media-seek-backward.svg
ui_doubleright_icon.png
http://commons.wikimedia.org/wiki/File:Media-seek-forward.svg
ui_left_icon.png / ui_right_icon.png
http://commons.wikimedia.org/wiki/File:Media-playback-start.svg
ui_skip_backward_icon.png
http://commons.wikimedia.org/wiki/File:Media-skip-backward.svg
ui_skip_forward_icon.png
http://commons.wikimedia.org/wiki/File:Media-skip-forward.svg
ui_reset_icon.png
https://commons.wikimedia.org/wiki/File:Edit-clear.svg
ui_gohome_icon.png / ui_home_icon.png / ui_sethome_icon.png
http://commons.wikimedia.org/wiki/File:Home_256x256.png
ui_moon_icon.png
http://commons.wikimedia.org/wiki/File:FullMoon2010.jpg
ui_sun_icon.png
http://commons.wikimedia.org/wiki/File:2012-10-13_15-29-35-sun.jpg
ui_trash_icon.png
http://www.clker.com/clipart-29090.html
http://www.clker.com/clipart-trash.html
ui_search_icon.png
http://www.clker.com/clipart-24887.html
ui_off_icon.png / ui_on_icon.png
http://www.clker.com/clipart-on-off-switches.html
ui_waypoints_icon.png
http://www.clker.com/clipart-map-pin-red.html
ui_circular_arrows_icon.png
http://www.clker.com/clipart-circular-arrow-pattern.html
ui_pencil_icon.pnc
http://www.clker.com/clipart-2256.html
ui_waypoint_set_icon.png
http://www.clker.com/clipart-larger-flag.html
ui_xyz_off_icon.png
http://commons.wikimedia.org/wiki/File:No_sign.svg
ui_ok_icon.png
http://commons.wikimedia.org/wiki/File:Yes_check.svg
inventory_plus_worldedit_gui.png
http://commons.wikimedia.org/wiki/File:Erioll_world_2.svg

View File

@ -0,0 +1,79 @@
-- Unified Inventory for Minetest >= 0.4.16
local modpath = minetest.get_modpath(minetest.get_current_modname())
local worldpath = minetest.get_worldpath()
-- Data tables definitions
unified_inventory = {
activefilter = {},
active_search_direction = {},
alternate = {},
current_page = {},
current_searchbox = {},
current_index = {},
current_item = {},
current_craft_direction = {},
registered_craft_types = {},
crafts_for = {usage = {}, recipe = {} },
players = {},
items_list_size = 0,
items_list = {},
filtered_items_list_size = {},
filtered_items_list = {},
pages = {},
buttons = {},
-- Homepos stuff
home_pos = {},
home_filename = worldpath.."/unified_inventory_home.home",
-- Default inventory page
default = "craft",
-- "Lite" mode
lite_mode = minetest.settings:get_bool("unified_inventory_lite"),
-- Trash enabled
trash_enabled = (minetest.settings:get_bool("unified_inventory_trash") ~= false),
pagecols = 8,
pagerows = 10,
page_y = 0,
formspec_y = 1,
main_button_x = 0,
main_button_y = 9,
craft_result_x = 0.3,
craft_result_y = 0.5,
form_header_y = 0
}
-- Disable default creative inventory
local creative = rawget(_G, "creative") or rawget(_G, "creative_inventory")
if creative then
function creative.set_creative_formspec(player, start_i, pagenum)
return
end
end
-- Disable sfinv inventory
local sfinv = rawget(_G, "sfinv")
if sfinv then
sfinv.enabled = false
end
dofile(modpath.."/group.lua")
dofile(modpath.."/api.lua")
dofile(modpath.."/internal.lua")
dofile(modpath.."/callbacks.lua")
dofile(modpath.."/match_craft.lua")
dofile(modpath.."/register.lua")
if minetest.settings:get_bool("unified_inventory_bags") ~= false then
dofile(modpath.."/bags.lua")
end
dofile(modpath.."/item_names.lua")
if minetest.get_modpath("datastorage") then
dofile(modpath.."/waypoints.lua")
end

View File

@ -0,0 +1,374 @@
local S = minetest.get_translator("unified_inventory")
local F = minetest.formspec_escape
-- This pair of encoding functions is used where variable text must go in
-- button names, where the text might contain formspec metacharacters.
-- We can escape button names for the formspec, to avoid screwing up
-- form structure overall, but they then don't get de-escaped, and so
-- the input we get back from the button contains the formspec escaping.
-- This is a game engine bug, and in the anticipation that it might be
-- fixed some day we don't want to rely on it. So for safety we apply
-- an encoding that avoids all formspec metacharacters.
function unified_inventory.mangle_for_formspec(str)
return string.gsub(str, "([^A-Za-z0-9])", function (c) return string.format("_%d_", string.byte(c)) end)
end
function unified_inventory.demangle_for_formspec(str)
return string.gsub(str, "_([0-9]+)_", function (v) return string.char(v) end)
end
function unified_inventory.get_per_player_formspec(player_name)
local lite = unified_inventory.lite_mode and not minetest.check_player_privs(player_name, {ui_full=true})
local ui = {}
ui.pagecols = unified_inventory.pagecols
ui.pagerows = unified_inventory.pagerows
ui.page_y = unified_inventory.page_y
ui.formspec_y = unified_inventory.formspec_y
ui.main_button_x = unified_inventory.main_button_x
ui.main_button_y = unified_inventory.main_button_y
ui.craft_result_x = unified_inventory.craft_result_x
ui.craft_result_y = unified_inventory.craft_result_y
ui.form_header_y = unified_inventory.form_header_y
if lite then
ui.pagecols = 4
ui.pagerows = 6
ui.page_y = 0.25
ui.formspec_y = 0.47
ui.main_button_x = 8.2
ui.main_button_y = 6.5
ui.craft_result_x = 2.8
ui.craft_result_y = 3.4
ui.form_header_y = -0.1
end
ui.items_per_page = ui.pagecols * ui.pagerows
return ui, lite
end
function unified_inventory.get_formspec(player, page)
if not player then
return ""
end
local player_name = player:get_player_name()
local ui_peruser,draw_lite_mode = unified_inventory.get_per_player_formspec(player_name)
unified_inventory.current_page[player_name] = page
local pagedef = unified_inventory.pages[page]
if not pagedef then
return "" -- Invalid page name
end
local formspec = {
"size[14,10]",
pagedef.formspec_prepend and "" or "no_prepend[]",
"background[-0.19,-0.25;14.4,10.75;ui_form_bg.png]" -- Background
}
local n = 4
if draw_lite_mode then
formspec[1] = "size[11,7.7]"
formspec[3] = "background[-0.19,-0.2;11.4,8.4;ui_form_bg.png]"
end
if unified_inventory.is_creative(player_name)
and page == "craft" then
formspec[n] = "background[0,"..(ui_peruser.formspec_y + 2)..";1,1;ui_single_slot.png]"
n = n+1
end
local perplayer_formspec = unified_inventory.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 i, def in pairs(unified_inventory.buttons) do
if not (draw_lite_mode and def.hide_lite) then
table.insert(filtered_inv_buttons, def)
end
end
for i, def in pairs(filtered_inv_buttons) do
if draw_lite_mode and i > 4 then
button_row = 1
button_col = 1
end
if def.type == "image" then
if (def.condition == nil or def.condition(player) == true) then
formspec[n] = "image_button["
formspec[n+1] = ( ui_peruser.main_button_x + 0.65 * (i - 1) - button_col * 0.65 * 4)
formspec[n+2] = ","..(ui_peruser.main_button_y + button_row * 0.7)..";0.8,0.8;"
formspec[n+3] = F(def.image)..";"
formspec[n+4] = F(def.name)..";]"
formspec[n+5] = "tooltip["..F(def.name)
formspec[n+6] = ";"..(def.tooltip or "").."]"
n = n+7
else
formspec[n] = "image["
formspec[n+1] = ( ui_peruser.main_button_x + 0.65 * (i - 1) - button_col * 0.65 * 4)
formspec[n+2] = ","..(ui_peruser.main_button_y + button_row * 0.7)..";0.8,0.8;"
formspec[n+3] = F(def.image).."^[colorize:#808080:alpha]"
n = n+4
end
end
end
if fsdata.draw_inventory ~= false then
-- Player inventory
formspec[n] = "listcolors[#00000000;#00000000]"
formspec[n+1] = "list[current_player;main;0,"..(ui_peruser.formspec_y + 3.5)..";8,4;]"
n = n+2
end
if fsdata.draw_item_list == false then
return table.concat(formspec, "")
end
-- Controls to flip items pages
local start_x = 9.2
if not draw_lite_mode then
formspec[n] =
"image_button[" .. (start_x + 0.6 * 0)
.. ",9;.8,.8;ui_skip_backward_icon.png;start_list;]"
.. "tooltip[start_list;" .. F(S("First page")) .. "]"
.. "image_button[" .. (start_x + 0.6 * 1)
.. ",9;.8,.8;ui_doubleleft_icon.png;rewind3;]"
.. "tooltip[rewind3;" .. F(S("Back three pages")) .. "]"
.. "image_button[" .. (start_x + 0.6 * 2)
.. ",9;.8,.8;ui_left_icon.png;rewind1;]"
.. "tooltip[rewind1;" .. F(S("Back one page")) .. "]"
.. "image_button[" .. (start_x + 0.6 * 3)
.. ",9;.8,.8;ui_right_icon.png;forward1;]"
.. "tooltip[forward1;" .. F(S("Forward one page")) .. "]"
.. "image_button[" .. (start_x + 0.6 * 4)
.. ",9;.8,.8;ui_doubleright_icon.png;forward3;]"
.. "tooltip[forward3;" .. F(S("Forward three pages")) .. "]"
.. "image_button[" .. (start_x + 0.6 * 5)
.. ",9;.8,.8;ui_skip_forward_icon.png;end_list;]"
.. "tooltip[end_list;" .. F(S("Last page")) .. "]"
else
formspec[n] =
"image_button[" .. (8.2 + 0.65 * 0)
.. ",5.8;.8,.8;ui_skip_backward_icon.png;start_list;]"
.. "tooltip[start_list;" .. F(S("First page")) .. "]"
.. "image_button[" .. (8.2 + 0.65 * 1)
.. ",5.8;.8,.8;ui_left_icon.png;rewind1;]"
.. "tooltip[rewind1;" .. F(S("Back one page")) .. "]"
.. "image_button[" .. (8.2 + 0.65 * 2)
.. ",5.8;.8,.8;ui_right_icon.png;forward1;]"
.. "tooltip[forward1;" .. F(S("Forward one page")) .. "]"
.. "image_button[" .. (8.2 + 0.65 * 3)
.. ",5.8;.8,.8;ui_skip_forward_icon.png;end_list;]"
.. "tooltip[end_list;" .. F(S("Last page")) .. "]"
end
n = n+1
-- Search box
formspec[n] = "field_close_on_enter[searchbox;false]"
n = n+1
if not draw_lite_mode then
formspec[n] = "field[9.5,8.325;3,1;searchbox;;"
.. F(unified_inventory.current_searchbox[player_name]) .. "]"
formspec[n+1] = "image_button[12.2,8.1;.8,.8;ui_search_icon.png;searchbutton;]"
.. "tooltip[searchbutton;" ..F(S("Search")) .. "]"
formspec[n+2] = "image_button[12.9,8.1;.8,.8;ui_reset_icon.png;searchresetbutton;]"
.. "tooltip[searchbutton;" ..F(S("Search")) .. "]"
.. "tooltip[searchresetbutton;" ..F(S("Reset search and display everything")) .. "]"
else
formspec[n] = "field[8.5,5.225;2.2,1;searchbox;;"
.. F(unified_inventory.current_searchbox[player_name]) .. "]"
formspec[n+1] = "image_button[10.3,5;.8,.8;ui_search_icon.png;searchbutton;]"
.. "tooltip[searchbutton;" ..F(S("Search")) .. "]"
formspec[n+2] = "image_button[11,5;.8,.8;ui_reset_icon.png;searchresetbutton;]"
.. "tooltip[searchbutton;" ..F(S("Search")) .. "]"
.. "tooltip[searchresetbutton;" ..F(S("Reset search and display everything")) .. "]"
end
n = n+3
local no_matches = S("No matching items")
if draw_lite_mode then
no_matches = S("No matches.")
end
-- Items list
if #unified_inventory.filtered_items_list[player_name] == 0 then
formspec[n] = "label[8.2,"..ui_peruser.form_header_y..";" .. F(no_matches) .. "]"
else
local dir = unified_inventory.active_search_direction[player_name]
local list_index = unified_inventory.current_index[player_name]
local page = math.floor(list_index / (ui_peruser.items_per_page) + 1)
local pagemax = math.floor(
(#unified_inventory.filtered_items_list[player_name] - 1)
/ (ui_peruser.items_per_page) + 1)
local item = {}
for y = 0, ui_peruser.pagerows - 1 do
for x = 0, ui_peruser.pagecols - 1 do
local name = unified_inventory.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 == unified_inventory.current_item[player_name] then
local cdir = unified_inventory.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 = unified_inventory.active_search_direction[player_name]
end
local button_name = "item_button_" .. dir .. "_"
.. unified_inventory.mangle_for_formspec(name)
formspec[n] = ("item_image_button[%f,%f;.81,.81;%s;%s;]"):format(
8.2 + x * 0.7, ui_peruser.formspec_y + ui_peruser.page_y + y * 0.7,
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
end
end
formspec[n] = "label[8.2,"..ui_peruser.form_header_y..";"..F(S("Page")) .. ": "
.. S("@1 of @2",page,pagemax).."]"
end
n= n+1
if unified_inventory.activefilter[player_name] ~= "" then
formspec[n] = "label[8.2,"..(ui_peruser.form_header_y + 0.4)..";" .. F(S("Filter")) .. ":]"
formspec[n+1] = "label[9.1,"..(ui_peruser.form_header_y + 0.4)..";"..F(unified_inventory.activefilter[player_name]).."]"
end
return table.concat(formspec, "")
end
function unified_inventory.set_inventory_formspec(player, page)
if player then
player:set_inventory_formspec(unified_inventory.get_formspec(player, page))
end
end
--apply filter to the inventory list (create filtered copy of full one)
function unified_inventory.apply_filter(player, filter, search_dir)
if not player then
return false
end
local player_name = player:get_player_name()
local lfilter = string.lower(filter)
local ffilter
if lfilter:sub(1, 6) == "group:" then
local groups = lfilter:sub(7):split(",")
ffilter = function(name, def)
for _, group in ipairs(groups) do
if not def.groups[group]
or def.groups[group] <= 0 then
return false
end
end
return true
end
else
ffilter = function(name, def)
local lname = string.lower(name)
local ldesc = string.lower(def.description)
return string.find(lname, lfilter, 1, true) or string.find(ldesc, lfilter, 1, true)
end
end
unified_inventory.filtered_items_list[player_name]={}
for name, def in pairs(minetest.registered_items) do
if (not def.groups.not_in_creative_inventory
or def.groups.not_in_creative_inventory == 0)
and def.description
and def.description ~= ""
and ffilter(name, def) then
table.insert(unified_inventory.filtered_items_list[player_name], name)
end
end
table.sort(unified_inventory.filtered_items_list[player_name])
unified_inventory.filtered_items_list_size[player_name] = #unified_inventory.filtered_items_list[player_name]
unified_inventory.current_index[player_name] = 1
unified_inventory.activefilter[player_name] = filter
unified_inventory.active_search_direction[player_name] = search_dir
unified_inventory.set_inventory_formspec(player,
unified_inventory.current_page[player_name])
end
function unified_inventory.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
return items
end
function unified_inventory.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

@ -0,0 +1,76 @@
-- Based on 4itemnames mod by 4aiman
local item_names = {} -- [player_name] = { hud, dtime, itemname }
local dlimit = 3 -- HUD element will be hidden after this many seconds
local air_hud_mod = minetest.get_modpath("4air")
local hud_mod = minetest.get_modpath("hud")
local hudbars_mod = minetest.get_modpath("hudbars")
local function set_hud(player)
local player_name = player:get_player_name()
local off = {x=0, y=-70}
if air_hud_mod or hud_mod then
off.y = off.y - 20
elseif hudbars_mod then
off.y = off.y + 13
end
item_names[player_name] = {
hud = player:hud_add({
hud_elem_type = "text",
position = {x=0.5, y=1},
offset = off,
alignment = {x=0, y=0},
number = 0xFFFFFF,
text = "",
}),
dtime = dlimit,
index = 1,
itemname = ""
}
end
minetest.register_on_joinplayer(function(player)
minetest.after(0, set_hud, player)
end)
minetest.register_on_leaveplayer(function(player)
item_names[player:get_player_name()] = nil
end)
minetest.register_globalstep(function(dtime)
for _, player in pairs(minetest.get_connected_players()) do
local data = item_names[player:get_player_name()]
if not data or not data.hud then
data = {} -- Update on next step
set_hud(player)
end
local index = player:get_wield_index()
local stack = player:get_wielded_item()
local itemname = stack:get_name()
if data.hud and data.dtime < dlimit then
data.dtime = data.dtime + dtime
if data.dtime > dlimit then
player:hud_change(data.hud, 'text', "")
end
end
if data.hud and (itemname ~= data.itemname or index ~= data.index) then
data.itemname = itemname
data.index = index
data.dtime = 0
local desc = stack.get_meta
and stack:get_meta():get_string("description")
if not desc or desc == "" then
-- Try to use default description when none is set in the meta
local def = minetest.registered_items[itemname]
desc = def and def.description or ""
end
player:hud_change(data.hud, 'text', desc)
end
end
end)

View File

@ -0,0 +1,409 @@
-- match_craft.lua
-- Find and automatically move inventory items to the crafting grid
-- according to the recipe.
--[[
Retrieve items from inventory lists and calculate their total count.
Return a table of "item name" - "total count" pairs.
Arguments:
inv: minetest inventory reference
lists: names of inventory lists to use
Example usage:
-- Count items in "main" and "craft" lists of player inventory
unified_inventory.count_items(player_inv_ref, {"main", "craft"})
Example output:
{
["default:pine_wood"] = 2,
["default:acacia_wood"] = 4,
["default:chest"] = 3,
["default:axe_diamond"] = 2, -- unstackable item are counted too
["wool:white"] = 6
}
]]--
function unified_inventory.count_items(inv, lists)
local counts = {}
for i = 1, #lists do
local name = lists[i]
local size = inv:get_size(name)
local list = inv:get_list(name)
for j = 1, size do
local stack = list[j]
if not stack:is_empty() then
local item = stack:get_name()
local count = stack:get_count()
counts[item] = (counts[item] or 0) + count
end
end
end
return counts
end
--[[
Retrieve craft recipe items and their positions in the crafting grid.
Return a table of "craft item name" - "set of positions" pairs.
Note that if craft width is not 3 then positions are recalculated as
if items were placed on a 3x3 grid. Also note that craft can contain
groups of items with "group:" prefix.
Arguments:
craft: minetest craft recipe
Example output:
-- Bed recipe
{
["wool:white"] = {[1] = true, [2] = true, [3] = true}
["group:wood"] = {[4] = true, [5] = true, [6] = true}
}
--]]
function unified_inventory.count_craft_positions(craft)
local positions = {}
local craft_items = craft.items
local craft_type = unified_inventory.registered_craft_types[craft.type]
or unified_inventory.craft_type_defaults(craft.type, {})
local display_width = craft_type.dynamic_display_size
and craft_type.dynamic_display_size(craft).width
or craft_type.width
local craft_width = craft_type.get_shaped_craft_width
and craft_type.get_shaped_craft_width(craft)
or display_width
local i = 0
for y = 1, 3 do
for x = 1, craft_width do
i = i + 1
local item = craft_items[i]
if item ~= nil then
local pos = 3 * (y - 1) + x
local set = positions[item]
if set ~= nil then
set[pos] = true
else
positions[item] = {[pos] = true}
end
end
end
end
return positions
end
--[[
For every craft item find all matching inventory items.
- If craft item is a group then find all inventory items that matches
this group.
- If craft item is not a group (regular item) then find only this item.
If inventory doesn't contain needed item then found set is empty for
this item.
Return a table of "craft item name" - "set of matching inventory items"
pairs.
Arguments:
inv_items: table with items names as keys
craft_items: table with items names or groups as keys
Example output:
{
["group:wood"] = {
["default:pine_wood"] = true,
["default:acacia_wood"] = true
},
["wool:white"] = {
["wool:white"] = true
}
}
--]]
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
local group = craft_item:match("^group:(.+)")
local found = {}
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
return result
end
--[[
Match inventory items with craft grid positions.
For every position select the matching inventory item with maximum
(total_count / (times_matched + 1)) value.
If for some position matching item cannot be found or match count is 0
then return nil.
Return a table of "matched item name" - "set of craft positions" pairs
and overall match count.
Arguments:
inv_counts: table of inventory items counts from "count_items"
craft_positions: table of craft positions from "count_craft_positions"
Example output:
match_table = {
["wool:white"] = {[1] = true, [2] = true, [3] = true}
["default:acacia_wood"] = {[4] = true, [6] = true}
["default:pine_wood"] = {[5] = true}
}
match_count = 2
--]]
function unified_inventory.match_items(inv_counts, craft_positions)
local usable = unified_inventory.find_usable_items(inv_counts, craft_positions)
local match_table = {}
local match_count
local matches = {}
for craft_item, pos_set in pairs(craft_positions) do
local use_set = usable[craft_item]
for pos in pairs(pos_set) do
local pos_item
local pos_count
for use_item in pairs(use_set) do
local count = inv_counts[use_item]
local times_matched = matches[use_item] or 0
local new_pos_count = math.floor(count / (times_matched + 1))
if pos_count == nil or pos_count < new_pos_count then
pos_item = use_item
pos_count = new_pos_count
end
end
if pos_item == nil or pos_count == 0 then
return nil
end
local set = match_table[pos_item]
if set ~= nil then
set[pos] = true
else
match_table[pos_item] = {[pos] = true}
end
matches[pos_item] = (matches[pos_item] or 0) + 1
end
end
for match_item, times_matched in pairs(matches) do
local count = inv_counts[match_item]
local item_count = math.floor(count / times_matched)
if match_count == nil or item_count < match_count then
match_count = item_count
end
end
return match_table, match_count
end
--[[
Remove item from inventory lists.
Return stack of actually removed items.
This function replicates the inv:remove_item function but can accept
multiple lists.
Arguments:
inv: minetest inventory reference
lists: names of inventory lists
stack: minetest item stack
--]]
function unified_inventory.remove_item(inv, lists, stack)
local removed = ItemStack(nil)
local leftover = ItemStack(stack)
for i = 1, #lists do
if leftover:is_empty() then
break
end
local cur_removed = inv:remove_item(lists[i], leftover)
removed:add_item(cur_removed)
leftover:take_item(cur_removed:get_count())
end
return removed
end
--[[
Add item to inventory lists.
Return leftover stack.
This function replicates the inv:add_item function but can accept
multiple lists.
Arguments:
inv: minetest inventory reference
lists: names of inventory lists
stack: minetest item stack
--]]
function unified_inventory.add_item(inv, lists, stack)
local leftover = ItemStack(stack)
for i = 1, #lists do
if leftover:is_empty() then
break
end
leftover = inv:add_item(lists[i], leftover)
end
return leftover
end
--[[
Move items from source list to destination list if possible.
Skip positions specified in exclude set.
Arguments:
inv: minetest inventory reference
src_list: name of source list
dst_list: name of destination list
exclude: set of positions to skip
--]]
function unified_inventory.swap_items(inv, src_list, dst_list, exclude)
local size = inv:get_size(src_list)
local empty = ItemStack(nil)
for i = 1, size do
if exclude == nil or exclude[i] == nil then
local stack = inv:get_stack(src_list, i)
if not stack:is_empty() then
inv:set_stack(src_list, i, empty)
local leftover = inv:add_item(dst_list, stack)
if not leftover:is_empty() then
inv:set_stack(src_list, i, leftover)
end
end
end
end
end
--[[
Move matched items to the destination list.
If destination list position is already occupied with some other item
then function tries to (in that order):
1. Move it to the source list
2. Move it to some other unused position in destination list itself
3. Drop it to the ground if nothing else is possible.
Arguments:
player: minetest player object
src_list: name of source list
dst_list: name of destination list
match_table: table of matched items
amount: amount of items per every position
--]]
function unified_inventory.move_match(player, src_list, dst_list, match_table, amount)
local inv = player:get_inventory()
local item_drop = minetest.item_drop
local src_dst_list = {src_list, dst_list}
local dst_src_list = {dst_list, src_list}
local needed = {}
local moved = {}
-- Remove stacks needed for craft
for item, pos_set in pairs(match_table) do
local stack = ItemStack(item)
local stack_max = stack:get_stack_max()
local bounded_amount = math.min(stack_max, amount)
stack:set_count(bounded_amount)
for pos in pairs(pos_set) do
needed[pos] = unified_inventory.remove_item(inv, dst_src_list, stack)
end
end
-- Add already removed stacks
for pos, stack in pairs(needed) do
local occupied = inv:get_stack(dst_list, pos)
inv:set_stack(dst_list, pos, stack)
if not occupied:is_empty() then
local leftover = unified_inventory.add_item(inv, src_dst_list, occupied)
if not leftover:is_empty() then
inv:set_stack(dst_list, pos, leftover)
local oversize = unified_inventory.add_item(inv, src_dst_list, stack)
if not oversize:is_empty() then
item_drop(oversize, player, player:get_pos())
end
end
end
moved[pos] = true
end
-- Swap items from unused positions to src (moved positions excluded)
unified_inventory.swap_items(inv, dst_list, src_list, moved)
end
--[[
Find craft match and move matched items to the destination list.
If match cannot be found or match count is smaller than the desired
amount then do nothing.
If amount passed is -1 then amount is defined by match count itself.
This is used to indicate "craft All" case.
Arguments:
player: minetest player object
src_list: name of source list
dst_list: name of destination list
craft: minetest craft recipe
amount: desired amount of output items
--]]
function unified_inventory.craftguide_match_craft(player, src_list, dst_list, craft, amount)
local inv = player:get_inventory()
local src_dst_list = {src_list, dst_list}
local counts = unified_inventory.count_items(inv, src_dst_list)
local positions = unified_inventory.count_craft_positions(craft)
local match_table, match_count = unified_inventory.match_items(counts, positions)
if match_table == nil or match_count < amount then
return
end
if amount == -1 then
amount = match_count
end
unified_inventory.move_match(player, src_list, dst_list, match_table, amount)
end

View File

@ -0,0 +1,4 @@
name = unified_inventory
depends = default
optional_depends = 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

@ -0,0 +1,489 @@
local S = minetest.get_translator("unified_inventory")
local NS = function(s) return s end
local F = minetest.formspec_escape
minetest.register_privilege("creative", {
description = S("Can use the creative inventory"),
give_to_singleplayer = false,
})
minetest.register_privilege("ui_full", {
description = S("Forces Unified Inventory to be displayed in Full mode if Lite mode is configured globally"),
give_to_singleplayer = false,
})
local trash = minetest.create_detached_inventory("trash", {
--allow_put = function(inv, listname, index, stack, player)
-- if unified_inventory.is_creative(player:get_player_name()) then
-- return stack:get_count()
-- else
-- return 0
-- end
--end,
on_put = function(inv, listname, index, stack, player)
inv:set_stack(listname, index, nil)
local player_name = player:get_player_name()
minetest.sound_play("trash", {to_player=player_name, gain = 1.0})
end,
})
trash:set_size("main", 1)
unified_inventory.register_button("craft", {
type = "image",
image = "ui_craft_icon.png",
tooltip = S("Crafting Grid")
})
unified_inventory.register_button("craftguide", {
type = "image",
image = "ui_craftguide_icon.png",
tooltip = S("Crafting Guide")
})
unified_inventory.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
unified_inventory.set_home(player, player:get_pos())
local home = unified_inventory.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!"))
unified_inventory.set_inventory_formspec(player, unified_inventory.current_page[player_name])
end
end,
condition = function(player)
return minetest.check_player_privs(player:get_player_name(), {home=true})
end,
})
unified_inventory.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
minetest.sound_play("teleport",
{to_player=player:get_player_name(), gain = 1.0})
unified_inventory.go_home(player)
else
minetest.chat_send_player(player_name,
S("You don't have the \"home\" privilege!"))
unified_inventory.set_inventory_formspec(player, unified_inventory.current_page[player_name])
end
end,
condition = function(player)
return minetest.check_player_privs(player:get_player_name(), {home=true})
end,
})
unified_inventory.register_button("misc_set_day", {
type = "image",
image = "ui_sun_icon.png",
tooltip = S("Set time to day"),
hide_lite=true,
action = function(player)
local player_name = player:get_player_name()
if minetest.check_player_privs(player_name, {settime=true}) then
minetest.sound_play("birds",
{to_player=player_name, gain = 1.0})
minetest.set_timeofday((6000 % 24000) / 24000)
minetest.chat_send_player(player_name,
S("Time of day set to 6am"))
else
minetest.chat_send_player(player_name,
S("You don't have the settime privilege!"))
unified_inventory.set_inventory_formspec(player, unified_inventory.current_page[player_name])
end
end,
condition = function(player)
return minetest.check_player_privs(player:get_player_name(), {settime=true})
end,
})
unified_inventory.register_button("misc_set_night", {
type = "image",
image = "ui_moon_icon.png",
tooltip = S("Set time to night"),
hide_lite=true,
action = function(player)
local player_name = player:get_player_name()
if minetest.check_player_privs(player_name, {settime=true}) then
minetest.sound_play("owl",
{to_player=player_name, gain = 1.0})
minetest.set_timeofday((21000 % 24000) / 24000)
minetest.chat_send_player(player_name,
S("Time of day set to 9pm"))
else
minetest.chat_send_player(player_name,
S("You don't have the settime privilege!"))
unified_inventory.set_inventory_formspec(player, unified_inventory.current_page[player_name])
end
end,
condition = function(player)
return minetest.check_player_privs(player:get_player_name(), {settime=true})
end,
})
unified_inventory.register_button("clear_inv", {
type = "image",
image = "ui_trash_icon.png",
tooltip = S("Clear inventory"),
action = function(player)
local player_name = player:get_player_name()
if not unified_inventory.is_creative(player_name) then
minetest.chat_send_player(player_name,
S("This button has been disabled outside"
.." of creative mode to prevent"
.." accidental inventory trashing."
.."\nUse the trash slot instead."))
unified_inventory.set_inventory_formspec(player, unified_inventory.current_page[player_name])
return
end
player:get_inventory():set_list("main", {})
minetest.chat_send_player(player_name, S('Inventory cleared!'))
minetest.sound_play("trash_all",
{to_player=player_name, gain = 1.0})
end,
condition = function(player)
return unified_inventory.is_creative(player:get_player_name())
end,
})
unified_inventory.register_page("craft", {
get_formspec = function(player, perplayer_formspec)
local formspecy = perplayer_formspec.formspec_y
local formheadery = perplayer_formspec.form_header_y
local player_name = player:get_player_name()
local formspec = "background[2,"..formspecy..";6,3;ui_crafting_form.png]"
formspec = formspec.."background[0,"..(formspecy + 3.5)..";8,4;ui_main_inventory.png]"
formspec = formspec.."label[0,"..formheadery..";" ..F(S("Crafting")).."]"
formspec = formspec.."listcolors[#00000000;#00000000]"
formspec = formspec.."list[current_player;craftpreview;6,"..formspecy..";1,1;]"
formspec = formspec.."list[current_player;craft;2,"..formspecy..";3,3;]"
if unified_inventory.trash_enabled or unified_inventory.is_creative(player_name) or minetest.get_player_privs(player_name).give then
formspec = formspec.."label[7,"..(formspecy + 1.5)..";" .. F(S("Trash:")) .. "]"
formspec = formspec.."background[7,"..(formspecy + 2)..";1,1;ui_single_slot.png]"
formspec = formspec.."list[detached:trash;main;7,"..(formspecy + 2)..";1,1;]"
end
formspec = formspec.."listring[current_name;craft]"
formspec = formspec.."listring[current_player;main]"
if unified_inventory.is_creative(player_name) then
formspec = formspec.."label[0,"..(formspecy + 1.5)..";" .. F(S("Refill:")) .. "]"
formspec = formspec.."list[detached:"..F(player_name).."refill;main;0,"..(formspecy +2)..";1,1;]"
end
return {formspec=formspec}
end,
})
-- stack_image_button(): generate a form button displaying a stack of items
--
-- The specified item may be a group. In that case, the group will be
-- represented by some item in the group, along with a flag indicating
-- that it's a group. If the group contains only one item, it will be
-- treated as if that item had been specified directly.
local function stack_image_button(x, y, w, h, buttonname_prefix, item)
local name = item:get_name()
local count = item:get_count()
local show_is_group = false
local displayitem = name.." "..count
local selectitem = name
if name:sub(1, 6) == "group:" then
local group_name = name:sub(7)
local group_item = unified_inventory.get_group_item(group_name)
show_is_group = not group_item.sole
displayitem = group_item.item or "unknown"
selectitem = group_item.sole and displayitem or name
end
local label = show_is_group and "G" or ""
local buttonname = F(buttonname_prefix..unified_inventory.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)
if show_is_group then
local groupstring, andcount = unified_inventory.extract_groupnames(name)
local grouptip
if andcount == 1 then
grouptip = S("Any item belonging to the @1 group", groupstring)
elseif andcount > 1 then
grouptip = S("Any item belonging to the groups @1", groupstring)
end
grouptip = F(grouptip)
if andcount >= 1 then
button = button .. string.format("tooltip[%s;%s]", buttonname, grouptip)
end
end
return button
end
local recipe_text = {
recipe = NS("Recipe @1 of @2"),
usage = NS("Usage @1 of @2"),
}
local no_recipe_text = {
recipe = S("No recipes"),
usage = S("No usages"),
}
local role_text = {
recipe = S("Result"),
usage = S("Ingredient"),
}
local next_alt_text = {
recipe = S("Show next recipe"),
usage = S("Show next usage"),
}
local prev_alt_text = {
recipe = S("Show previous recipe"),
usage = S("Show previous usage"),
}
local other_dir = {
recipe = "usage",
usage = "recipe",
}
unified_inventory.register_page("craftguide", {
get_formspec = function(player, perplayer_formspec)
local formspecy = perplayer_formspec.formspec_y
local formheadery = perplayer_formspec.form_header_y
local craftresultx = perplayer_formspec.craft_result_x
local craftresulty = perplayer_formspec.craft_result_y
local player_name = player:get_player_name()
local player_privs = minetest.get_player_privs(player_name)
local fs = {
"background[0,"..(formspecy + 3.5)..";8,4;ui_main_inventory.png]",
"label[0,"..formheadery..";" .. F(S("Crafting Guide")) .. "]",
"listcolors[#00000000;#00000000]"
}
local item_name = unified_inventory.current_item[player_name]
if not item_name then
return { formspec = table.concat(fs) }
end
local item_name_shown
if minetest.registered_items[item_name]
and minetest.registered_items[item_name].description then
item_name_shown = S("@1 (@2)",
minetest.registered_items[item_name].description, item_name)
else
item_name_shown = item_name
end
local dir = unified_inventory.current_craft_direction[player_name]
local rdir = dir == "recipe" and "usage" or "recipe"
local crafts = unified_inventory.crafts_for[dir][item_name]
local alternate = unified_inventory.alternate[player_name]
local alternates, craft
if crafts and #crafts > 0 then
alternates = #crafts
craft = crafts[alternate]
end
local has_give = player_privs.give or unified_inventory.is_creative(player_name)
fs[#fs + 1] = "background[0.5,"..(formspecy + 0.2)..";8,3;ui_craftguide_form.png]"
fs[#fs + 1] = string.format("textarea[%f,%f;10,1;;%s: %s;]",
craftresultx, craftresulty, F(role_text[dir]), item_name_shown)
fs[#fs + 1] = stack_image_button(0, formspecy, 1.1, 1.1,
"item_button_" .. rdir .. "_", ItemStack(item_name))
if not craft then
-- No craft recipes available for this item.
fs[#fs + 1] = "label[5.5,"..(formspecy + 2.35)..";"
.. F(no_recipe_text[dir]) .. "]"
local no_pos = dir == "recipe" and 4.5 or 6.5
local item_pos = dir == "recipe" and 6.5 or 4.5
fs[#fs + 1] = "image["..no_pos..","..formspecy..";1.1,1.1;ui_no.png]"
fs[#fs + 1] = stack_image_button(item_pos, formspecy, 1.1, 1.1,
"item_button_" .. other_dir[dir] .. "_", ItemStack(item_name))
if has_give then
fs[#fs + 1] = "label[0," .. (formspecy + 2.10) .. ";" .. F(S("Give me:")) .. "]"
.. "button[0, " .. (formspecy + 2.7) .. ";0.6,0.5;craftguide_giveme_1;1]"
.. "button[0.6," .. (formspecy + 2.7) .. ";0.7,0.5;craftguide_giveme_10;10]"
.. "button[1.3," .. (formspecy + 2.7) .. ";0.8,0.5;craftguide_giveme_99;99]"
end
return { formspec = table.concat(fs) }
end
local craft_type = unified_inventory.registered_craft_types[craft.type] or
unified_inventory.craft_type_defaults(craft.type, {})
if craft_type.icon then
fs[#fs + 1] = string.format("image[%f,%f;%f,%f;%s]",
5.7, (formspecy + 0.05), 0.5, 0.5, craft_type.icon)
end
fs[#fs + 1] = "label[5.5,"..(formspecy + 1)..";" .. F(craft_type.description).."]"
fs[#fs + 1] = stack_image_button(6.5, formspecy, 1.1, 1.1,
"item_button_usage_", ItemStack(craft.output))
local display_size = craft_type.dynamic_display_size
and craft_type.dynamic_display_size(craft)
or { width = craft_type.width, height = craft_type.height }
local craft_width = craft_type.get_shaped_craft_width
and craft_type.get_shaped_craft_width(craft)
or display_size.width
-- This keeps recipes aligned to the right,
-- so that they're close to the arrow.
local xoffset = 5.5
-- Offset factor for crafting grids with side length > 4
local of = (3/math.max(3, math.max(display_size.width, display_size.height)))
local od = 0
-- Minimum grid size at which size optimazation measures kick in
local mini_craft_size = 6
if display_size.width >= mini_craft_size then
od = math.max(1, display_size.width - 2)
xoffset = xoffset - 0.1
end
-- Size modifier factor
local sf = math.min(1, of * (1.05 + 0.05*od))
-- Button size
local bsize_h = 1.1 * sf
local bsize_w = bsize_h
if display_size.width >= mini_craft_size then
bsize_w = 1.175 * sf
end
if (bsize_h > 0.35 and display_size.width) then
for y = 1, display_size.height do
for x = 1, display_size.width do
local item
if craft and x <= craft_width then
item = craft.items[(y-1) * craft_width + x]
end
-- Flipped x, used to build formspec buttons from right to left
local fx = display_size.width - (x-1)
-- x offset, y offset
local xof = (fx-1) * of + of
local yof = (y-1) * of + 1
if item then
fs[#fs + 1] = stack_image_button(
xoffset - xof, formspecy - 1 + yof, bsize_w, bsize_h,
"item_button_recipe_",
ItemStack(item))
else
-- Fake buttons just to make grid
fs[#fs + 1] = string.format("image_button[%f,%f;%f,%f;ui_blank_image.png;;]",
xoffset - xof, formspecy - 1 + yof, bsize_w, bsize_h)
end
end
end
else
-- Error
fs[#fs + 1] = string.format("label[2,%f;%s]",
formspecy, F(S("This recipe is too\nlarge to be displayed.")))
end
if craft_type.uses_crafting_grid and display_size.width <= 3 then
fs[#fs + 1] = "label[0," .. (formspecy + 0.9) .. ";" .. F(S("To craft grid:")) .. "]"
.. "button[0, " .. (formspecy + 1.5) .. ";0.6,0.5;craftguide_craft_1;1]"
.. "button[0.6," .. (formspecy + 1.5) .. ";0.7,0.5;craftguide_craft_10;10]"
.. "button[1.3," .. (formspecy + 1.5) .. ";0.8,0.5;craftguide_craft_max;" .. F(S("All")) .. "]"
end
if has_give then
fs[#fs + 1] = "label[0," .. (formspecy + 2.1) .. ";" .. F(S("Give me:")) .. "]"
.. "button[0, " .. (formspecy + 2.7) .. ";0.6,0.5;craftguide_giveme_1;1]"
.. "button[0.6," .. (formspecy + 2.7) .. ";0.7,0.5;craftguide_giveme_10;10]"
.. "button[1.3," .. (formspecy + 2.7) .. ";0.8,0.5;craftguide_giveme_99;99]"
end
if alternates and alternates > 1 then
fs[#fs + 1] = "label[5.5," .. (formspecy + 1.6) .. ";"
.. F(S(recipe_text[dir], alternate, alternates)) .. "]"
.. "image_button[5.5," .. (formspecy + 2) .. ";1,1;ui_left_icon.png;alternate_prev;]"
.. "image_button[6.5," .. (formspecy + 2) .. ";1,1;ui_right_icon.png;alternate;]"
.. "tooltip[alternate_prev;" .. F(prev_alt_text[dir]) .. "]"
.. "tooltip[alternate;" .. F(next_alt_text[dir]) .. "]"
end
return { formspec = table.concat(fs) }
end,
})
local function craftguide_giveme(player, formname, fields)
local player_name = player:get_player_name()
local player_privs = minetest.get_player_privs(player_name)
if not player_privs.give and
not unified_inventory.is_creative(player_name) then
minetest.log("action", "[unified_inventory] Denied give action to player " ..
player_name)
return
end
local amount
for k, v in pairs(fields) do
amount = k:match("craftguide_giveme_(.*)")
if amount then break end
end
amount = tonumber(amount) or 0
if amount == 0 then return end
local output = unified_inventory.current_item[player_name]
if (not output) or (output == "") then return end
local player_inv = player:get_inventory()
player_inv:add_item("main", {name = output, count = amount})
end
local function craftguide_craft(player, formname, fields)
local amount
for k, v in pairs(fields) do
amount = k:match("craftguide_craft_(.*)")
if amount then break end
end
if not amount then return end
amount = tonumber(amount) or -1 -- fallback for "all"
if amount == 0 or amount < -1 or amount > 99 then return end
local player_name = player:get_player_name()
local output = unified_inventory.current_item[player_name] or ""
if output == "" then return end
local crafts = unified_inventory.crafts_for[
unified_inventory.current_craft_direction[player_name]][output] or {}
if #crafts == 0 then return end
local alternate = unified_inventory.alternate[player_name]
local craft = crafts[alternate]
if craft.width > 3 then return end
unified_inventory.craftguide_match_craft(player, "main", "craft", craft, amount)
unified_inventory.set_inventory_formspec(player, "craft")
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "" then
return
end
for k, v in pairs(fields) do
if k:match("craftguide_craft_") then
craftguide_craft(player, formname, fields)
return
end
if k:match("craftguide_giveme_") then
craftguide_giveme(player, formname, fields)
return
end
end
end)

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

View File

@ -0,0 +1,11 @@
#Enabling lite mode enables a smaller and simpler version of the Unified
#Inventory, optimized for small displays.
unified_inventory_lite (Lite mode) bool false
#If enabled, bags will be made available which can be used to extend
#inventory storage size.
unified_inventory_bags (Enable bags) bool true
#If enabled, the trash slot can be used by those without both creative
#and the give privilege.
unified_inventory_trash (Enable trash) bool true