develop #35

Merged
milan merged 16 commits from develop into master 2021-07-24 12:49:35 +02:00
27 changed files with 1486 additions and 372 deletions
Showing only changes of commit fef615b588 - Show all commits

View file

@ -446,12 +446,22 @@ question is already loaded, or false if not.
===== =====
dbg(string) dbg(string, level)
This is a simple debug output function which takes one string parameter. It This is a simple debug output function which takes one string parameter. It
just checks if DEBUG is true and outputs the phrase "[Plantlife] " followed by just checks if DEBUG is true and outputs the phrase "[Plantlife] " followed by
the supplied string, via the print() function, if so. the supplied string, via the print() function, if so.
'level' is a number that, if supplied, dictates the lowest 'biome_lib_debug'
can be set to in minetest.conf for this message to be displayed. Both the
default log level and the default message level are 0, thus always showing the
supplied message.
Although it's not set in stone, a good practice is to use a level of 0 (or
just omit the value) for anything that generally important enough that it
ought always be shown, 1 for errors, 2 for warnings, 3 for info, 4 for verbose
spammy stuff.
===== =====
biome_lib:generate_tree(pos, treemodel) biome_lib:generate_tree(pos, treemodel)
biome_lib:grow_tree(pos, treemodel) biome_lib:grow_tree(pos, treemodel)

View file

@ -4,37 +4,42 @@
-- Splizard's snow mod. -- Splizard's snow mod.
-- --
biome_lib = {}
-- Boilerplate to support localized strings if intllib mod is installed.
local S
if minetest.global_exists("intllib") then
if intllib.make_gettext_pair then
S = intllib.make_gettext_pair()
else
S = intllib.Getter()
end
else
S = function(s) return s end
end
biome_lib.intllib = S
-- Various settings - most of these probably won't need to be changed -- Various settings - most of these probably won't need to be changed
biome_lib = {}
biome_lib.air = {name = "air"} biome_lib.air = {name = "air"}
biome_lib.blocklist_aircheck = {} biome_lib.block_log = {}
biome_lib.blocklist_no_aircheck = {} biome_lib.block_recheck_list = {}
biome_lib.run_block_recheck_list = false
biome_lib.surface_nodes_aircheck = {}
biome_lib.surface_nodes_no_aircheck = {}
biome_lib.surfaceslist_aircheck = {}
biome_lib.surfaceslist_no_aircheck = {}
biome_lib.actioncount_aircheck = {}
biome_lib.actioncount_no_aircheck = {}
biome_lib.actionslist_aircheck = {} biome_lib.actionslist_aircheck = {}
biome_lib.actionslist_no_aircheck = {} biome_lib.actionslist_no_aircheck = {}
biome_lib.surfaceslist_aircheck = {}
biome_lib.surfaceslist_no_aircheck = {}
biome_lib.modpath = minetest.get_modpath("biome_lib") biome_lib.modpath = minetest.get_modpath("biome_lib")
biome_lib.total_no_aircheck_calls = 0
biome_lib.queue_run_ratio = tonumber(minetest.settings:get("biome_lib_queue_run_ratio")) or 100
local function tableize(s) local function tableize(s)
return string.split(string.trim(string.gsub(s, " ", ""))) return string.split(string.trim(string.gsub(s, " ", "")))
end end
local c1 minetest.settings:get("biome_lib_default_grow_through_nodes") local c1 = minetest.settings:get("biome_lib_default_grow_through_nodes")
biome_lib.default_grow_through_nodes = {["air"] = true} biome_lib.default_grow_through_nodes = {["air"] = true}
if c1 then if c1 then
for _, i in ipairs(tableize(c1)) do for _, i in ipairs(tableize(c1)) do
@ -44,7 +49,7 @@ else
biome_lib.default_grow_through_nodes["default:snow"] = true biome_lib.default_grow_through_nodes["default:snow"] = true
end end
local c2 minetest.settings:get("biome_lib_default_water_nodes") local c2 = minetest.settings:get("biome_lib_default_water_nodes")
biome_lib.default_water_nodes = {} biome_lib.default_water_nodes = {}
if c2 then if c2 then
for _, i in ipairs(tableize(c2)) do for _, i in ipairs(tableize(c2)) do
@ -65,29 +70,27 @@ biome_lib.default_wet_surfaces = c3 and tableize(c3) or {"default:dirt", "defaul
biome_lib.default_ground_nodes = c4 and tableize(c4) or {"default:dirt_with_grass"} biome_lib.default_ground_nodes = c4 and tableize(c4) or {"default:dirt_with_grass"}
biome_lib.default_grow_nodes = c5 and tableize(c5) or {"default:dirt_with_grass"} biome_lib.default_grow_nodes = c5 and tableize(c5) or {"default:dirt_with_grass"}
-- Boilerplate to support localized strings if intllib mod is installed. biome_lib.debug_log_level = tonumber(minetest.settings:get("biome_lib_debug_log_level")) or 0
local S
if minetest.global_exists("intllib") then
if intllib.make_gettext_pair then
S = intllib.make_gettext_pair()
else
S = intllib.Getter()
end
else
S = function(s) return s end
end
biome_lib.intllib = S
local DEBUG = minetest.settings:get_bool("biome_lib_debug", false) local rr = tonumber(minetest.settings:get("biome_lib_queue_run_ratio")) or -100
biome_lib.queue_run_ratio = 100 - rr
biome_lib.entries_per_step = math.max(-rr, 1)
function biome_lib:dbg(msg) -- the timer that manages the block timeout is in microseconds, but the timer
if DEBUG then -- that manages the queue wakeup call has to be in seconds, and works best if
print("[Biome Lib] "..msg) -- it takes a fraction of the block timeout interval.
minetest.log("verbose", "[Biome Lib] "..msg)
end
end
biome_lib.plantlife_seed_diff = 329 -- needs to be global so other mods can see it local t = tonumber(minetest.settings:get("biome_lib_block_timeout")) or 300
biome_lib.block_timeout = t * 1000000
-- we don't want the wakeup function to trigger TOO often,
-- in case the user's block timeout setting is really low
biome_lib.block_queue_wakeup_time = math.min(t/2, math.max(20, t/10))
local time_speed = tonumber(minetest.settings:get("time_speed"))
biome_lib.plantlife_seed_diff = 329 -- needs to be global so other mods can see it
local perlin_octaves = 3 local perlin_octaves = 3
local perlin_persistence = 0.6 local perlin_persistence = 0.6
@ -104,7 +107,6 @@ local humidity_persistence = 0.5
local humidity_scale = 250 local humidity_scale = 250
local time_scale = 1 local time_scale = 1
local time_speed = tonumber(minetest.settings:get("time_speed"))
if time_speed and time_speed > 0 then if time_speed and time_speed > 0 then
time_scale = 72 / time_speed time_scale = 72 / time_speed
@ -117,6 +119,14 @@ biome_lib.perlin_humidity = PerlinNoise(humidity_seeddiff, humidity_octaves, hum
-- Local functions -- Local functions
function biome_lib.dbg(msg, level)
local l = tonumber(level) or 0
if biome_lib.debug_log_level >= l then
print("[Biome Lib] "..msg)
minetest.log("verbose", "[Biome Lib] "..msg)
end
end
local function get_biome_data(pos, perlin_fertile) local function get_biome_data(pos, perlin_fertile)
local fertility = perlin_fertile:get_2d({x=pos.x, y=pos.z}) local fertility = perlin_fertile:get_2d({x=pos.x, y=pos.z})
@ -187,17 +197,17 @@ function biome_lib:register_generate_plant(biomedef, nodes_or_function_or_model)
if type(nodes_or_function_or_model) == "string" if type(nodes_or_function_or_model) == "string"
and string.find(nodes_or_function_or_model, ":") and string.find(nodes_or_function_or_model, ":")
and not minetest.registered_nodes[nodes_or_function_or_model] then and not minetest.registered_nodes[nodes_or_function_or_model] then
biome_lib:dbg("Warning: Ignored registration for undefined spawn node: "..dump(nodes_or_function_or_model)) biome_lib.dbg("Warning: Ignored registration for undefined spawn node: "..dump(nodes_or_function_or_model), 2)
return return
end end
if type(nodes_or_function_or_model) == "string" if type(nodes_or_function_or_model) == "string"
and not string.find(nodes_or_function_or_model, ":") then and not string.find(nodes_or_function_or_model, ":") then
biome_lib:dbg("Warning: Registered function call using deprecated string method: "..dump(nodes_or_function_or_model)) biome_lib.dbg("Warning: Registered function call using deprecated string method: "..dump(nodes_or_function_or_model), 2)
end end
if biomedef.check_air == false then if biomedef.check_air == false then
biome_lib:dbg("Register no-air-check mapgen hook: "..dump(nodes_or_function_or_model)) biome_lib.dbg("Register no-air-check mapgen hook: "..dump(nodes_or_function_or_model), 3)
biome_lib.actionslist_no_aircheck[#biome_lib.actionslist_no_aircheck + 1] = { biomedef, nodes_or_function_or_model } biome_lib.actionslist_no_aircheck[#biome_lib.actionslist_no_aircheck + 1] = { biomedef, nodes_or_function_or_model }
local s = biomedef.surface local s = biomedef.surface
if type(s) == "string" then if type(s) == "string" then
@ -206,7 +216,7 @@ function biome_lib:register_generate_plant(biomedef, nodes_or_function_or_model)
biome_lib.surfaceslist_no_aircheck[#biome_lib.surfaceslist_no_aircheck + 1] = s biome_lib.surfaceslist_no_aircheck[#biome_lib.surfaceslist_no_aircheck + 1] = s
end end
else else
biome_lib:dbg("Warning: Ignored no-air-check registration for undefined surface node: "..dump(s)) biome_lib.dbg("Warning: Ignored no-air-check registration for undefined surface node: "..dump(s), 2)
end end
else else
for i = 1, #biomedef.surface do for i = 1, #biomedef.surface do
@ -216,12 +226,12 @@ function biome_lib:register_generate_plant(biomedef, nodes_or_function_or_model)
biome_lib.surfaceslist_no_aircheck[#biome_lib.surfaceslist_no_aircheck + 1] = s biome_lib.surfaceslist_no_aircheck[#biome_lib.surfaceslist_no_aircheck + 1] = s
end end
else else
biome_lib:dbg("Warning: Ignored no-air-check registration for undefined surface node: "..dump(s)) biome_lib.dbg("Warning: Ignored no-air-check registration for undefined surface node: "..dump(s), 2)
end end
end end
end end
else else
biome_lib:dbg("Register with-air-checking mapgen hook: "..dump(nodes_or_function_or_model)) biome_lib.dbg("Register with-air-checking mapgen hook: "..dump(nodes_or_function_or_model), 3)
biome_lib.actionslist_aircheck[#biome_lib.actionslist_aircheck + 1] = { biomedef, nodes_or_function_or_model } biome_lib.actionslist_aircheck[#biome_lib.actionslist_aircheck + 1] = { biomedef, nodes_or_function_or_model }
local s = biomedef.surface local s = biomedef.surface
if type(s) == "string" then if type(s) == "string" then
@ -230,7 +240,7 @@ function biome_lib:register_generate_plant(biomedef, nodes_or_function_or_model)
biome_lib.surfaceslist_aircheck[#biome_lib.surfaceslist_aircheck + 1] = s biome_lib.surfaceslist_aircheck[#biome_lib.surfaceslist_aircheck + 1] = s
end end
else else
biome_lib:dbg("Warning: Ignored with-air-checking registration for undefined surface node: "..dump(s)) biome_lib.dbg("Warning: Ignored with-air-checking registration for undefined surface node: "..dump(s), 2)
end end
else else
for i = 1, #biomedef.surface do for i = 1, #biomedef.surface do
@ -240,7 +250,7 @@ function biome_lib:register_generate_plant(biomedef, nodes_or_function_or_model)
biome_lib.surfaceslist_aircheck[#biome_lib.surfaceslist_aircheck + 1] = s biome_lib.surfaceslist_aircheck[#biome_lib.surfaceslist_aircheck + 1] = s
end end
else else
biome_lib:dbg("Warning: Ignored with-air-checking registration for undefined surface node: "..dump(s)) biome_lib.dbg("Warning: Ignored with-air-checking registration for undefined surface node: "..dump(s), 2)
end end
end end
end end
@ -328,7 +338,7 @@ local function populate_single_surface(biome, pos, perlin_fertile_area, checkair
return true return true
end end
function biome_lib:populate_surfaces(biome, nodes_or_function_or_model, snodes, checkair) function biome_lib.populate_surfaces(biome, nodes_or_function_or_model, snodes, checkair)
local items_added = 0 local items_added = 0
biome_lib:set_defaults(biome) biome_lib:set_defaults(biome)
@ -392,13 +402,16 @@ function biome_lib:populate_surfaces(biome, nodes_or_function_or_model, snodes,
if objtype == "table" then if objtype == "table" then
if nodes_or_function_or_model.axiom then if nodes_or_function_or_model.axiom then
biome_lib:generate_tree(p_top, nodes_or_function_or_model) biome_lib:generate_tree(p_top, nodes_or_function_or_model)
biome_lib.dbg("An L-tree was spawned at "..minetest.pos_to_string(p_top), 4)
spawned = true spawned = true
else else
local fdir = nil local fdir = nil
if biome.random_facedir then if biome.random_facedir then
fdir = math.random(biome.random_facedir[1], biome.random_facedir[2]) fdir = math.random(biome.random_facedir[1], biome.random_facedir[2])
end end
minetest.swap_node(p_top, { name = nodes_or_function_or_model[math.random(#nodes_or_function_or_model)], param2 = fdir }) local n=nodes_or_function_or_model[math.random(#nodes_or_function_or_model)]
minetest.swap_node(p_top, { name = n, param2 = fdir })
biome_lib.dbg("Node \""..n.."\" was randomly picked from a list and placed at "..minetest.pos_to_string(p_top), 4)
spawned = true spawned = true
end end
elseif objtype == "string" and elseif objtype == "string" and
@ -408,15 +421,18 @@ function biome_lib:populate_surfaces(biome, nodes_or_function_or_model, snodes,
fdir = math.random(biome.random_facedir[1], biome.random_facedir[2]) fdir = math.random(biome.random_facedir[1], biome.random_facedir[2])
end end
minetest.swap_node(p_top, { name = nodes_or_function_or_model, param2 = fdir }) minetest.swap_node(p_top, { name = nodes_or_function_or_model, param2 = fdir })
biome_lib.dbg("Node \""..nodes_or_function_or_model.."\" was placed at "..minetest.pos_to_string(p_top), 4)
spawned = true spawned = true
elseif objtype == "function" then elseif objtype == "function" then
nodes_or_function_or_model(pos) nodes_or_function_or_model(pos)
biome_lib.dbg("A function was run on surface node at "..minetest.pos_to_string(pos), 4)
spawned = true spawned = true
elseif objtype == "string" and pcall(loadstring(("return %s(...)"): elseif objtype == "string" and pcall(loadstring(("return %s(...)"):
format(nodes_or_function_or_model)),pos) then format(nodes_or_function_or_model)),pos) then
spawned = true spawned = true
biome_lib.dbg("An obsolete string-specified function was run on surface node at "..minetest.pos_to_string(p_top), 4)
else else
biome_lib:dbg("Warning: Ignored invalid definition for object "..dump(nodes_or_function_or_model).." that was pointed at {"..dump(pos).."}") biome_lib.dbg("Warning: Ignored invalid definition for object "..dump(nodes_or_function_or_model).." that was pointed at {"..dump(pos).."}", 2)
end end
else else
tries = tries + 1 tries = tries + 1
@ -427,134 +443,196 @@ function biome_lib:populate_surfaces(biome, nodes_or_function_or_model, snodes,
return items_added return items_added
end end
-- Primary mapgen spawner, for mods that can work with air checking enabled on -- Primary log read-out/mapgen spawner
-- a surface during the initial map read stage.
function biome_lib:generate_block_with_air_checking() local function confirm_block_surroundings(p)
if not biome_lib.blocklist_aircheck[1] then local n=minetest.get_node_or_nil(p)
return if not n or n.name == "ignore" then return false end
end
local minp = biome_lib.blocklist_aircheck[1][1] for x = -32,32,64 do -- step of 64 causes it to only check the 8 corner blocks
local maxp = biome_lib.blocklist_aircheck[1][2] for y = -32,32,64 do
for z = -32,32,64 do
-- use the block hash as a unique key into the surface nodes local n=minetest.get_node_or_nil({x=p.x + x, y=p.y + y, z=p.z + z})
-- tables, so that we can write the tables thread-safely. if not n or n.name == "ignore" then return false end
local blockhash = minetest.hash_node_position(minp)
if not biome_lib.surface_nodes_aircheck.blockhash then -- read it into the block cache
biome_lib.surface_nodes_aircheck.blockhash =
minetest.find_nodes_in_area_under_air(minp, maxp, biome_lib.surfaceslist_aircheck)
biome_lib.actioncount_aircheck.blockhash = 1
if #biome_lib.surface_nodes_aircheck.blockhash > 0 then
biome_lib:dbg("Mapblock at "..minetest.pos_to_string(minp).." added, with "..#biome_lib.surface_nodes_aircheck.blockhash.." surface nodes detected.")
end
elseif not minetest.get_node_or_nil(minp) then
minetest.load_area(minp)
else
if biome_lib.actionslist_aircheck[biome_lib.actioncount_aircheck.blockhash] then
-- [1] is biome, [2] is node/function/model
local added = biome_lib:populate_surfaces(
biome_lib.actionslist_aircheck[biome_lib.actioncount_aircheck.blockhash][1],
biome_lib.actionslist_aircheck[biome_lib.actioncount_aircheck.blockhash][2],
biome_lib.surface_nodes_aircheck.blockhash, true)
if added > 0 then
biome_lib:dbg("Ran biome_lib:populate_surfaces for block at "..minetest.pos_to_string(minp)..
". Entry #"..biome_lib.actioncount_aircheck.blockhash.." added "..added.." items.")
end end
biome_lib.actioncount_aircheck.blockhash = biome_lib.actioncount_aircheck.blockhash + 1
else
table.remove(biome_lib.blocklist_aircheck, 1)
biome_lib.surface_nodes_aircheck.blockhash = nil
biome_lib.actioncount_aircheck.blockhash = nil
end end
end end
return true
end end
-- Secondary mapgen spawner, for mods that require disabling of function biome_lib.generate_block(shutting_down)
-- checking for air during the initial map read stage.
function biome_lib:generate_block_no_aircheck() if shutting_down then
if not biome_lib.blocklist_no_aircheck[1] then if #biome_lib.block_recheck_list > 0 then
return for i = 1, #biome_lib.block_recheck_list do
biome_lib.block_log[#biome_lib.block_log + 1] = biome_lib.block_recheck_list[i]
end
biome_lib.block_recheck_list = {}
end
biome_lib.run_block_recheck_list = false
else
if biome_lib.run_block_recheck_list
and not biome_lib.block_recheck_list[1] then
biome_lib.run_block_recheck_list = false
end
end end
local minp = biome_lib.blocklist_no_aircheck[1][1] local blocklog = biome_lib.run_block_recheck_list
local maxp = biome_lib.blocklist_no_aircheck[1][2] and biome_lib.block_recheck_list
or biome_lib.block_log
local blockhash = minetest.hash_node_position(minp) if not blocklog[1] then return end
if not biome_lib.surface_nodes_no_aircheck.blockhash then local minp = blocklog[1][1]
biome_lib.surface_nodes_no_aircheck.blockhash = local maxp = blocklog[1][2]
minetest.find_nodes_in_area(minp, maxp, biome_lib.surfaceslist_no_aircheck) local airflag = blocklog[1][3]
biome_lib.actioncount_no_aircheck.blockhash = 1 local pos_hash = minetest.hash_node_position(minp)
elseif not minetest.get_node_or_nil(minp) then
if not biome_lib.pos_hash then -- we need to read the maplock and get the surfaces list
local now = minetest.get_us_time()
biome_lib.pos_hash = {}
minetest.load_area(minp) minetest.load_area(minp)
else if not confirm_block_surroundings(minp)
if biome_lib.actionslist_no_aircheck[biome_lib.actioncount_no_aircheck.blockhash] then and not shutting_down
biome_lib:populate_surfaces( and (blocklog[1][4] + biome_lib.block_timeout) > now then -- if any neighbors appear not to be loaded and the block hasn't expired yet, defer it
biome_lib.actionslist_no_aircheck[biome_lib.actioncount_no_aircheck.blockhash][1],
biome_lib.actionslist_no_aircheck[biome_lib.actioncount_no_aircheck.blockhash][2], if biome_lib.run_block_recheck_list then
biome_lib.surface_nodes_no_aircheck.blockhash, false) biome_lib.block_log[#biome_lib.block_log + 1] = table.copy(biome_lib.block_recheck_list[1])
biome_lib.actioncount_no_aircheck.blockhash = biome_lib.actioncount_no_aircheck.blockhash + 1 table.remove(biome_lib.block_recheck_list, 1)
else
biome_lib.block_recheck_list[#biome_lib.block_recheck_list + 1] = table.copy(biome_lib.block_log[1])
table.remove(biome_lib.block_log, 1)
end
biome_lib.pos_hash = nil
biome_lib.dbg("Mapblock at "..minetest.pos_to_string(minp)..
" had a neighbor not fully emerged, skipped it for now.", 4)
return
else else
table.remove(biome_lib.blocklist_no_aircheck, 1) biome_lib.pos_hash.surface_node_list = airflag
biome_lib.surface_nodes_no_aircheck.blockhash = nil and minetest.find_nodes_in_area_under_air(minp, maxp, biome_lib.surfaceslist_aircheck)
biome_lib.actioncount_no_aircheck.blockhash = nil or minetest.find_nodes_in_area(minp, maxp, biome_lib.surfaceslist_no_aircheck)
biome_lib.pos_hash.action_index = 1
if #biome_lib.pos_hash.surface_node_list > 0 then
biome_lib.dbg("Mapblock at "..minetest.pos_to_string(minp)..
" has "..#biome_lib.pos_hash.surface_node_list..
" surface nodes to work on (airflag="..dump(airflag)..")", 4)
end
end
elseif not (airflag and biome_lib.actionslist_aircheck[biome_lib.pos_hash.action_index])
and not (not airflag and biome_lib.actionslist_no_aircheck[biome_lib.pos_hash.action_index]) then
-- the block is finished, remove it
if #biome_lib.pos_hash.surface_node_list > 0 then
biome_lib.dbg("Deleted mapblock "..minetest.pos_to_string(minp).." from the block log", 4)
end
table.remove(blocklog, 1)
biome_lib.pos_hash = nil
else
-- below, [1] is biome, [2] is the thing to be added
local added = 0
if airflag then
if biome_lib.actionslist_aircheck[biome_lib.pos_hash.action_index] then
added = biome_lib.populate_surfaces(
biome_lib.actionslist_aircheck[biome_lib.pos_hash.action_index][1],
biome_lib.actionslist_aircheck[biome_lib.pos_hash.action_index][2],
biome_lib.pos_hash.surface_node_list, true)
biome_lib.pos_hash.action_index = biome_lib.pos_hash.action_index + 1
end
else
if biome_lib.actionslist_no_aircheck[biome_lib.pos_hash.action_index] then
added = biome_lib.populate_surfaces(
biome_lib.actionslist_no_aircheck[biome_lib.pos_hash.action_index][1],
biome_lib.actionslist_no_aircheck[biome_lib.pos_hash.action_index][2],
biome_lib.pos_hash.surface_node_list, false)
biome_lib.pos_hash.action_index = biome_lib.pos_hash.action_index + 1
end
end
if added > 0 then
biome_lib.dbg("biome_lib.populate_surfaces ran on mapblock at "..
minetest.pos_to_string(minp)..". Entry #"..
(biome_lib.pos_hash.action_index-1).." added "..added.." items.", 4)
end end
end end
end end
-- "Play" them back, populating them with new stuff in the process -- "Play" them back, populating them with new stuff in the process
local step_duration = tonumber(minetest.settings:get("dedicated_server_step"))
minetest.register_globalstep(function(dtime) minetest.register_globalstep(function(dtime)
if dtime >= step_duration + 0.1 -- don't attempt to populate if lag is already too high if not biome_lib.block_log[1] then return end -- the block log is empty
or math.random(100) > biome_lib.queue_run_ratio
or (#biome_lib.blocklist_aircheck == 0 and #biome_lib.blocklist_no_aircheck == 0) then
return
end
biome_lib.globalstep_start_time = minetest.get_us_time() if math.random(100) > biome_lib.queue_run_ratio then return end
biome_lib.globalstep_runtime = 0 for s = 1, biome_lib.entries_per_step do
while (#biome_lib.blocklist_aircheck > 0 or #biome_lib.blocklist_no_aircheck > 0) biome_lib.generate_block()
and biome_lib.globalstep_runtime < 200000 do -- 0.2 seconds, in uS.
if #biome_lib.blocklist_aircheck > 0 then
biome_lib:generate_block_with_air_checking()
end
if #biome_lib.blocklist_no_aircheck > 0 then
biome_lib:generate_block_no_aircheck()
end
biome_lib.globalstep_runtime = minetest.get_us_time() - biome_lib.globalstep_start_time
end end
end) end)
-- Periodically wake-up the queue to give old blocks a chance to time-out
-- if the player isn't currently exploring (i.e. they're just playing in one area)
function biome_lib.wake_up_queue()
if #biome_lib.block_recheck_list > 1
and #biome_lib.block_log == 0 then
biome_lib.block_log[#biome_lib.block_log + 1] =
table.copy(biome_lib.block_recheck_list[#biome_lib.block_recheck_list])
biome_lib.block_recheck_list[#biome_lib.block_recheck_list] = nil
biome_lib.run_block_recheck_list = true
biome_lib.dbg("Woke-up the map queue to give old blocks a chance to time-out.", 3)
end
minetest.after(biome_lib.block_queue_wakeup_time, biome_lib.wake_up_queue)
end
biome_lib.wake_up_queue()
-- Play out the entire log all at once on shutdown -- Play out the entire log all at once on shutdown
-- to prevent unpopulated map areas -- to prevent unpopulated map areas
local function format_time(t)
if t > 59999999 then
return os.date("!%M minutes and %S seconds", math.ceil(t/1000000))
else
return os.date("!%S seconds", math.ceil(t/1000000))
end
end
function biome_lib.check_remaining_time()
if minetest.get_us_time() > (biome_lib.shutdown_last_timestamp + 10000000) then -- report progress every 10s
biome_lib.shutdown_last_timestamp = minetest.get_us_time()
local entries_remaining = #biome_lib.block_log + #biome_lib.block_recheck_list
local total_purged = biome_lib.starting_count - entries_remaining
local elapsed_time = biome_lib.shutdown_last_timestamp - biome_lib.shutdown_start_time
biome_lib.dbg(string.format("%i entries, approximately %s remaining.",
entries_remaining, format_time(elapsed_time/total_purged * entries_remaining)))
end
end
minetest.register_on_shutdown(function() minetest.register_on_shutdown(function()
if #biome_lib.blocklist_aircheck == 0 then biome_lib.shutdown_start_time = minetest.get_us_time()
biome_lib.shutdown_last_timestamp = minetest.get_us_time()+1
biome_lib.starting_count = #biome_lib.block_log + #biome_lib.block_recheck_list
if biome_lib.starting_count == 0 then
return return
end end
print("[biome_lib] Stand by, playing out the rest of the aircheck mapblock log") biome_lib.dbg("Stand by, purging the mapblock log "..
print("(there are "..#biome_lib.blocklist_aircheck.." entries)...") "(there are "..(#biome_lib.block_log + #biome_lib.block_recheck_list).." entries) ...", 0)
while #biome_lib.blocklist_aircheck > 0 do
biome_lib:generate_block_with_air_checking(0.1)
end
end)
minetest.register_on_shutdown(function() while #biome_lib.block_log > 0 do
if #biome_lib.blocklist_aircheck == 0 then biome_lib.generate_block(true)
return biome_lib.check_remaining_time()
end end
print("[biome_lib] Stand by, playing out the rest of the no-aircheck mapblock log") if #biome_lib.block_recheck_list > 0 then
print("(there are "..#biome_lib.blocklist_no_aircheck.." entries)...") biome_lib.block_log = table.copy(biome_lib.block_recheck_list)
while #biome_lib.blocklist_no_aircheck > 0 do while #biome_lib.block_log > 0 do
biome_lib:generate_block_no_aircheck(0.1) biome_lib.generate_block(true)
biome_lib.check_remaining_time()
end
end end
biome_lib.dbg("Log purge completed after "..
format_time(minetest.get_us_time() - biome_lib.shutdown_start_time)..".", 0)
end) end)
-- The spawning ABM -- The spawning ABM
@ -747,29 +825,34 @@ function biome_lib:get_nodedef_field(nodename, fieldname)
return minetest.registered_nodes[nodename][fieldname] return minetest.registered_nodes[nodename][fieldname]
end end
if DEBUG then if biome_lib.debug_log_level >= 3 then
biome_lib.last_count_air = 0 biome_lib.last_count = 0
biome_lib.last_count_no_air = 0
function biome_lib.show_pending_block_counts() function biome_lib.show_pending_block_count()
if biome_lib.last_count_air ~= #biome_lib.blocklist_aircheck if biome_lib.last_count ~= #biome_lib.block_log then
or biome_lib.last_count_no_air ~= #biome_lib.blocklist_no_aircheck then biome_lib.dbg(string.format("Pending block counts - ready to process: %-8icurrently deferred: %i",
biome_lib:dbg(string.format("Pending block counts, air: %-7i no-air: %i", #biome_lib.block_log, #biome_lib.block_recheck_list), 3)
#biome_lib.blocklist_aircheck, #biome_lib.blocklist_no_aircheck)) biome_lib.last_count = #biome_lib.block_log
biome_lib.queue_idle_flag = false
biome_lib.last_count_air = #biome_lib.blocklist_aircheck elseif not biome_lib.queue_idle_flag then
biome_lib.last_count_no_air = #biome_lib.blocklist_no_aircheck if #biome_lib.block_recheck_list > 0 then
biome_lib.dbg("Mapblock queue only contains blocks that can't yet be processed.", 3)
biome_lib.dbg("Idling the queue until new blocks arrive or the next wake-up call occurs.", 3)
else
biome_lib.dbg("Mapblock queue has run dry.", 3)
biome_lib.dbg("Idling the queue until new blocks arrive.", 3)
end
biome_lib.queue_idle_flag = true
end end
minetest.after(1, biome_lib.show_pending_block_counts) minetest.after(1, biome_lib.show_pending_block_count)
end end
biome_lib.show_pending_block_counts() biome_lib.show_pending_block_count()
minetest.after(0, function()
print("Registered a total of "..(#biome_lib.surfaceslist_aircheck)+(#biome_lib.surfaceslist_no_aircheck).." surface types to be evaluated, spread")
print("across "..#biome_lib.actionslist_aircheck.." actions with air-checking and "..#biome_lib.actionslist_no_aircheck.." actions without.")
end)
end end
print("[Biome Lib] Loaded") minetest.after(0, function()
biome_lib.dbg("Registered a total of "..(#biome_lib.surfaceslist_aircheck)+(#biome_lib.surfaceslist_no_aircheck).." surface types to be evaluated, spread", 0)
biome_lib.dbg("across "..#biome_lib.actionslist_aircheck.." actions with air-checking and "..#biome_lib.actionslist_no_aircheck.." actions without.", 0)
end)
biome_lib.dbg("[Biome Lib] Loaded", 0)

View file

@ -56,6 +56,7 @@ end
-- split into individual mapblocks to reduce lag -- split into individual mapblocks to reduce lag
minetest.register_on_generated(function(minp, maxp, blockseed) minetest.register_on_generated(function(minp, maxp, blockseed)
local timestamp = minetest.get_us_time()
for x = 0, 4 do for x = 0, 4 do
local minx = minp.x + x*16 local minx = minp.x + x*16
for y = 0, 4 do for y = 0, 4 do
@ -65,10 +66,10 @@ minetest.register_on_generated(function(minp, maxp, blockseed)
local bmin = {x=minx, y=miny, z=minz} local bmin = {x=minx, y=miny, z=minz}
local bmax = {x=minx + 15, y=miny + 15, z=minz + 15} local bmax = {x=minx + 15, y=miny + 15, z=minz + 15}
biome_lib.block_log[#biome_lib.block_log + 1] = { bmin, bmax, true, timestamp }
biome_lib.blocklist_aircheck[#biome_lib.blocklist_aircheck + 1] = { bmin, bmax } biome_lib.block_log[#biome_lib.block_log + 1] = { bmin, bmax, false, timestamp }
biome_lib.blocklist_no_aircheck[#biome_lib.blocklist_no_aircheck + 1] = { bmin, bmax }
end end
end end
end end
biome_lib.run_block_recheck_list = true
end) end)

View file

@ -639,3 +639,25 @@ minetest.register_craft({
{"group:food_skillet", "farming:skillet"} {"group:food_skillet", "farming:skillet"}
} }
}) })
-- Mochi
minetest.register_craftitem("farming:mochi", {
description = S("Mochi"),
inventory_image = "farming_mochi.png",
on_use = minetest.item_eat(3),
groups = {flammable = 2},
})
minetest.register_craft({
type = "shapeless",
output = "farming:mochi",
recipe = {
"group:food_mortar_pestle", "group:food_rice", "group:food_rice",
"group:food_sugar", "bucket:bucket_river_water"
},
replacements = {
{"group:food_mortar_pestle", "farming:mortar_pestle"},
{"bucket:bucket_river_water", "bucket:bucket_empty"}
}
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

View file

@ -138,7 +138,7 @@ mobs:register_arrow("mobs_animal:egg_entity", {
end, end,
hit_mob = function(self, player) hit_mob = function(self, player)
player:punch(minetest.get_player_by_name(self.playername) or self.object, 1.0, { player:punch(self.object, 1.0, {
full_punch_interval = 1.0, full_punch_interval = 1.0,
damage_groups = {fleshy = 1}, damage_groups = {fleshy = 1},
}, nil) }, nil)
@ -213,6 +213,7 @@ local mobs_shoot_egg = function (item, player, pointed_thing)
ent.velocity = egg_VELOCITY -- needed for api internal timing ent.velocity = egg_VELOCITY -- needed for api internal timing
ent.switch = 1 -- needed so that egg doesn't despawn straight away ent.switch = 1 -- needed so that egg doesn't despawn straight away
ent._is_arrow = true -- tell advanced mob protection this is an arrow
obj:setvelocity({ obj:setvelocity({
x = dir.x * egg_VELOCITY, x = dir.x * egg_VELOCITY,

View file

@ -8,7 +8,7 @@ local use_cmi = minetest.global_exists("cmi")
mobs = { mobs = {
mod = "redo", mod = "redo",
version = "20210405", version = "20210407",
intllib = S, intllib = S,
invis = minetest.global_exists("invisibility") and invisibility or {} invis = minetest.global_exists("invisibility") and invisibility or {}
} }
@ -4499,8 +4499,9 @@ function mobs:capture_mob(self, clicker, chance_hand, chance_net,
return false return false
end end
-- cannot pick up if not owner -- cannot pick up if not owner (unless player has protection_bypass priv)
if self.owner ~= name and force_take == false then if not minetest.check_player_privs(name, "protection_bypass")
and self.owner ~= name and force_take == false then
minetest.chat_send_player(name, S("@1 is owner!", self.owner)) minetest.chat_send_player(name, S("@1 is owner!", self.owner))
@ -4729,12 +4730,12 @@ function mobs:feed_tame(self, clicker, feed_count, breed, tame)
end end
local item = clicker:get_wielded_item() local item = clicker:get_wielded_item()
local name = clicker:get_player_name()
-- if mob has been tamed you can name it with a nametag -- if mob has been tamed you can name it with a nametag
if item:get_name() == "mobs:nametag" if item:get_name() == "mobs:nametag"
and clicker:get_player_name() == self.owner then and (name == self.owner
or minetest.check_player_privs(name, "protection_bypass")) then
local name = clicker:get_player_name()
-- store mob and nametag stack in external variables -- store mob and nametag stack in external variables
mob_obj[name] = self mob_obj[name] = self

View file

@ -29,7 +29,10 @@ local dropbox = gen_def({
return 0 return 0
end end
return stack_count return stack_count
end end,
allow_metadata_inventory_take = actions.get_allow_metadata_inventory_take({
"dropbox", check_privs = actions.has_locked_chest_privilege
}),
}) })

View file

@ -64,7 +64,7 @@ for i in ipairs(moretrees.treelist) do
grow_function = "moretrees.grow_"..treename grow_function = "moretrees.grow_"..treename
end end
biome_lib:dbg(dump(moretrees[tree_biome].surface)) biome_lib.dbg(dump(moretrees[tree_biome].surface), 4)
biome_lib:grow_plants({ biome_lib:grow_plants({
grow_delay = moretrees.sapling_interval, grow_delay = moretrees.sapling_interval,

View file

@ -7,6 +7,8 @@ local mname = "cavestuff"
-- support for i18n -- support for i18n
local S = minetest.get_translator("cavestuff") local S = minetest.get_translator("cavestuff")
cavestuff = {}
dofile(minetest.get_modpath("cavestuff").."/nodes.lua") dofile(minetest.get_modpath("cavestuff").."/nodes.lua")
dofile(minetest.get_modpath("cavestuff").."/mapgen.lua") dofile(minetest.get_modpath("cavestuff").."/mapgen.lua")

View file

@ -1,52 +1,39 @@
--Map Generation Stuff --Map Generation Stuff
minetest.register_on_generated(function(minp, maxp, seed) biome_lib:register_generate_plant(
if maxp.y >= 2 and minp.y <= 0 then {
-- Generate pebbles surface = {
local perlin1 = minetest.get_perlin(329, 3, 0.6, 100) "default:dirt_with_grass",
-- Assume X and Z lengths are equal "default:gravel",
local divlen = 16 "default:stone",
local divs = (maxp.x-minp.x)/divlen+1; "default:permafrost_with_stones"
for divx=0,divs-1 do },
for divz=0,divs-1 do max_count = 50,
local x0 = minp.x + math.floor((divx+0)*divlen) rarity = 0,
local z0 = minp.z + math.floor((divz+0)*divlen) plantlife_limit = -1,
local x1 = minp.x + math.floor((divx+1)*divlen) check_air = true,
local z1 = minp.z + math.floor((divz+1)*divlen) random_facedir = {0, 3}
-- Determine pebble amount from perlin noise },
local pebble_amount = math.floor(perlin1:get2d({x=x0, y=z0}) ^ 2 * 2) {
-- Find random positions for pebbles based on this random "cavestuff:pebble_1",
local pr = PseudoRandom(seed+1) "cavestuff:pebble_2"
for i=0,pebble_amount do }
local x = pr:next(x0, x1) )
local z = pr:next(z0, z1)
-- Find ground level (0...15)
local ground_y = nil
for y=30,0,-1 do
if minetest.get_node({x=x,y=y,z=z}).name ~= "air" then
ground_y = y
break
end
end
if ground_y then biome_lib:register_generate_plant(
local p = {x=x,y=ground_y+1,z=z} {
local nn = minetest.get_node(p).name surface = {
-- Check if the node can be replaced "default:desert_sand",
if minetest.registered_nodes[nn] and "default:desert_stone"
minetest.registered_nodes[nn].buildable_to then },
nn = minetest.get_node({x=x,y=ground_y,z=z}).name max_count = 50,
-- If desert sand, add dry shrub rarity = 0,
if nn == "default:dirt_with_grass" then plantlife_limit = -1,
minetest.swap_node(p,{name="cavestuff:pebble_"..pr:next(1,2), param2=math.random(0,3)}) check_air = true,
elseif nn == "default:desert_sand" then random_facedir = {0, 3}
minetest.swap_node(p,{name="cavestuff:desert_pebble_"..pr:next(1,2), param2=math.random(0,3)}) },
end {
end "cavestuff:desert_pebble_1",
end "cavestuff:desert_pebble_2"
}
end )
end
end
end
end)

View file

@ -1,2 +1,2 @@
name = cavestuff name = cavestuff
depends = default depends = default,biome_lib

View file

@ -7,26 +7,24 @@
-- Looked at code from: default -- Looked at code from: default
----------------------------------------------------------------------------------------------- -----------------------------------------------------------------------------------------------
abstract_dryplants.grow_grass = function(pos) biome_lib:register_generate_plant(
local right_here = {x=pos.x, y=pos.y+1, z=pos.z} {
local grass_size = math.random(1,5) surface = {
if minetest.get_node(right_here).name == "air" -- instead of check_air = true, "default:dirt_with_grass",
or minetest.get_node(right_here).name == "default:junglegrass" then "stoneage:grass_with_silex",
minetest.swap_node(right_here, {name="default:grass_"..grass_size}) "sumpf:peat",
end "sumpf:sumpf"
end },
max_count = TALL_GRASS_PER_MAPBLOCK,
biome_lib:register_generate_plant({ rarity = 101 - TALL_GRASS_RARITY,
surface = { min_elevation = 1, -- above sea level
"default:dirt_with_grass", plantlife_limit = -0.9,
"stoneage:grass_with_silex", check_air = true,
"sumpf:peat",
"sumpf:sumpf"
}, },
max_count = TALL_GRASS_PER_MAPBLOCK, { "default:grass_1",
rarity = 101 - TALL_GRASS_RARITY, "default:grass_2",
min_elevation = 1, -- above sea level "default:grass_3",
plantlife_limit = -0.9, "default:grass_4",
}, "default:grass_5"
abstract_dryplants.grow_grass }
) )

View file

@ -20,6 +20,7 @@ https://forum.minetest.net/viewtopic.php?t=10090&p=153667
- 1.2 - Added POVA support, tweaked code slightly - 1.2 - Added POVA support, tweaked code slightly
- 1.3 - Add setting under Advanced to enable older sneak glitch movement - 1.3 - Add setting under Advanced to enable older sneak glitch movement
- 1.4 - Add minetest 5.0 check for knockback y_offset - 1.4 - Add minetest 5.0 check for knockback y_offset
- 1.5 - Use Minetext 5.x functions for proper player knockback
API: API:

View file

@ -49,7 +49,7 @@ minetest.register_globalstep(function(dtime)
time = 0 time = 0
-- define locals outside loop -- define locals outside loop
local name, pos, ndef, def, nslow, nfast local name, pos, ndef, def, nslow, nfast, prop
-- get list of players -- get list of players
local players = minetest.get_connected_players() local players = minetest.get_connected_players()
@ -66,8 +66,8 @@ if name and playerplus[name] then
pos = player:get_pos() pos = player:get_pos()
-- what is around me? -- what is around me?
pos.y = pos.y - 0.1 -- standing on playerplus[name].nod_stand = node_ok({
playerplus[name].nod_stand = node_ok(pos) x = pos.x, y = pos.y - 0.1, z = pos.z})
-- Does the node below me have an on_walk_over function set? -- Does the node below me have an on_walk_over function set?
ndef = minetest.registered_nodes[playerplus[name].nod_stand] ndef = minetest.registered_nodes[playerplus[name].nod_stand]
@ -75,13 +75,15 @@ if name and playerplus[name] then
ndef.on_walk_over(pos, ndef, player) ndef.on_walk_over(pos, ndef, player)
end end
pos.y = pos.y + 1.5 -- head level prop = player:get_properties()
playerplus[name].nod_head = node_ok(pos)
pos.y = pos.y - 1.2 -- feet level -- node at eye level
playerplus[name].nod_feet = node_ok(pos) playerplus[name].nod_head = node_ok({
x = pos.x, y = pos.y + prop.eye_height, z = pos.z})
pos.y = pos.y - 0.2 -- reset pos -- node at foot level
playerplus[name].nod_feet = node_ok({
x = pos.x, y = pos.y + 0.2, z = pos.z})
-- get player physics -- get player physics
def = player:get_physics_override() def = player:get_physics_override()
@ -247,29 +249,19 @@ minetest.register_privilege("no_knockback", {
-- is player knock-back effect enabled? -- is player knock-back effect enabled?
if minetest.settings:get_bool("player_knockback") == true then if minetest.settings:get_bool("player_knockback") == true then
minetest.register_entity("playerplus:temp", {
physical = true,
collisionbox = {-0.20, -1, -0.20, 0.20, 1, 0.20},
visual_size = {x = 0, y = 0},
visual = "sprite",
textures = {"trans.png"},
stepheight = 0.6,
on_step = function(self, dtime)
self.timer = (self.timer or 0) + dtime
if self.timer > 1 then
self.object:remove()
end
end
})
-- player knock-back function -- player knock-back function
local punchy = function( local punchy = function(
player, hitter, time_from_last_punch, tool_capabilities, dir, damage) player, hitter, time_from_last_punch, tool_capabilities, dir, damage)
if not dir then return end
-- check if player has 'no_knockback' privelage
local privs = minetest.get_player_privs(player:get_player_name())
if privs["no_knockback"] then
return
end
local damage = 0 local damage = 0
-- get tool damage -- get tool damage
@ -289,7 +281,6 @@ local punchy = function(
end end
damage = damage + (tool_capabilities.damage_groups[group] or 0) * tmp damage = damage + (tool_capabilities.damage_groups[group] or 0) * tmp
end end
-- check for knockback value -- check for knockback value
@ -302,48 +293,12 @@ local punchy = function(
-- print ("---", player:get_player_name(), damage) -- print ("---", player:get_player_name(), damage)
if not dir then return end -- knock back player
player:add_velocity({
-- check if player has 'no_knockback' privelage x = dir.x * (damage * 2),
local privs = minetest.get_player_privs(player:get_player_name()) y = -1,
z = dir.z * (damage * 2)
if privs["no_knockback"] then })
return
end
local y_offset = mt50 and -10 or 0
local vel = damage * 2
local pos = player:get_pos() ; pos.y = pos.y + 1.0
local ent = minetest.add_entity(pos, "playerplus:temp")
local obj = ent:get_luaentity()
local yaw = player:get_look_horizontal() or 0 ; yaw = -yaw * (180 / 3.14) -- pi
if obj and not player:get_attach() then
player:set_attach(ent, "", {x = 0, y = y_offset, z = 0}, {x = 0, y = yaw, z = 0})
ent:set_velocity({
x = dir.x * vel,
y = -1,
z = dir.z * vel
})
ent:set_acceleration({
x = dir.x * -1,
y = 0,
z = dir.z * -1
})
minetest.after(0.25, function()
player:set_detach()
ent:remove()
end)
else
ent:remove()
end
end end
minetest.register_on_punchplayer(punchy) minetest.register_on_punchplayer(punchy)

View file

@ -544,7 +544,7 @@ minetest.register_node("protector:chest", {
local inv = meta:get_inventory() local inv = meta:get_inventory()
meta:set_string("infotext", S("Protected Chest")) meta:set_string("infotext", S("Protected Chest"))
meta:set_string("name", "") meta:set_string("name", S("Protected Chest"))
inv:set_size("main", 8 * 4) inv:set_size("main", 8 * 4)
end, end,
@ -575,7 +575,8 @@ minetest.register_node("protector:chest", {
minetest.pos_to_string(pos)) minetest.pos_to_string(pos))
end, end,
on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) on_metadata_inventory_move = function(
pos, from_list, from_index, to_list, to_index, count, player)
minetest.log("action", player:get_player_name() .. minetest.log("action", player:get_player_name() ..
" moves stuff inside protected chest at " .. " moves stuff inside protected chest at " ..
@ -600,7 +601,8 @@ minetest.register_node("protector:chest", {
return stack:get_count() return stack:get_count()
end, end,
allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) allow_metadata_inventory_move = function(
pos, from_list, from_index, to_list, to_index, count, player)
if minetest.is_protected(pos, player:get_player_name()) then if minetest.is_protected(pos, player:get_player_name()) then
return 0 return 0
@ -645,15 +647,39 @@ minetest.register_node("protector:chest", {
on_blast = function() end, on_blast = function() end,
}) })
-- Container transfer helper
local to_from = function(src, dst)
local stack, item, leftover
local size = dst:get_size("main")
for i = 1, size do
stack = src:get_stack("main", i)
item = stack:get_name()
if item ~= "" and dst:room_for_item("main", item) then
leftover = dst:add_item("main", stack)
if leftover and not leftover:is_empty() then
src:set_stack("main", i, leftover)
else
src:set_stack("main", i, nil)
end
end
end
end
-- Protected Chest formspec buttons -- Protected Chest formspec buttons
minetest.register_on_player_receive_fields(function(player, formname, fields) minetest.register_on_player_receive_fields(function(player, formname, fields)
if string.sub(formname, 0, string.len("protector:chest_")) ~= "protector:chest_" then if string.sub(formname, 0, 16) ~= "protector:chest_" then
return return
end end
local pos_s = string.sub(formname,string.len("protector:chest_") + 1) local pos_s = string.sub(formname, 17)
local pos = minetest.string_to_pos(pos_s) local pos = minetest.string_to_pos(pos_s)
if minetest.is_protected(pos, player:get_player_name()) then if minetest.is_protected(pos, player:get_player_name()) then
@ -663,43 +689,16 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
local meta = minetest.get_meta(pos) ; if not meta then return end local meta = minetest.get_meta(pos) ; if not meta then return end
local chest_inv = meta:get_inventory() ; if not chest_inv then return end local chest_inv = meta:get_inventory() ; if not chest_inv then return end
local player_inv = player:get_inventory() local player_inv = player:get_inventory()
local leftover
-- copy contents of player inventory to chest
if fields.toup then if fields.toup then
-- copy contents of players inventory to chest to_from(player_inv, chest_inv)
for i, v in ipairs(player_inv:get_list("main") or {}) do
if chest_inv:room_for_item("main", v) then
leftover = chest_inv:add_item("main", v)
player_inv:remove_item("main", v)
if leftover
and not leftover:is_empty() then
player_inv:add_item("main", v)
end
end
end
-- copy contents of chest to player inventory
elseif fields.todn then elseif fields.todn then
-- copy contents of chest to players inventory to_from(chest_inv, player_inv)
for i, v in ipairs(chest_inv:get_list("main") or {}) do
if player_inv:room_for_item("main", v) then
leftover = player_inv:add_item("main", v)
chest_inv:remove_item("main", v)
if leftover
and not leftover:is_empty() then
chest_inv:add_item("main", v)
end
end
end
elseif fields.chestname then elseif fields.chestname then
@ -707,9 +706,9 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
if fields.chestname ~= "" then if fields.chestname ~= "" then
meta:set_string("name", fields.chestname) meta:set_string("name", fields.chestname)
meta:set_string("infotext", meta:set_string("infotext", fields.chestname)
S("Protected Chest (@1)", fields.chestname))
else else
meta:set_string("name", S("Protected Chest"))
meta:set_string("infotext", S("Protected Chest")) meta:set_string("infotext", S("Protected Chest"))
end end

View file

@ -14,6 +14,7 @@ read_globals = {
"ItemStack", "datastorage", "ItemStack", "datastorage",
"hb", "hb",
"doors",
} }
files["callbacks.lua"].ignore = { "player", "draw_lite_mode" } files["callbacks.lua"].ignore = { "player", "draw_lite_mode" }

View file

@ -19,6 +19,8 @@ minetest.register_on_joinplayer(function(player)
unified_inventory.active_search_direction[player_name] = "nochange" unified_inventory.active_search_direction[player_name] = "nochange"
unified_inventory.apply_filter(player, "", "nochange") unified_inventory.apply_filter(player, "", "nochange")
unified_inventory.current_searchbox[player_name] = "" 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.alternate[player_name] = 1
unified_inventory.current_item[player_name] = nil unified_inventory.current_item[player_name] = nil
unified_inventory.current_craft_direction[player_name] = "recipe" unified_inventory.current_craft_direction[player_name] = "recipe"
@ -69,6 +71,41 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
unified_inventory.current_searchbox[player_name] = fields.searchbox unified_inventory.current_searchbox[player_name] = fields.searchbox
end end
local clicked_category
for name, value in pairs(fields) do
local category_name = string.match(name, "^category_(.+)$")
if category_name then
clicked_category = category_name
break
end
end
if clicked_category
and clicked_category ~= unified_inventory.current_category[player_name] then
unified_inventory.current_category[player_name] = clicked_category
unified_inventory.apply_filter(player, unified_inventory.current_searchbox[player_name], "nochange")
unified_inventory.set_inventory_formspec(player,
unified_inventory.current_page[player_name])
end
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
for i, def in pairs(unified_inventory.buttons) do for i, def in pairs(unified_inventory.buttons) do
if fields[def.name] then if fields[def.name] then
def.action(player) def.action(player)
@ -126,6 +163,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
clicked_item = unified_inventory.demangle_for_formspec(mangled_item) clicked_item = unified_inventory.demangle_for_formspec(mangled_item)
if string.sub(clicked_item, 1, 6) == "group:" then if string.sub(clicked_item, 1, 6) == "group:" then
-- Change search filter to this group -- Change search filter to this group
unified_inventory.current_category[player_name] = "all"
apply_new_filter(player, clicked_item, new_dir) apply_new_filter(player, clicked_item, new_dir)
return return
end end

View file

@ -0,0 +1,149 @@
local S = minetest.get_translator("unified_inventory")
unified_inventory.registered_categories = {}
unified_inventory.registered_category_items = {}
unified_inventory.category_list = {}
local function char_to_sort_index(char_code)
if char_code <= 32 then
-- Command codes, no thanks
return 0
end
if char_code <= 64 then
-- Sorts numbers, and some punctuation, after letters
return char_code
end
if char_code >= 158 then
-- Out of sortable range
return 0
end
if char_code > 122 then
-- Avoids overlap with {, |, } and ~
return char_code - 58
end
if char_code > 96 then
-- Normalises lowercase with uppercase
return char_code - 96
end
return char_code - 64
end
local function string_to_sort_index(str)
local max_chars = 5
local power = 100
local index = 0
for i=1,math.min(#str, max_chars) do
index = index + (char_to_sort_index(string.byte(str, i))/(power^i))
end
return index
end
function update_category_list()
local category_list = {}
table.insert(category_list, {
name = "all",
label = S("All Items"),
symbol = "ui_category_all.png",
index = -2,
})
table.insert(category_list, {
name = "uncategorized",
label = S("Misc. Items"),
symbol = "ui_category_none.png",
index = -1,
})
for category, def in pairs(unified_inventory.registered_categories) do
table.insert(category_list, {
name = category,
label = def.label or category,
symbol = def.symbol,
index = def.index or -- sortby defined order
string_to_sort_index(category) -- or do a rudimentary alphabetical sort
})
end
table.sort(category_list, function (a,b)
return a.index < b.index
end)
unified_inventory.category_list = category_list
end
local function ensure_category_exists(category_name)
if not unified_inventory.registered_categories[category_name] then
unified_inventory.registered_categories[category_name] = {
symbol = "default:stick",
label = category_name
}
end
if not unified_inventory.registered_category_items[category_name] then
unified_inventory.registered_category_items[category_name] = {}
end
end
function unified_inventory.register_category(category_name, config)
ensure_category_exists(category_name)
if config and config.symbol then
unified_inventory.set_category_symbol(category_name, config.symbol)
end
if config and config.label then
unified_inventory.set_category_label(category_name, config.label)
end
if config and config.index then
unified_inventory.set_category_index(category_name, config.index)
end
if config and config.items then
unified_inventory.add_category_items(category_name, config.items)
end
update_category_list()
end
function unified_inventory.set_category_symbol(category_name, symbol)
ensure_category_exists(category_name)
unified_inventory.registered_categories[category_name].symbol = symbol
update_category_list()
end
function unified_inventory.set_category_label(category_name, label)
ensure_category_exists(category_name)
unified_inventory.registered_categories[category_name].label = label
update_category_list()
end
function unified_inventory.set_category_index(category_name, index)
ensure_category_exists(category_name)
unified_inventory.registered_categories[category_name].index = index
update_category_list()
end
function unified_inventory.add_category_item(category_name, item)
ensure_category_exists(category_name)
unified_inventory.registered_category_items[category_name][item] = true
end
function unified_inventory.add_category_items(category_name, items)
for _,item in ipairs(items) do
unified_inventory.add_category_item(category_name, item)
end
end
function unified_inventory.remove_category_item(category_name, item)
unified_inventory.registered_category_items[category_name][item] = nil
end
function unified_inventory.remove_category(category_name)
unified_inventory.registered_categories[category_name] = nil
unified_inventory.registered_category_items[category_name] = nil
update_category_list()
end
function unified_inventory.find_category(item)
-- Returns the first category the item exists in
-- Best for checking if an item has any category at all
for category, items in pairs(unified_inventory.registered_category_items) do
if items[item] then return category end
end
end
function unified_inventory.find_categories(item)
-- Returns all the categories the item exists in
-- Best for listing all categories
local categories = {}
for category, items in pairs(unified_inventory.registered_category_items) do
if items[item] then
table.insert(categories, category)
end
end
return categories
end

View file

@ -0,0 +1,704 @@
local S = minetest.get_translator("unified_inventory")
unified_inventory.register_category('plants', {
symbol = "flowers:tulip",
label = S("Plant Life")
})
unified_inventory.register_category('building', {
symbol = "default:brick",
label = S("Building Materials")
})
unified_inventory.register_category('tools', {
symbol = "default:pick_diamond",
label = S("Tools")
})
unified_inventory.register_category('minerals', {
symbol = "default:iron_lump",
label = S("Minerals and Metals")
})
unified_inventory.register_category('environment', {
symbol = "default:dirt_with_grass",
label = S("Environment and Worldgen")
})
unified_inventory.register_category('lighting', {
symbol = "default:torch",
label = S("Lighting")
})
if unified_inventory.automatic_categorization then
minetest.register_on_mods_loaded(function()
-- Add biome nodes to environment category
for _,def in pairs(minetest.registered_biomes) do
local env_nodes = {
def.node_riverbed, def.node_top, def.node_filler, def.node_dust,
}
for i,node in pairs(env_nodes) do
if node then
unified_inventory.add_category_item('environment', node)
end
end
end
-- Add minable ores to minerals and everything else (pockets of stone & sand variations) to environment
for _,item in pairs(minetest.registered_ores) do
if item.ore_type == "scatter" then
local drop = minetest.registered_nodes[item.ore].drop
if drop and drop ~= "" then
unified_inventory.add_category_item('minerals', item.ore)
unified_inventory.add_category_item('minerals', drop)
else
unified_inventory.add_category_item('environment', item.ore)
end
else
unified_inventory.add_category_item('environment', item.ore)
end
end
-- Add items by item definition
for name, def in pairs(minetest.registered_items) do
local group = def.groups or {}
if not group.not_in_creative_inventory then
if group.stair or
group.slab or
group.wall or
group.fence then
unified_inventory.add_category_item('building', name)
elseif group.flora or
group.flower or
group.seed or
group.leaves or
group.sapling or
group.tree then
unified_inventory.add_category_item('plants', name)
elseif def.type == 'tool' then
unified_inventory.add_category_item('tools', name)
elseif def.liquidtype == 'source' then
unified_inventory.add_category_item('environment', name)
elseif def.light_source and def.light_source > 0 then
unified_inventory.add_category_item('lighting', name)
elseif group.door or
minetest.global_exists("doors") and (
doors.registered_doors and doors.registered_doors[name..'_a'] or
doors.registered_trapdoors and doors.registered_trapdoors[name]
) then
unified_inventory.add_category_item('building', name)
end
end
end
end)
end
-- [[
unified_inventory.add_category_items('plants', {
"default:dry_grass_5",
"default:acacia_sapling",
"default:blueberry_bush_sapling",
"default:grass_2",
"default:pine_bush_stem",
"default:leaves",
"default:pine_needles",
"default:cactus",
"default:junglegrass",
"default:pine_sapling",
"default:sapling",
"default:bush_stem",
"default:dry_grass_2",
"default:fern_1",
"default:grass_3",
"default:marram_grass_1",
"default:pine_tree",
"default:dry_grass_3",
"default:dry_shrub",
"default:grass_4",
"default:marram_grass_2",
"default:jungleleaves",
"default:apple",
"default:tree",
"default:aspen_tree",
"default:bush_sapling",
"default:grass_5",
"default:blueberry_bush_leaves_with_berries",
"default:acacia_bush_sapling",
"default:grass_1",
"default:aspen_leaves",
"default:marram_grass_3",
"default:large_cactus_seedling",
"default:junglesapling",
"default:dry_grass_4",
"default:acacia_bush_stem",
"default:papyrus",
"default:pine_bush_needles",
"default:bush_leaves",
"default:fern_3",
"default:aspen_sapling",
"default:acacia_tree",
"default:apple_mark",
"default:acacia_leaves",
"default:jungletree",
"default:dry_grass_1",
"default:acacia_bush_leaves",
"default:emergent_jungle_sapling",
"default:fern_2",
"default:blueberries",
"default:sand_with_kelp",
"default:blueberry_bush_leaves",
"default:pine_bush_sapling",
"farming:cotton",
"farming:cotton_1",
"farming:cotton_2",
"farming:cotton_3",
"farming:cotton_4",
"farming:cotton_5",
"farming:cotton_6",
"farming:cotton_7",
"farming:cotton_8",
"farming:cotton_wild",
"farming:seed_cotton",
"farming:seed_wheat",
"farming:straw",
"farming:wheat",
"farming:wheat_1",
"farming:wheat_2",
"farming:wheat_3",
"farming:wheat_4",
"farming:wheat_5",
"farming:wheat_6",
"farming:wheat_7",
"farming:wheat_8",
"flowers:chrysanthemum_green",
"flowers:dandelion_white",
"flowers:dandelion_yellow",
"flowers:geranium",
"flowers:mushroom_brown",
"flowers:mushroom_red",
"flowers:rose",
"flowers:tulip",
"flowers:tulip_black",
"flowers:viola",
"flowers:waterlily",
"flowers:waterlily_waving",
})
unified_inventory.add_category_items('tools', {
"default:sword_diamond",
"default:axe_diamond",
"default:shovel_diamond",
"default:axe_steel",
"default:shovel_mese",
"default:sword_wood",
"default:pick_bronze",
"default:axe_stone",
"default:sword_stone",
"default:pick_stone",
"default:shovel_stone",
"default:sword_mese",
"default:shovel_bronze",
"default:sword_bronze",
"default:axe_bronze",
"default:shovel_steel",
"default:sword_steel",
"default:axe_mese",
"default:shovel_wood",
"default:pick_mese",
"default:axe_wood",
"default:pick_diamond",
"default:pick_wood",
"default:pick_steel",
"farming:hoe_bronze",
"farming:hoe_diamond",
"farming:hoe_mese",
"farming:hoe_steel",
"farming:hoe_stone",
"farming:hoe_wood",
"fire:flint_and_steel",
"map:mapping_kit",
"screwdriver:screwdriver",
"fireflies:bug_net",
"bucket:bucket_empty",
"binoculars:binoculars",
"default:skeleton_key",
})
unified_inventory.add_category_items('minerals', {
"default:stone_with_copper",
"default:stone_with_gold",
"default:stone_with_iron",
"default:copper_ingot",
"default:copper_lump",
"default:gold_lump",
"default:diamondblock",
"default:stone_with_diamond",
"default:stone_with_mese",
"default:steel_ingot",
"default:gold_ingot",
"default:iron_lump",
"default:tinblock",
"default:tin_lump",
"default:stone_with_tin",
"default:mese_crystal",
"default:diamond",
"default:bronze_ingot",
"default:mese",
"default:mese_crystal_fragment",
"default:copperblock",
"default:stone_with_coal",
"default:steelblock",
"default:tin_ingot",
"default:coalblock",
"default:coal_lump",
"default:bronzeblock",
"default:goldblock",
"stairs:slab_bronzeblock",
"stairs:slab_copperblock",
"stairs:slab_steelblock",
"stairs:slab_tinblock",
"stairs:stair_bronzeblock",
"stairs:stair_copperblock",
"stairs:stair_inner_bronzeblock",
"stairs:stair_inner_copperblock",
"stairs:stair_inner_steelblock",
"stairs:stair_inner_tinblock",
"stairs:stair_outer_bronzeblock",
"stairs:stair_outer_copperblock",
"stairs:stair_outer_steelblock",
"stairs:stair_outer_tinblock",
"stairs:stair_steelblock",
"stairs:stair_tinblock",
})
unified_inventory.add_category_items('building', {
"default:fence_rail_aspen_wood",
"default:fence_rail_acacia_wood",
"default:fence_junglewood",
"default:fence_rail_junglewood",
"default:fence_aspen_wood",
"default:fence_pine_wood",
"default:fence_rail_wood",
"default:fence_rail_pine_wood",
"default:fence_acacia_wood",
"default:junglewood",
"default:acacia_wood",
"default:aspen_wood",
"default:fence_wood",
"default:pine_wood",
"default:silver_sandstone",
"default:desert_sandstone",
"default:sandstone_block",
"default:desert_sandstone_brick",
"default:stone_block",
"default:stonebrick",
"default:obsidian_glass",
"default:desert_sandstone_block",
"default:silver_sandstone_brick",
"default:brick",
"default:obsidianbrick",
"default:sandstonebrick",
"default:sandstone",
"default:desert_stone_block",
"default:silver_sandstone_block",
"default:wood",
"default:obsidian_block",
"default:glass",
"default:clay_brick",
"default:desert_stonebrick",
"default:desert_cobble",
"default:cobble",
"default:mossycobble",
"doors:door_glass",
"doors:door_glass_a",
"doors:door_glass_b",
"doors:door_glass_c",
"doors:door_glass_d",
"doors:door_obsidian_glass",
"doors:door_obsidian_glass_a",
"doors:door_obsidian_glass_b",
"doors:door_obsidian_glass_c",
"doors:door_obsidian_glass_d",
"doors:door_steel",
"doors:door_steel_a",
"doors:door_steel_b",
"doors:door_steel_c",
"doors:door_steel_d",
"doors:door_wood",
"doors:door_wood_a",
"doors:door_wood_b",
"doors:door_wood_c",
"doors:door_wood_d",
"doors:gate_acacia_wood_closed",
"doors:gate_acacia_wood_open",
"doors:gate_aspen_wood_closed",
"doors:gate_aspen_wood_open",
"doors:gate_junglewood_closed",
"doors:gate_junglewood_open",
"doors:gate_pine_wood_closed",
"doors:gate_pine_wood_open",
"doors:gate_wood_closed",
"doors:gate_wood_open",
"doors:hidden",
"doors:trapdoor",
"doors:trapdoor_open",
"doors:trapdoor_steel",
"doors:trapdoor_steel_open",
"stairs:slab_bronzeblock",
"stairs:slab_copperblock",
"stairs:slab_steelblock",
"stairs:slab_tinblock",
"stairs:stair_bronzeblock",
"stairs:stair_copperblock",
"stairs:stair_inner_bronzeblock",
"stairs:stair_inner_copperblock",
"stairs:stair_inner_steelblock",
"stairs:stair_inner_tinblock",
"stairs:stair_outer_bronzeblock",
"stairs:stair_outer_copperblock",
"stairs:stair_outer_steelblock",
"stairs:stair_outer_tinblock",
"stairs:stair_steelblock",
"stairs:stair_tinblock",
"stairs:slab_acacia_wood",
"stairs:slab_aspen_wood",
"stairs:slab_brick",
"stairs:slab_cobble",
"stairs:slab_desert_cobble",
"stairs:slab_desert_sandstone",
"stairs:slab_desert_sandstone_block",
"stairs:slab_desert_sandstone_brick",
"stairs:slab_desert_stone",
"stairs:slab_desert_stone_block",
"stairs:slab_desert_stonebrick",
"stairs:slab_glass",
"stairs:slab_goldblock",
"stairs:slab_ice",
"stairs:slab_junglewood",
"stairs:slab_mossycobble",
"stairs:slab_obsidian",
"stairs:slab_obsidian_block",
"stairs:slab_obsidian_glass",
"stairs:slab_obsidianbrick",
"stairs:slab_pine_wood",
"stairs:slab_sandstone",
"stairs:slab_sandstone_block",
"stairs:slab_sandstonebrick",
"stairs:slab_silver_sandstone",
"stairs:slab_silver_sandstone_block",
"stairs:slab_silver_sandstone_brick",
"stairs:slab_snowblock",
"stairs:slab_stone",
"stairs:slab_stone_block",
"stairs:slab_stonebrick",
"stairs:slab_straw",
"stairs:slab_wood",
"stairs:stair_acacia_wood",
"stairs:stair_aspen_wood",
"stairs:stair_brick",
"stairs:stair_cobble",
"stairs:stair_desert_cobble",
"stairs:stair_desert_sandstone",
"stairs:stair_desert_sandstone_block",
"stairs:stair_desert_sandstone_brick",
"stairs:stair_desert_stone",
"stairs:stair_desert_stone_block",
"stairs:stair_desert_stonebrick",
"stairs:stair_glass",
"stairs:stair_goldblock",
"stairs:stair_ice",
"stairs:stair_inner_acacia_wood",
"stairs:stair_inner_aspen_wood",
"stairs:stair_inner_brick",
"stairs:stair_inner_cobble",
"stairs:stair_inner_desert_cobble",
"stairs:stair_inner_desert_sandstone",
"stairs:stair_inner_desert_sandstone_block",
"stairs:stair_inner_desert_sandstone_brick",
"stairs:stair_inner_desert_stone",
"stairs:stair_inner_desert_stone_block",
"stairs:stair_inner_desert_stonebrick",
"stairs:stair_inner_glass",
"stairs:stair_inner_goldblock",
"stairs:stair_inner_ice",
"stairs:stair_inner_junglewood",
"stairs:stair_inner_mossycobble",
"stairs:stair_inner_obsidian",
"stairs:stair_inner_obsidian_block",
"stairs:stair_inner_obsidian_glass",
"stairs:stair_inner_obsidianbrick",
"stairs:stair_inner_pine_wood",
"stairs:stair_inner_sandstone",
"stairs:stair_inner_sandstone_block",
"stairs:stair_inner_sandstonebrick",
"stairs:stair_inner_silver_sandstone",
"stairs:stair_inner_silver_sandstone_block",
"stairs:stair_inner_silver_sandstone_brick",
"stairs:stair_inner_snowblock",
"stairs:stair_inner_stone",
"stairs:stair_inner_stone_block",
"stairs:stair_inner_stonebrick",
"stairs:stair_inner_straw",
"stairs:stair_inner_wood",
"stairs:stair_junglewood",
"stairs:stair_mossycobble",
"stairs:stair_obsidian",
"stairs:stair_obsidian_block",
"stairs:stair_obsidian_glass",
"stairs:stair_obsidianbrick",
"stairs:stair_outer_acacia_wood",
"stairs:stair_outer_aspen_wood",
"stairs:stair_outer_brick",
"stairs:stair_outer_cobble",
"stairs:stair_outer_desert_cobble",
"stairs:stair_outer_desert_sandstone",
"stairs:stair_outer_desert_sandstone_block",
"stairs:stair_outer_desert_sandstone_brick",
"stairs:stair_outer_desert_stone",
"stairs:stair_outer_desert_stone_block",
"stairs:stair_outer_desert_stonebrick",
"stairs:stair_outer_glass",
"stairs:stair_outer_goldblock",
"stairs:stair_outer_ice",
"stairs:stair_outer_junglewood",
"stairs:stair_outer_mossycobble",
"stairs:stair_outer_obsidian",
"stairs:stair_outer_obsidian_block",
"stairs:stair_outer_obsidian_glass",
"stairs:stair_outer_obsidianbrick",
"stairs:stair_outer_pine_wood",
"stairs:stair_outer_sandstone",
"stairs:stair_outer_sandstone_block",
"stairs:stair_outer_sandstonebrick",
"stairs:stair_outer_silver_sandstone",
"stairs:stair_outer_silver_sandstone_block",
"stairs:stair_outer_silver_sandstone_brick",
"stairs:stair_outer_snowblock",
"stairs:stair_outer_stone",
"stairs:stair_outer_stone_block",
"stairs:stair_outer_stonebrick",
"stairs:stair_outer_straw",
"stairs:stair_outer_wood",
"stairs:stair_pine_wood",
"stairs:stair_sandstone",
"stairs:stair_sandstone_block",
"stairs:stair_sandstonebrick",
"stairs:stair_silver_sandstone",
"stairs:stair_silver_sandstone_block",
"stairs:stair_silver_sandstone_brick",
"stairs:stair_snowblock",
"stairs:stair_stone",
"stairs:stair_stone_block",
"stairs:stair_stonebrick",
"stairs:stair_straw",
"stairs:stair_wood",
"xpanes:bar",
"xpanes:bar_flat",
"xpanes:door_steel_bar",
"xpanes:door_steel_bar_a",
"xpanes:door_steel_bar_b",
"xpanes:door_steel_bar_c",
"xpanes:door_steel_bar_d",
"xpanes:obsidian_pane",
"xpanes:obsidian_pane_flat",
"xpanes:pane",
"xpanes:pane_flat",
"xpanes:trapdoor_steel_bar",
"xpanes:trapdoor_steel_bar_open",
"walls:cobble",
"walls:desertcobble",
"walls:mossycobble",
})
unified_inventory.add_category_items('environment', {
"air",
"default:cave_ice",
"default:dirt_with_rainforest_litter",
"default:gravel",
"default:dry_dirt_with_dry_grass",
"default:permafrost",
"default:desert_stone",
"default:ice",
"default:dry_dirt",
"default:obsidian",
"default:sand",
"default:river_water_source",
"default:dirt_with_snow",
"default:dirt_with_grass",
"default:water_flowing",
"default:dirt",
"default:desert_sand",
"default:permafrost_with_moss",
"default:dirt_with_coniferous_litter",
"default:water_source",
"default:dirt_with_dry_grass",
"default:river_water_flowing",
"default:stone",
"default:snow",
"default:lava_flowing",
"default:lava_source",
"default:permafrost_with_stones",
"default:dirt_with_grass_footsteps",
"default:silver_sand",
"default:snowblock",
"default:clay",
"farming:desert_sand_soil",
"farming:desert_sand_soil_wet",
"farming:dry_soil",
"farming:dry_soil_wet",
"farming:soil",
"farming:soil_wet",
})
unified_inventory.add_category_items('lighting', {
"default:mese_post_light_junglewood",
"default:torch_ceiling",
"default:meselamp",
"default:torch",
"default:mese_post_light_acacia_wood",
"default:mese_post_light",
"default:torch_wall",
"default:mese_post_light_pine_wood",
"default:mese_post_light_aspen_wood"
})
--]]
--[[ UNCATEGORISED
"farming:string",
"beds:bed_bottom",
"beds:bed_top",
"beds:fancy_bed_bottom",
"beds:fancy_bed_top",
"boats:boat",
"bones:bones",
"bucket:bucket_lava",
"bucket:bucket_river_water",
"bucket:bucket_water",
"butterflies:butterfly_red",
"butterflies:butterfly_violet",
"butterflies:butterfly_white",
"butterflies:hidden_butterfly_red",
"butterflies:hidden_butterfly_violet",
"butterflies:hidden_butterfly_white",
"carts:brakerail",
"carts:cart",
"carts:powerrail",
"carts:rail",
"default:book",
"default:book_written",
"default:bookshelf",
"default:chest",
"default:chest_locked",
"default:chest_locked_open",
"default:chest_open",
"default:clay_lump",
"default:cloud",
"default:coral_brown",
"default:coral_cyan",
"default:coral_green",
"default:coral_orange",
"default:coral_pink",
"default:coral_skeleton",
"default:flint",
"default:furnace",
"default:furnace_active",
"default:key",
"default:ladder_steel",
"default:ladder_wood",
"default:obsidian_shard",
"default:paper",
"default:sign_wall_steel",
"default:sign_wall_wood",
"default:stick",
"fire:basic_flame",
"fire:permanent_flame",
"fireflies:firefly",
"fireflies:firefly_bottle",
"fireflies:hidden_firefly",
"ignore",
"unknown",
"tnt:boom",
"tnt:gunpowder",
"tnt:gunpowder_burning",
"tnt:tnt",
"tnt:tnt_burning",
"tnt:tnt_stick",
"vessels:drinking_glass",
"vessels:glass_bottle",
"vessels:glass_fragments",
"vessels:shelf",
"vessels:steel_bottle",
"dye:black",
"dye:blue",
"dye:brown",
"dye:cyan",
"dye:dark_green",
"dye:dark_grey",
"dye:green",
"dye:grey",
"dye:magenta",
"dye:orange",
"dye:pink",
"dye:red",
"dye:violet",
"dye:white",
"dye:yellow",
"wool:black",
"wool:blue",
"wool:brown",
"wool:cyan",
"wool:dark_green",
"wool:dark_grey",
"wool:green",
"wool:grey",
"wool:magenta",
"wool:orange",
"wool:pink",
"wool:red",
"wool:violet",
"wool:white",
"wool:yellow",
"unified_inventory:bag_large",
"unified_inventory:bag_medium",
"unified_inventory:bag_small",
--]]
--[[ LIST UNCATEGORIZED AFTER LOAD
minetest.register_on_mods_loaded(function()
minetest.after(1, function ( )
local l = {}
for name,_ in pairs(minetest.registered_items) do
if not unified_inventory.find_category(name) then
-- minetest.log("error", minetest.serialize(minetest.registered_items[name]))
table.insert(l, name)
end
end
table.sort(l)
minetest.log(table.concat(l, '",'.."\n"..'"'))
end)
end)
--]]

View file

@ -101,3 +101,72 @@ Register a non-standard craft recipe:
-- ^ Same as `minetest.register_recipe` -- ^ Same as `minetest.register_recipe`
}) })
Categories
----------
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 = "mod_name:item_name" or "texture.png",
label = "Human Readable Label",
index = 5,
items = {
"mod_name:item_name",
"another_mod:different_item"
}
})
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("category_name", "mod_name:item_name" or "texture.png")
Add / override the human readable label for a category:
If unset this will default to the category name
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")

View file

@ -10,6 +10,8 @@ unified_inventory = {
alternate = {}, alternate = {},
current_page = {}, current_page = {},
current_searchbox = {}, current_searchbox = {},
current_category = {},
current_category_scroll = {},
current_index = {}, current_index = {},
current_item = {}, current_item = {},
current_craft_direction = {}, current_craft_direction = {},
@ -33,6 +35,9 @@ unified_inventory = {
-- "Lite" mode -- "Lite" mode
lite_mode = minetest.settings:get_bool("unified_inventory_lite"), lite_mode = minetest.settings:get_bool("unified_inventory_lite"),
-- Items automatically added to categories based on item definitions
automatic_categorization = (minetest.settings:get_bool("unified_inventory_automatic_categorization") ~= false),
-- Trash enabled -- Trash enabled
trash_enabled = (minetest.settings:get_bool("unified_inventory_trash") ~= false), trash_enabled = (minetest.settings:get_bool("unified_inventory_trash") ~= false),
imgscale = 1.25, imgscale = 1.25,
@ -52,9 +57,9 @@ ui.style_full = {
formw = 17.75, formw = 17.75,
formh = 12.25, formh = 12.25,
pagecols = 8, pagecols = 8,
pagerows = 10, pagerows = 9,
page_x = 10.75, page_x = 10.75,
page_y = 1.45, page_y = 2.30,
craft_x = 2.8, craft_x = 2.8,
craft_y = 1.15, craft_y = 1.15,
craftresult_x = 7.8, craftresult_x = 7.8,
@ -85,9 +90,9 @@ ui.style_lite = {
formw = 14, formw = 14,
formh = 9.75, formh = 9.75,
pagecols = 4, pagecols = 4,
pagerows = 6, pagerows = 5,
page_x = 10.5, page_x = 10.5,
page_y = 1.25, page_y = 2.15,
craft_x = 2.6, craft_x = 2.6,
craft_y = 0.75, craft_y = 0.75,
craftresult_x = 5.75, craftresult_x = 5.75,
@ -100,9 +105,9 @@ ui.style_lite = {
craft_guide_resultstr_y = 0.35, craft_guide_resultstr_y = 0.35,
give_btn_x = 0.15, give_btn_x = 0.15,
main_button_x = 10.5, main_button_x = 10.5,
main_button_y = 7.9, main_button_y = 8.15,
page_buttons_x = 10.5, page_buttons_x = 10.5,
page_buttons_y = 6.3, page_buttons_y = 6.15,
searchwidth = 1.6, searchwidth = 1.6,
form_header_x = 0.2, form_header_x = 0.2,
form_header_y = 0.2, form_header_y = 0.2,
@ -149,6 +154,8 @@ if sfinv then
end end
dofile(modpath.."/group.lua") dofile(modpath.."/group.lua")
dofile(modpath.."/category.lua")
dofile(modpath.."/default-categories.lua")
dofile(modpath.."/internal.lua") dofile(modpath.."/internal.lua")
dofile(modpath.."/callbacks.lua") dofile(modpath.."/callbacks.lua")
dofile(modpath.."/match_craft.lua") dofile(modpath.."/match_craft.lua")

View file

@ -25,6 +25,20 @@ function ui.get_per_player_formspec(player_name)
return table.copy(draw_lite_mode and ui.style_lite or ui.style_full), draw_lite_mode return table.copy(draw_lite_mode and ui.style_lite or ui.style_full), draw_lite_mode
end end
local function formspec_button(ui_peruser, name, image, offset, pos, scale, label)
local element = 'image_button'
if minetest.registered_items[image] then
element = 'item_image_button'
end
local spc = (1-scale)*ui_peruser.btn_size/2
local size = ui_peruser.btn_size*scale
return string.format("%s[%f,%f;%f,%f;%s;%s;]", element,
(offset.x or offset[1]) + ( ui_peruser.btn_spc * (pos.x or pos[1]) ) + spc,
(offset.y or offset[2]) + ( ui_peruser.btn_spc * (pos.y or pos[2]) ) + spc,
size, size, image, name) ..
string.format("tooltip[%s;%s]", name, F(label or name))
end
function ui.get_formspec(player, page) function ui.get_formspec(player, page)
if not player then if not player then
@ -107,6 +121,48 @@ function ui.get_formspec(player, page)
return table.concat(formspec, "") return table.concat(formspec, "")
end end
-- Category filters
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+(draw_lite_mode and 0.3 or 0.2), "Category:")
n = n + 1
local scroll_offset = 0
local category_count = #unified_inventory.category_list
if category_count > ui_peruser.pagecols then
scroll_offset = unified_inventory.current_category_scroll[player_name]
end
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 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)
n = n + 1
end
end
if category_count > ui_peruser.pagecols and scroll_offset > 0 then
-- prev
formspec[n] = formspec_button(ui_peruser, "prev_category", "ui_left_icon.png", categories_scroll_pos, {ui_peruser.pagecols - 2, 0}, 0.8, S("Scroll categories left"))
n = n + 1
end
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
-- Search box -- Search box
formspec[n] = "field_close_on_enter[searchbox;false]" formspec[n] = "field_close_on_enter[searchbox;false]"
@ -205,16 +261,16 @@ function ui.get_formspec(player, page)
end end
end end
formspec[n] = string.format("label[%f,%f;%s: %s]", formspec[n] = string.format("label[%f,%f;%s: %s]",
ui_peruser.page_x, ui_peruser.form_header_y, 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)) F(S("Page")), S("@1 of @2",page2,pagemax))
end end
n= n+1 n= n+1
if ui.activefilter[player_name] ~= "" then if ui.activefilter[player_name] ~= "" then
formspec[n] = string.format("label[%f,%f;%s:]", formspec[n] = string.format("label[%f,%f;%s: %s]",
ui_peruser.page_x, ui_peruser.page_y - 0.65, F(S("Filter"))) ui_peruser.page_x, ui_peruser.page_y - 0.25,
formspec[n+1] = string.format("label[%f,%f;%s]", F(S("Filter")), F(ui.activefilter[player_name]))
ui_peruser.page_x, ui_peruser.page_y - 0.25, F(ui.activefilter[player_name]))
end end
return table.concat(formspec, "") return table.concat(formspec, "")
end end
@ -225,6 +281,13 @@ function ui.set_inventory_formspec(player, page)
end end
end end
local function valid_def(def)
return (not def.groups.not_in_creative_inventory
or def.groups.not_in_creative_inventory == 0)
and def.description
and def.description ~= ""
end
--apply filter to the inventory list (create filtered copy of full one) --apply filter to the inventory list (create filtered copy of full one)
function ui.apply_filter(player, filter, search_dir) function ui.apply_filter(player, filter, search_dir)
if not player then if not player then
@ -256,13 +319,30 @@ function ui.apply_filter(player, filter, search_dir)
end end
end end
ui.filtered_items_list[player_name]={} ui.filtered_items_list[player_name]={}
for name, def in pairs(minetest.registered_items) do local category = ui.current_category[player_name] or 'all'
if (not def.groups.not_in_creative_inventory if category == 'all' then
or def.groups.not_in_creative_inventory == 0) for name, def in pairs(minetest.registered_items) do
and def.description if valid_def(def)
and def.description ~= "" and ffilter(name, def) then
and ffilter(name, def) then table.insert(ui.filtered_items_list[player_name], 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 (not ui.find_category(name))
and valid_def(def)
and ffilter(name, def) then
table.insert(ui.filtered_items_list[player_name], name)
end
end
else
for name,exists in pairs(ui.registered_category_items[category]) do
local def = minetest.registered_items[name]
if exists and def
and valid_def(def)
and ffilter(name, def) then
table.insert(ui.filtered_items_list[player_name], name)
end
end end
end end
table.sort(ui.filtered_items_list[player_name]) table.sort(ui.filtered_items_list[player_name])

View file

@ -9,3 +9,6 @@ unified_inventory_bags (Enable bags) bool true
#If enabled, the trash slot can be used by those without both creative #If enabled, the trash slot can be used by those without both creative
#and the give privilege. #and the give privilege.
unified_inventory_trash (Enable trash) bool true unified_inventory_trash (Enable trash) bool true
unified_inventory_automatic_categorization (Items automatically added to categories) bool true

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B