write something there
This commit is contained in:
commit
b4b6c08f4f
8546 changed files with 309825 additions and 0 deletions
445
mods/unified_inventory/api.lua
Normal file
445
mods/unified_inventory/api.lua
Normal file
|
@ -0,0 +1,445 @@
|
|||
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
|
||||
local itemname_cleaned = ItemStack(itemname):get_name()
|
||||
if not minetest.registered_items[itemname_cleaned]
|
||||
or minetest.get_item_group(itemname_cleaned, "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)
|
||||
end
|
||||
|
||||
-- Filtered item list
|
||||
ui.items_list = {}
|
||||
for name, def in pairs(minetest.registered_items) do
|
||||
if ui.is_itemdef_listable(def) 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)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
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
|
||||
for _, name in ipairs(ui.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
|
||||
ui.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
|
||||
ui.register_craft({
|
||||
type = "digging",
|
||||
items = {name},
|
||||
output = itemstring .. " " .. count,
|
||||
width = 0,
|
||||
})
|
||||
end
|
||||
for itemstring, count in pairs(drop_maybe) do
|
||||
ui.register_craft({
|
||||
type = "digging_chance",
|
||||
items = {name},
|
||||
output = itemstring .. " " .. count,
|
||||
width = 0,
|
||||
})
|
||||
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 _, 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
|
||||
end
|
||||
end
|
||||
for name, _ in pairs(ingredient_items) do
|
||||
if not ui.crafts_for.usage[name] 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
|
||||
|
||||
-- Remove unknown items from all categories
|
||||
local total_removed = 0
|
||||
for cat_name, cat_def in pairs(ui.registered_category_items) do
|
||||
for itemname, _ in pairs(cat_def) do
|
||||
local idef = minetest.registered_items[itemname]
|
||||
if not idef then
|
||||
total_removed = total_removed + 1
|
||||
--[[
|
||||
-- For analysis
|
||||
minetest.log("warning", "[unified_inventory] Removed item '"
|
||||
.. itemname .. "' from category '" .. cat_name
|
||||
.. "'. Reason: item not registered")
|
||||
]]
|
||||
cat_def[itemname] = nil
|
||||
elseif not ui.is_itemdef_listable(idef) then
|
||||
total_removed = total_removed + 1
|
||||
--[[
|
||||
-- For analysis
|
||||
minetest.log("warning", "[unified_inventory] Removed item '"
|
||||
.. itemname .. "' from category '" .. cat_name
|
||||
.. "'. Reason: item is in 'not_in_creative_inventory' group")
|
||||
]]
|
||||
cat_def[itemname] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
if total_removed > 0 then
|
||||
minetest.log("info", "[unified_inventory] Removed " .. total_removed ..
|
||||
" items from the categories.")
|
||||
end
|
||||
|
||||
for _, callback in ipairs(ui.initialized_callbacks) do
|
||||
callback()
|
||||
end
|
||||
end)
|
||||
|
||||
---------------- Home API ----------------
|
||||
|
||||
local function load_home()
|
||||
local input = io.open(ui.home_filename, "r")
|
||||
if not input then
|
||||
ui.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")
|
||||
ui.home_pos[name:sub(2)] = {x = x, y = y, z = z}
|
||||
end
|
||||
io.close(input)
|
||||
end
|
||||
|
||||
load_home()
|
||||
|
||||
function ui.set_home(player, pos)
|
||||
local player_name = player:get_player_name()
|
||||
ui.home_pos[player_name] = vector.round(pos)
|
||||
|
||||
-- save the home data from the table to the file
|
||||
local output = io.open(ui.home_filename, "w")
|
||||
if not output then
|
||||
minetest.log("warning", "[unified_inventory] Failed to save file: "
|
||||
.. ui.home_filename)
|
||||
return
|
||||
end
|
||||
for k, v in pairs(ui.home_pos) do
|
||||
output:write(v.x.." "..v.y.." "..v.z.." "..k.."\n")
|
||||
end
|
||||
io.close(output)
|
||||
end
|
||||
|
||||
function ui.go_home(player)
|
||||
local pos = ui.home_pos[player:get_player_name()]
|
||||
if pos then
|
||||
player:set_pos(pos)
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
---------------- Crafting API ----------------
|
||||
|
||||
function ui.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
|
||||
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)
|
||||
end
|
||||
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
|
||||
end
|
||||
setmetatable(options, {__index = craft_type_defaults})
|
||||
return options
|
||||
end
|
||||
|
||||
|
||||
function ui.register_craft_type(name, options)
|
||||
ui.registered_craft_types[name] = ui.craft_type_defaults(name, options)
|
||||
end
|
||||
|
||||
|
||||
ui.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,
|
||||
})
|
||||
|
||||
|
||||
ui.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,
|
||||
})
|
||||
|
||||
|
||||
ui.register_craft_type("cooking", {
|
||||
description = F(S("Cooking")),
|
||||
icon = "default_furnace_front.png",
|
||||
width = 1,
|
||||
height = 1,
|
||||
})
|
||||
|
||||
|
||||
ui.register_craft_type("digging", {
|
||||
description = F(S("Digging")),
|
||||
icon = "default_tool_steelpick.png",
|
||||
width = 1,
|
||||
height = 1,
|
||||
})
|
||||
|
||||
ui.register_craft_type("digging_chance", {
|
||||
description = "Digging (by chance)",
|
||||
icon = "default_tool_steelpick.png^[transformFY.png",
|
||||
width = 1,
|
||||
height = 1,
|
||||
})
|
||||
|
||||
---------------- GUI registrations ----------------
|
||||
|
||||
function ui.register_page(name, def)
|
||||
ui.pages[name] = def
|
||||
end
|
||||
|
||||
|
||||
function ui.register_button(name, def)
|
||||
if not def.action then
|
||||
def.action = function(player)
|
||||
ui.set_inventory_formspec(player, name)
|
||||
end
|
||||
end
|
||||
def.name = name
|
||||
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 "") )
|
||||
end
|
||||
|
||||
function ui.make_trash_slot(xpos, ypos)
|
||||
return
|
||||
ui.single_slot(xpos, ypos)..
|
||||
"image["..xpos..","..ypos..";1.25,1.25;ui_trash_slot_icon.png]"..
|
||||
"list[detached:trash;main;"..(xpos + ui.list_img_offset)..","..(ypos + ui.list_img_offset)..";1,1;]"
|
||||
end
|
||||
|
||||
function ui.make_inv_img_grid(xpos, ypos, width, height, bright)
|
||||
local tiled = {}
|
||||
local n=1
|
||||
for y = 0, (height - 1) do
|
||||
for x = 0, (width -1) do
|
||||
tiled[n] = ui.single_slot(xpos + (ui.imgscale * x), ypos + (ui.imgscale * y), bright)
|
||||
n = n + 1
|
||||
end
|
||||
end
|
||||
return table.concat(tiled)
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue