From fef615b588d74b36d413e071c80905fb4bf75d95 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 10 Apr 2021 09:51:05 +0200 Subject: [PATCH] update --- mods/biome_lib/API.txt | 12 +- mods/biome_lib/init.lua | 407 ++++++---- mods/biome_lib/search_functions.lua | 7 +- mods/farming/food.lua | 22 + mods/farming/textures/farming_mochi.png | Bin 0 -> 212 bytes mods/mobs_animal/chicken.lua | 3 +- mods/mobs_redo/api.lua | 13 +- mods/more_chests/models/dropbox.lua | 5 +- mods/moretrees/saplings.lua | 2 +- mods/plantlife_modpack/cavestuff/init.lua | 2 + mods/plantlife_modpack/cavestuff/mapgen.lua | 85 +-- mods/plantlife_modpack/cavestuff/mod.conf | 2 +- .../plantlife_modpack/dryplants/moregrass.lua | 40 +- mods/playerplus/README.md | 1 + mods/playerplus/init.lua | 95 +-- mods/protector/doors_chest.lua | 75 +- mods/unified_inventory/.luacheckrc | 1 + mods/unified_inventory/callbacks.lua | 38 + mods/unified_inventory/category.lua | 149 ++++ mods/unified_inventory/default-categories.lua | 704 ++++++++++++++++++ mods/unified_inventory/doc/mod_api.txt | 69 ++ mods/unified_inventory/init.lua | 19 +- mods/unified_inventory/internal.lua | 104 ++- mods/unified_inventory/settingtypes.txt | 3 + .../textures/ui_category_all.png | Bin 0 -> 1233 bytes .../textures/ui_category_none.png | Bin 0 -> 7966 bytes .../textures/ui_smallbg_9_sliced.png | Bin 0 -> 139 bytes 27 files changed, 1486 insertions(+), 372 deletions(-) create mode 100644 mods/farming/textures/farming_mochi.png create mode 100644 mods/unified_inventory/category.lua create mode 100644 mods/unified_inventory/default-categories.lua create mode 100644 mods/unified_inventory/textures/ui_category_all.png create mode 100644 mods/unified_inventory/textures/ui_category_none.png create mode 100644 mods/unified_inventory/textures/ui_smallbg_9_sliced.png diff --git a/mods/biome_lib/API.txt b/mods/biome_lib/API.txt index bacbafce..3dc337eb 100644 --- a/mods/biome_lib/API.txt +++ b/mods/biome_lib/API.txt @@ -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 just checks if DEBUG is true and outputs the phrase "[Plantlife] " followed by 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:grow_tree(pos, treemodel) diff --git a/mods/biome_lib/init.lua b/mods/biome_lib/init.lua index 515ae074..a59c17c2 100644 --- a/mods/biome_lib/init.lua +++ b/mods/biome_lib/init.lua @@ -4,37 +4,42 @@ -- 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 -biome_lib = {} biome_lib.air = {name = "air"} -biome_lib.blocklist_aircheck = {} -biome_lib.blocklist_no_aircheck = {} - -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.block_log = {} +biome_lib.block_recheck_list = {} +biome_lib.run_block_recheck_list = false biome_lib.actionslist_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.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) return string.split(string.trim(string.gsub(s, " ", ""))) 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} if c1 then for _, i in ipairs(tableize(c1)) do @@ -44,7 +49,7 @@ else biome_lib.default_grow_through_nodes["default:snow"] = true 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 = {} if c2 then 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_grow_nodes = c5 and tableize(c5) or {"default:dirt_with_grass"} --- 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 +biome_lib.debug_log_level = tonumber(minetest.settings:get("biome_lib_debug_log_level")) or 0 -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) - if DEBUG then - print("[Biome Lib] "..msg) - minetest.log("verbose", "[Biome Lib] "..msg) - end -end +-- the timer that manages the block timeout is in microseconds, but the timer +-- that manages the queue wakeup call has to be in seconds, and works best if +-- it takes a fraction of the block timeout interval. -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_persistence = 0.6 @@ -104,7 +107,6 @@ local humidity_persistence = 0.5 local humidity_scale = 250 local time_scale = 1 -local time_speed = tonumber(minetest.settings:get("time_speed")) if time_speed and time_speed > 0 then time_scale = 72 / time_speed @@ -117,6 +119,14 @@ biome_lib.perlin_humidity = PerlinNoise(humidity_seeddiff, humidity_octaves, hum -- 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 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" and string.find(nodes_or_function_or_model, ":") 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 end if type(nodes_or_function_or_model) == "string" 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 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 } local s = biomedef.surface 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 end 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 else 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 end 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 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 } local s = biomedef.surface 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 end 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 else 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 end 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 @@ -328,7 +338,7 @@ local function populate_single_surface(biome, pos, perlin_fertile_area, checkair return true 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 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 nodes_or_function_or_model.axiom then 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 else local fdir = nil if biome.random_facedir then fdir = math.random(biome.random_facedir[1], biome.random_facedir[2]) 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 end 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]) end 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 elseif objtype == "function" then 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 elseif objtype == "string" and pcall(loadstring(("return %s(...)"): format(nodes_or_function_or_model)),pos) then spawned = true + biome_lib.dbg("An obsolete string-specified function was run on surface node at "..minetest.pos_to_string(p_top), 4) 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 else tries = tries + 1 @@ -427,134 +443,196 @@ function biome_lib:populate_surfaces(biome, nodes_or_function_or_model, snodes, return items_added end --- Primary mapgen spawner, for mods that can work with air checking enabled on --- a surface during the initial map read stage. +-- Primary log read-out/mapgen spawner -function biome_lib:generate_block_with_air_checking() - if not biome_lib.blocklist_aircheck[1] then - return - end +local function confirm_block_surroundings(p) + local n=minetest.get_node_or_nil(p) + if not n or n.name == "ignore" then return false end - local minp = biome_lib.blocklist_aircheck[1][1] - local maxp = biome_lib.blocklist_aircheck[1][2] - - -- use the block hash as a unique key into the surface nodes - -- tables, so that we can write the tables thread-safely. - - 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.") + for x = -32,32,64 do -- step of 64 causes it to only check the 8 corner blocks + for y = -32,32,64 do + for z = -32,32,64 do + local n=minetest.get_node_or_nil({x=p.x + x, y=p.y + y, z=p.z + z}) + if not n or n.name == "ignore" then return false 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 + return true end --- Secondary mapgen spawner, for mods that require disabling of --- checking for air during the initial map read stage. +function biome_lib.generate_block(shutting_down) -function biome_lib:generate_block_no_aircheck() - if not biome_lib.blocklist_no_aircheck[1] then - return + if shutting_down then + if #biome_lib.block_recheck_list > 0 then + 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 - local minp = biome_lib.blocklist_no_aircheck[1][1] - local maxp = biome_lib.blocklist_no_aircheck[1][2] + local blocklog = biome_lib.run_block_recheck_list + 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 - biome_lib.surface_nodes_no_aircheck.blockhash = - minetest.find_nodes_in_area(minp, maxp, biome_lib.surfaceslist_no_aircheck) - biome_lib.actioncount_no_aircheck.blockhash = 1 - elseif not minetest.get_node_or_nil(minp) then + local minp = blocklog[1][1] + local maxp = blocklog[1][2] + local airflag = blocklog[1][3] + local pos_hash = minetest.hash_node_position(minp) + + 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) - else - if biome_lib.actionslist_no_aircheck[biome_lib.actioncount_no_aircheck.blockhash] then - biome_lib:populate_surfaces( - biome_lib.actionslist_no_aircheck[biome_lib.actioncount_no_aircheck.blockhash][1], - biome_lib.actionslist_no_aircheck[biome_lib.actioncount_no_aircheck.blockhash][2], - biome_lib.surface_nodes_no_aircheck.blockhash, false) - biome_lib.actioncount_no_aircheck.blockhash = biome_lib.actioncount_no_aircheck.blockhash + 1 + if not confirm_block_surroundings(minp) + and not shutting_down + 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 + + if biome_lib.run_block_recheck_list then + biome_lib.block_log[#biome_lib.block_log + 1] = table.copy(biome_lib.block_recheck_list[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 - table.remove(biome_lib.blocklist_no_aircheck, 1) - biome_lib.surface_nodes_no_aircheck.blockhash = nil - biome_lib.actioncount_no_aircheck.blockhash = nil + biome_lib.pos_hash.surface_node_list = airflag + and minetest.find_nodes_in_area_under_air(minp, maxp, biome_lib.surfaceslist_aircheck) + 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 -- "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) - if dtime >= step_duration + 0.1 -- don't attempt to populate if lag is already too high - 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 + if not biome_lib.block_log[1] then return end -- the block log is empty - biome_lib.globalstep_start_time = minetest.get_us_time() - biome_lib.globalstep_runtime = 0 - while (#biome_lib.blocklist_aircheck > 0 or #biome_lib.blocklist_no_aircheck > 0) - 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 + if math.random(100) > biome_lib.queue_run_ratio then return end + for s = 1, biome_lib.entries_per_step do + biome_lib.generate_block() 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 -- 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() - 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 end - print("[biome_lib] Stand by, playing out the rest of the aircheck mapblock log") - print("(there are "..#biome_lib.blocklist_aircheck.." entries)...") - while #biome_lib.blocklist_aircheck > 0 do - biome_lib:generate_block_with_air_checking(0.1) - end -end) + biome_lib.dbg("Stand by, purging the mapblock log ".. + "(there are "..(#biome_lib.block_log + #biome_lib.block_recheck_list).." entries) ...", 0) -minetest.register_on_shutdown(function() - if #biome_lib.blocklist_aircheck == 0 then - return + while #biome_lib.block_log > 0 do + biome_lib.generate_block(true) + biome_lib.check_remaining_time() end - print("[biome_lib] Stand by, playing out the rest of the no-aircheck mapblock log") - print("(there are "..#biome_lib.blocklist_no_aircheck.." entries)...") - while #biome_lib.blocklist_no_aircheck > 0 do - biome_lib:generate_block_no_aircheck(0.1) + if #biome_lib.block_recheck_list > 0 then + biome_lib.block_log = table.copy(biome_lib.block_recheck_list) + while #biome_lib.block_log > 0 do + biome_lib.generate_block(true) + biome_lib.check_remaining_time() + end end + biome_lib.dbg("Log purge completed after ".. + format_time(minetest.get_us_time() - biome_lib.shutdown_start_time)..".", 0) end) -- The spawning ABM @@ -747,29 +825,34 @@ function biome_lib:get_nodedef_field(nodename, fieldname) return minetest.registered_nodes[nodename][fieldname] end -if DEBUG then - biome_lib.last_count_air = 0 - biome_lib.last_count_no_air = 0 +if biome_lib.debug_log_level >= 3 then + biome_lib.last_count = 0 - function biome_lib.show_pending_block_counts() - if biome_lib.last_count_air ~= #biome_lib.blocklist_aircheck - or biome_lib.last_count_no_air ~= #biome_lib.blocklist_no_aircheck then - biome_lib:dbg(string.format("Pending block counts, air: %-7i no-air: %i", - #biome_lib.blocklist_aircheck, #biome_lib.blocklist_no_aircheck)) - - biome_lib.last_count_air = #biome_lib.blocklist_aircheck - biome_lib.last_count_no_air = #biome_lib.blocklist_no_aircheck + function biome_lib.show_pending_block_count() + if biome_lib.last_count ~= #biome_lib.block_log then + biome_lib.dbg(string.format("Pending block counts - ready to process: %-8icurrently deferred: %i", + #biome_lib.block_log, #biome_lib.block_recheck_list), 3) + biome_lib.last_count = #biome_lib.block_log + biome_lib.queue_idle_flag = false + elseif not biome_lib.queue_idle_flag then + 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 - minetest.after(1, biome_lib.show_pending_block_counts) + minetest.after(1, biome_lib.show_pending_block_count) end - biome_lib.show_pending_block_counts() - - 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) - + biome_lib.show_pending_block_count() 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) diff --git a/mods/biome_lib/search_functions.lua b/mods/biome_lib/search_functions.lua index 11f379aa..118e121d 100644 --- a/mods/biome_lib/search_functions.lua +++ b/mods/biome_lib/search_functions.lua @@ -56,6 +56,7 @@ end -- split into individual mapblocks to reduce lag minetest.register_on_generated(function(minp, maxp, blockseed) + local timestamp = minetest.get_us_time() for x = 0, 4 do local minx = minp.x + x*16 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 bmax = {x=minx + 15, y=miny + 15, z=minz + 15} - - biome_lib.blocklist_aircheck[#biome_lib.blocklist_aircheck + 1] = { bmin, bmax } - biome_lib.blocklist_no_aircheck[#biome_lib.blocklist_no_aircheck + 1] = { bmin, bmax } + biome_lib.block_log[#biome_lib.block_log + 1] = { bmin, bmax, true, timestamp } + biome_lib.block_log[#biome_lib.block_log + 1] = { bmin, bmax, false, timestamp } end end end + biome_lib.run_block_recheck_list = true end) diff --git a/mods/farming/food.lua b/mods/farming/food.lua index 2a1c2862..a097e6c5 100644 --- a/mods/farming/food.lua +++ b/mods/farming/food.lua @@ -639,3 +639,25 @@ minetest.register_craft({ {"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"} + } +}) diff --git a/mods/farming/textures/farming_mochi.png b/mods/farming/textures/farming_mochi.png new file mode 100644 index 0000000000000000000000000000000000000000..7b3b2b57e63bc9ad0c27366950496551bcc4adf1 GIT binary patch literal 212 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPHF3h)VWWe5ba7&bQVTCy;$cln7E zGtTeaTDfrdo#)#=+`K$(@AY5*@BY8@{r{hD+XA-ETLDzcSQ6wH%;50sMjDV4;pyTS z!f`!0!J#2^wSvj@k45YT$-b49oHI7cbTUp%DlANV@gjp^v*ornwnoXj3l)x?naQ(O zf$jNL6Xs= 2 and minp.y <= 0 then - -- Generate pebbles - local perlin1 = minetest.get_perlin(329, 3, 0.6, 100) - -- Assume X and Z lengths are equal - local divlen = 16 - local divs = (maxp.x-minp.x)/divlen+1; - for divx=0,divs-1 do - for divz=0,divs-1 do - local x0 = minp.x + math.floor((divx+0)*divlen) - local z0 = minp.z + math.floor((divz+0)*divlen) - local x1 = minp.x + math.floor((divx+1)*divlen) - local z1 = minp.z + math.floor((divz+1)*divlen) - -- 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 - local pr = PseudoRandom(seed+1) - 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 +biome_lib:register_generate_plant( + { + surface = { + "default:dirt_with_grass", + "default:gravel", + "default:stone", + "default:permafrost_with_stones" + }, + max_count = 50, + rarity = 0, + plantlife_limit = -1, + check_air = true, + random_facedir = {0, 3} + }, + { + "cavestuff:pebble_1", + "cavestuff:pebble_2" + } +) - if ground_y then - local p = {x=x,y=ground_y+1,z=z} - local nn = minetest.get_node(p).name - -- Check if the node can be replaced - if minetest.registered_nodes[nn] and - minetest.registered_nodes[nn].buildable_to then - nn = minetest.get_node({x=x,y=ground_y,z=z}).name - -- If desert sand, add dry shrub - if nn == "default:dirt_with_grass" then - minetest.swap_node(p,{name="cavestuff:pebble_"..pr:next(1,2), param2=math.random(0,3)}) - elseif nn == "default:desert_sand" then - minetest.swap_node(p,{name="cavestuff:desert_pebble_"..pr:next(1,2), param2=math.random(0,3)}) - end - end - end - - end - end - end - end -end) +biome_lib:register_generate_plant( + { + surface = { + "default:desert_sand", + "default:desert_stone" + }, + max_count = 50, + rarity = 0, + plantlife_limit = -1, + check_air = true, + random_facedir = {0, 3} + }, + { + "cavestuff:desert_pebble_1", + "cavestuff:desert_pebble_2" + } +) diff --git a/mods/plantlife_modpack/cavestuff/mod.conf b/mods/plantlife_modpack/cavestuff/mod.conf index b021c9e0..55cdcaba 100644 --- a/mods/plantlife_modpack/cavestuff/mod.conf +++ b/mods/plantlife_modpack/cavestuff/mod.conf @@ -1,2 +1,2 @@ name = cavestuff -depends = default +depends = default,biome_lib diff --git a/mods/plantlife_modpack/dryplants/moregrass.lua b/mods/plantlife_modpack/dryplants/moregrass.lua index b8d5f829..46768e7c 100644 --- a/mods/plantlife_modpack/dryplants/moregrass.lua +++ b/mods/plantlife_modpack/dryplants/moregrass.lua @@ -7,26 +7,24 @@ -- Looked at code from: default ----------------------------------------------------------------------------------------------- -abstract_dryplants.grow_grass = function(pos) - local right_here = {x=pos.x, y=pos.y+1, z=pos.z} - local grass_size = math.random(1,5) - if minetest.get_node(right_here).name == "air" -- instead of check_air = true, - or minetest.get_node(right_here).name == "default:junglegrass" then - minetest.swap_node(right_here, {name="default:grass_"..grass_size}) - end -end - -biome_lib:register_generate_plant({ - surface = { - "default:dirt_with_grass", - "stoneage:grass_with_silex", - "sumpf:peat", - "sumpf:sumpf" +biome_lib:register_generate_plant( + { + surface = { + "default:dirt_with_grass", + "stoneage:grass_with_silex", + "sumpf:peat", + "sumpf:sumpf" + }, + max_count = TALL_GRASS_PER_MAPBLOCK, + rarity = 101 - TALL_GRASS_RARITY, + min_elevation = 1, -- above sea level + plantlife_limit = -0.9, + check_air = true, }, - max_count = TALL_GRASS_PER_MAPBLOCK, - rarity = 101 - TALL_GRASS_RARITY, - min_elevation = 1, -- above sea level - plantlife_limit = -0.9, - }, - abstract_dryplants.grow_grass + { "default:grass_1", + "default:grass_2", + "default:grass_3", + "default:grass_4", + "default:grass_5" + } ) diff --git a/mods/playerplus/README.md b/mods/playerplus/README.md index c4cfe60d..3bf47f12 100644 --- a/mods/playerplus/README.md +++ b/mods/playerplus/README.md @@ -20,6 +20,7 @@ https://forum.minetest.net/viewtopic.php?t=10090&p=153667 - 1.2 - Added POVA support, tweaked code slightly - 1.3 - Add setting under Advanced to enable older sneak glitch movement - 1.4 - Add minetest 5.0 check for knockback y_offset +- 1.5 - Use Minetext 5.x functions for proper player knockback API: diff --git a/mods/playerplus/init.lua b/mods/playerplus/init.lua index 7801e865..0160f9dd 100644 --- a/mods/playerplus/init.lua +++ b/mods/playerplus/init.lua @@ -49,7 +49,7 @@ minetest.register_globalstep(function(dtime) time = 0 -- define locals outside loop - local name, pos, ndef, def, nslow, nfast + local name, pos, ndef, def, nslow, nfast, prop -- get list of players local players = minetest.get_connected_players() @@ -66,8 +66,8 @@ if name and playerplus[name] then pos = player:get_pos() -- what is around me? - pos.y = pos.y - 0.1 -- standing on - playerplus[name].nod_stand = node_ok(pos) + playerplus[name].nod_stand = node_ok({ + x = pos.x, y = pos.y - 0.1, z = pos.z}) -- Does the node below me have an on_walk_over function set? 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) end - pos.y = pos.y + 1.5 -- head level - playerplus[name].nod_head = node_ok(pos) + prop = player:get_properties() - pos.y = pos.y - 1.2 -- feet level - playerplus[name].nod_feet = node_ok(pos) + -- node at eye level + 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 def = player:get_physics_override() @@ -247,29 +249,19 @@ minetest.register_privilege("no_knockback", { -- is player knock-back effect enabled? 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 local punchy = function( 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 -- get tool damage @@ -289,7 +281,6 @@ local punchy = function( end damage = damage + (tool_capabilities.damage_groups[group] or 0) * tmp - end -- check for knockback value @@ -302,48 +293,12 @@ local punchy = function( -- print ("---", player:get_player_name(), 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 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 + -- knock back player + player:add_velocity({ + x = dir.x * (damage * 2), + y = -1, + z = dir.z * (damage * 2) + }) end minetest.register_on_punchplayer(punchy) diff --git a/mods/protector/doors_chest.lua b/mods/protector/doors_chest.lua index e98e18a0..f5a2dead 100644 --- a/mods/protector/doors_chest.lua +++ b/mods/protector/doors_chest.lua @@ -544,7 +544,7 @@ minetest.register_node("protector:chest", { local inv = meta:get_inventory() meta:set_string("infotext", S("Protected Chest")) - meta:set_string("name", "") + meta:set_string("name", S("Protected Chest")) inv:set_size("main", 8 * 4) end, @@ -575,7 +575,8 @@ minetest.register_node("protector:chest", { minetest.pos_to_string(pos)) 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() .. " moves stuff inside protected chest at " .. @@ -600,7 +601,8 @@ minetest.register_node("protector:chest", { return stack:get_count() 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 return 0 @@ -645,15 +647,39 @@ minetest.register_node("protector:chest", { 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 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 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) 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 chest_inv = meta:get_inventory() ; if not chest_inv then return end local player_inv = player:get_inventory() - local leftover + -- copy contents of player inventory to chest if fields.toup then - -- copy contents of players inventory to chest - 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 + to_from(player_inv, chest_inv) + -- copy contents of chest to player inventory elseif fields.todn then - -- copy contents of chest to players inventory - 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 + to_from(chest_inv, player_inv) elseif fields.chestname then @@ -707,9 +706,9 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) if fields.chestname ~= "" then meta:set_string("name", fields.chestname) - meta:set_string("infotext", - S("Protected Chest (@1)", fields.chestname)) + meta:set_string("infotext", fields.chestname) else + meta:set_string("name", S("Protected Chest")) meta:set_string("infotext", S("Protected Chest")) end diff --git a/mods/unified_inventory/.luacheckrc b/mods/unified_inventory/.luacheckrc index 9fb6a7c1..e6fec97d 100644 --- a/mods/unified_inventory/.luacheckrc +++ b/mods/unified_inventory/.luacheckrc @@ -14,6 +14,7 @@ read_globals = { "ItemStack", "datastorage", "hb", + "doors", } files["callbacks.lua"].ignore = { "player", "draw_lite_mode" } diff --git a/mods/unified_inventory/callbacks.lua b/mods/unified_inventory/callbacks.lua index bc902375..1f43e398 100644 --- a/mods/unified_inventory/callbacks.lua +++ b/mods/unified_inventory/callbacks.lua @@ -19,6 +19,8 @@ minetest.register_on_joinplayer(function(player) unified_inventory.active_search_direction[player_name] = "nochange" unified_inventory.apply_filter(player, "", "nochange") unified_inventory.current_searchbox[player_name] = "" + unified_inventory.current_category[player_name] = "all" + unified_inventory.current_category_scroll[player_name] = 0 unified_inventory.alternate[player_name] = 1 unified_inventory.current_item[player_name] = nil unified_inventory.current_craft_direction[player_name] = "recipe" @@ -69,6 +71,41 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) unified_inventory.current_searchbox[player_name] = fields.searchbox 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 if fields[def.name] then 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) if string.sub(clicked_item, 1, 6) == "group:" then -- Change search filter to this group + unified_inventory.current_category[player_name] = "all" apply_new_filter(player, clicked_item, new_dir) return end diff --git a/mods/unified_inventory/category.lua b/mods/unified_inventory/category.lua new file mode 100644 index 00000000..72e40382 --- /dev/null +++ b/mods/unified_inventory/category.lua @@ -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 diff --git a/mods/unified_inventory/default-categories.lua b/mods/unified_inventory/default-categories.lua new file mode 100644 index 00000000..57d3e887 --- /dev/null +++ b/mods/unified_inventory/default-categories.lua @@ -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) +--]] \ No newline at end of file diff --git a/mods/unified_inventory/doc/mod_api.txt b/mods/unified_inventory/doc/mod_api.txt index 0d100a08..ff527920 100644 --- a/mods/unified_inventory/doc/mod_api.txt +++ b/mods/unified_inventory/doc/mod_api.txt @@ -101,3 +101,72 @@ Register a non-standard craft 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") diff --git a/mods/unified_inventory/init.lua b/mods/unified_inventory/init.lua index ef42533b..391eb3cc 100644 --- a/mods/unified_inventory/init.lua +++ b/mods/unified_inventory/init.lua @@ -10,6 +10,8 @@ unified_inventory = { alternate = {}, current_page = {}, current_searchbox = {}, + current_category = {}, + current_category_scroll = {}, current_index = {}, current_item = {}, current_craft_direction = {}, @@ -33,6 +35,9 @@ unified_inventory = { -- "Lite" mode 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 = (minetest.settings:get_bool("unified_inventory_trash") ~= false), imgscale = 1.25, @@ -52,9 +57,9 @@ ui.style_full = { formw = 17.75, formh = 12.25, pagecols = 8, - pagerows = 10, + pagerows = 9, page_x = 10.75, - page_y = 1.45, + page_y = 2.30, craft_x = 2.8, craft_y = 1.15, craftresult_x = 7.8, @@ -85,9 +90,9 @@ ui.style_lite = { formw = 14, formh = 9.75, pagecols = 4, - pagerows = 6, + pagerows = 5, page_x = 10.5, - page_y = 1.25, + page_y = 2.15, craft_x = 2.6, craft_y = 0.75, craftresult_x = 5.75, @@ -100,9 +105,9 @@ ui.style_lite = { craft_guide_resultstr_y = 0.35, give_btn_x = 0.15, main_button_x = 10.5, - main_button_y = 7.9, + main_button_y = 8.15, page_buttons_x = 10.5, - page_buttons_y = 6.3, + page_buttons_y = 6.15, searchwidth = 1.6, form_header_x = 0.2, form_header_y = 0.2, @@ -149,6 +154,8 @@ if sfinv then end dofile(modpath.."/group.lua") +dofile(modpath.."/category.lua") +dofile(modpath.."/default-categories.lua") dofile(modpath.."/internal.lua") dofile(modpath.."/callbacks.lua") dofile(modpath.."/match_craft.lua") diff --git a/mods/unified_inventory/internal.lua b/mods/unified_inventory/internal.lua index 215a4f5a..6113300d 100644 --- a/mods/unified_inventory/internal.lua +++ b/mods/unified_inventory/internal.lua @@ -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 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) if not player then @@ -107,6 +121,48 @@ function ui.get_formspec(player, page) return table.concat(formspec, "") 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 formspec[n] = "field_close_on_enter[searchbox;false]" @@ -205,16 +261,16 @@ function ui.get_formspec(player, page) end end 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)) end n= n+1 if ui.activefilter[player_name] ~= "" then - formspec[n] = string.format("label[%f,%f;%s:]", - ui_peruser.page_x, ui_peruser.page_y - 0.65, F(S("Filter"))) - formspec[n+1] = string.format("label[%f,%f;%s]", - ui_peruser.page_x, ui_peruser.page_y - 0.25, F(ui.activefilter[player_name])) + formspec[n] = string.format("label[%f,%f;%s: %s]", + ui_peruser.page_x, ui_peruser.page_y - 0.25, + F(S("Filter")), F(ui.activefilter[player_name])) end return table.concat(formspec, "") end @@ -225,6 +281,13 @@ function ui.set_inventory_formspec(player, page) 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) function ui.apply_filter(player, filter, search_dir) if not player then @@ -256,13 +319,30 @@ function ui.apply_filter(player, filter, search_dir) end end ui.filtered_items_list[player_name]={} - for name, def in pairs(minetest.registered_items) do - if (not def.groups.not_in_creative_inventory - or def.groups.not_in_creative_inventory == 0) - and def.description - and def.description ~= "" - and ffilter(name, def) then - table.insert(ui.filtered_items_list[player_name], name) + local category = ui.current_category[player_name] or 'all' + if category == 'all' then + for name, def in pairs(minetest.registered_items) do + if valid_def(def) + and ffilter(name, def) then + 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 table.sort(ui.filtered_items_list[player_name]) diff --git a/mods/unified_inventory/settingtypes.txt b/mods/unified_inventory/settingtypes.txt index 910989fd..27768ac2 100644 --- a/mods/unified_inventory/settingtypes.txt +++ b/mods/unified_inventory/settingtypes.txt @@ -9,3 +9,6 @@ unified_inventory_bags (Enable bags) bool true #If enabled, the trash slot can be used by those without both creative #and the give privilege. unified_inventory_trash (Enable trash) bool true + + +unified_inventory_automatic_categorization (Items automatically added to categories) bool true \ No newline at end of file diff --git a/mods/unified_inventory/textures/ui_category_all.png b/mods/unified_inventory/textures/ui_category_all.png new file mode 100644 index 0000000000000000000000000000000000000000..0af7a53713d99fe343de955bb6cda70ede76a6e3 GIT binary patch literal 1233 zcmV;?1TOoDP)C0001!P)t-s0000* zMMVGr08>*_J3T&!mxv>kdCn{N(>!*Rkd8B^d#sl*99lt{e5&(cT|cr1xzf#0R@o`6>Z?+a8u94Fue zvIu{t+rzkQ=P`lQ@K zpz;*``gc$le12BrUtW-#>yvUDfrujxmjc9TJQIjGRDhoEaIg`mMjVegG66!4=K@%F zIN+$~yPe7{1&+D^cu5jCBna$m1p*1pr}t_QqzYg?&BuBzr}@aO1QeMB81cE4K&=+k z6zKvrb^>zqI#vR}yr2?bIm5{91%5H)i7JPHnS<0kzW^!F`0E5d&40&L9m)#HKTRXL z#0fZouPJcz528#pTL?XGHe=U^668e+h)@$~Mi}eiN(%kF`vRWRe3m!Q#IBbLw)=u5 zk=XNT{$<`|#H=6mMfhY_%a=f}*?{J={Pk6h@AWR$V_Q%e^xx=@z)D~?UXI5w&;>A` z3lOLAOn|_Ad6G>npUuXSCB3woE$DYhZv+z{XncfT%V|En+X-xDP%s2&IOcN!(9;Ff zd?K`}G^BV?F-z#zzH~k|3={EA4IBt zhhcmzm#rl8kLw$atM3ho#?!g6WAa(^g>LqTu>;xyhufc1GI zuIG!8<-R|Z2zp)Sod}G#6~G)Wkequy6m$X5wG`le9(6h`_X#*4&naN``PAI=e2`Nh zsn4h7gjDc6N#A>pBE-?+fzeovzy#xP5^WZ~~uB;N~Ah z-%sV+tl6NH=ZM0H8wsHL1B>T{ihGVIWbppKBI52NEQrFMBWktO_;#Ds@)a=dBP;;& z+|LoUh|_p3FrTa@6Bsao>1Z(;!GPtGJr0%vgdEQWs`L3ovZ5EO<;$mQM*d@SLFexW7<{K(O7;fi`Hpo&uIL@(ZAT zONH&$tmRT+y)NoGA|=2t2m?9a%-2ML&k;c&cP-Czzo6Alx}bZG$n6W9KrVs%{)$H_ vJl^lud`jctKELW!D*pX_-Ou50d|Cbin*%0p!3mn+00000NkvXXu0mjf(tSv~ literal 0 HcmV?d00001 diff --git a/mods/unified_inventory/textures/ui_category_none.png b/mods/unified_inventory/textures/ui_category_none.png new file mode 100644 index 0000000000000000000000000000000000000000..8976fb0eb122a801499fd2dbe009c0449fed1a6e GIT binary patch literal 7966 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 UNozg^(*OVf07*qoM6N<$f(*TUkpKVy literal 0 HcmV?d00001 diff --git a/mods/unified_inventory/textures/ui_smallbg_9_sliced.png b/mods/unified_inventory/textures/ui_smallbg_9_sliced.png new file mode 100644 index 0000000000000000000000000000000000000000..865e0c965c47dc4e224e5f5bef279e547168d408 GIT binary patch literal 139 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDD3?#L31Vw-pPk>K|E0FH(?QLmksj8}y5%Zf5 zo&VKa{5Uz`Fa!Iv#d8Ar*w|Hf fE>KaKqQb_IlIdwG-SNT~XbOX;tDnm{r-UW|$5SPz literal 0 HcmV?d00001