Compare commits
	
		
			41 Commits
		
	
	
		
			15a84bdbf4
			...
			hide_uncra
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 4499ae1139 | ||
|  | 693ca112b8 | ||
|  | 380b77d0fb | ||
|  | 43c9b50800 | ||
|  | 5d233a0f0a | ||
|  | 2426b6c912 | ||
|  | d6d4bea819 | ||
|  | 15d729c351 | ||
|  | b2cc3d1532 | ||
|  | 37969b2a1b | ||
|  | 1b074828a6 | ||
|  | de0063835c | ||
|  | bda9f2598f | ||
|  | b590764026 | ||
|  | 82cdf24045 | ||
|  | 31c35dcd59 | ||
|  | 826d5f4683 | ||
|  | db1c3c10b8 | ||
|  | 9533200e25 | ||
|  | 177debd13c | ||
|  | 8e9ea34ae8 | ||
|  | 574de91971 | ||
|  | fc562ecaa0 | ||
|  | 182ab493c3 | ||
|  | 14da1a3dd0 | ||
|  | fa079c31b6 | ||
|  | c1fef26c87 | ||
|  | 477acd2f89 | ||
|  | 19efce45ed | ||
|  | dbe06be68b | ||
|  | 3074d625e2 | ||
|  | 5ac2558da4 | ||
|  | 25c40fea6c | ||
|  | 23a45b8131 | ||
|  | d6688872c8 | ||
|  | 64b0248c77 | ||
|  | 28a5d4db49 | ||
|  | 8c84751ff8 | ||
|  | b1c85a2cb8 | ||
|  | 3fffa41eaf | ||
|  | 077bed90eb | 
							
								
								
									
										16
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @@ -71,6 +71,9 @@ From http://www.clker.com (Public Domain, CC-BY-4.0): | ||||
|   * [`ui_pencil_icon.pnc`](http://www.clker.com/clipart-2256.html) | ||||
|   * [`ui_waypoint_set_icon.png`](http://www.clker.com/clipart-larger-flag.html) | ||||
|  | ||||
| From https://www.svgrepo.com (CC-BY) | ||||
|   * [`ui_teleport.png`](https://www.svgrepo.com/svg/321565/teleport) | ||||
|  | ||||
| Everaldo Coelho (YellowIcon) (LGPL v2.1+): | ||||
|  | ||||
|   * [`ui_craftguide_icon.png` / `ui_craft_icon.png`](http://commons.wikimedia.org/wiki/File:Advancedsettings.png) | ||||
| @@ -102,3 +105,16 @@ Other files from Wikimedia Commons: | ||||
| RealBadAngel: (CC-BY-4.0) | ||||
|  | ||||
|   * Everything else. | ||||
|  | ||||
|  | ||||
| ## Sounds | ||||
|  | ||||
|  * [`bell.ogg`](https://freesound.org/people/bennstir/sounds/81072/) by bennstir, CC 4.0 | ||||
|  * [`electricity.ogg`](https://freesound.org/people/Halleck/sounds/19486/) by Halleck, CC 4.0 (cut) | ||||
|  * [`pageflip1.ogg`](https://freesound.org/people/themfish/sounds/45823/) by themfish, CC 4.0 (cut, slowed down) | ||||
|  * `pageflip2.ogg` (derived from `pageflip1.ogg`) | ||||
|  * [`trash.ogg`](https://freesound.org/people/OwlStorm/sounds/151231/) by OwlStorm, CC 0 (speed up) | ||||
|  * [`trash_all.ogg`](https://freesound.org/people/abel_K/sounds/68280/) by abel_K, Sampling Plus 1.0 (speed up) | ||||
|  * [`ui_click.ogg`](https://freesound.org/people/lartti/sounds/527569/) by lartti, CC 0 (cut) | ||||
|  * [`ui_morning.ogg`](https://freesound.org/people/InspectorJ/sounds/439472/) by InspectorJ, CC 4.0 | ||||
|  * [`ui_owl.ogg`](https://freesound.org/people/manda_g/sounds/54987/) by manda_g, Sampling Plus 1.0 (cut) | ||||
|   | ||||
							
								
								
									
										159
									
								
								api.lua
									
									
									
									
									
								
							
							
						
						| @@ -2,48 +2,61 @@ local S = minetest.get_translator("unified_inventory") | ||||
| local F = minetest.formspec_escape | ||||
| local ui = unified_inventory | ||||
|  | ||||
| local function is_recipe_craftable(recipe) | ||||
| 	-- Ensure the ingedients exist | ||||
| 	for _, itemname in pairs(recipe.items) do | ||||
| 		local groups = string.find(itemname, "group:") | ||||
| 		if groups then | ||||
| 			if not ui.get_group_item(string.sub(groups, 8)).item then | ||||
| 				return false | ||||
| 			end | ||||
| 		else | ||||
| 			-- Possibly an item | ||||
| 			local itemname_cleaned = ItemStack(itemname):get_name() | ||||
| 			if not minetest.registered_items[itemname_cleaned] | ||||
| 					or minetest.get_item_group(itemname_cleaned, "not_in_craft_guide") ~= 0 then | ||||
| 				return false | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
| 	return true | ||||
| end | ||||
|  | ||||
| -- Create detached creative inventory after loading all mods | ||||
| minetest.after(0.01, function() | ||||
| 	local rev_aliases = {} | ||||
| 	for source, target in pairs(minetest.registered_aliases) do | ||||
| 		if not rev_aliases[target] then rev_aliases[target] = {} end | ||||
| 		table.insert(rev_aliases[target], source) | ||||
| 	for original, newname in pairs(minetest.registered_aliases) do | ||||
| 		if not rev_aliases[newname] then | ||||
| 			rev_aliases[newname] = {} | ||||
| 		end | ||||
| 		table.insert(rev_aliases[newname], original) | ||||
| 	end | ||||
|  | ||||
| 	-- Filtered item list | ||||
| 	ui.items_list = {} | ||||
| 	for name, def in pairs(minetest.registered_items) do | ||||
| 		if (not def.groups.not_in_creative_inventory or | ||||
| 		   def.groups.not_in_creative_inventory == 0) and | ||||
| 		   def.description and def.description ~= "" then | ||||
| 		if ui.is_itemdef_listable(def) then | ||||
| 			table.insert(ui.items_list, name) | ||||
|  | ||||
| 			-- Alias processing: Find recipes that belong to the current item name | ||||
| 			local all_names = rev_aliases[name] or {} | ||||
| 			table.insert(all_names, name) | ||||
| 			for _, player_name in ipairs(all_names) do | ||||
| 				local recipes = minetest.get_all_craft_recipes(player_name) | ||||
| 				if recipes then | ||||
| 					for _, recipe in ipairs(recipes) do | ||||
|  | ||||
| 						local unknowns | ||||
|  | ||||
| 						for _,chk in pairs(recipe.items) do | ||||
| 							local groupchk = string.find(chk, "group:") | ||||
| 							if (not groupchk and not minetest.registered_items[chk]) | ||||
| 							  or (groupchk and not ui.get_group_item(string.gsub(chk, "group:", "")).item) | ||||
| 							  or minetest.get_item_group(chk, "not_in_craft_guide") ~= 0 then | ||||
| 								unknowns = true | ||||
| 							end | ||||
| 						end | ||||
|  | ||||
| 						if not unknowns then | ||||
| 							ui.register_craft(recipe) | ||||
| 						end | ||||
| 			for _, itemname in ipairs(all_names) do | ||||
| 				local recipes = minetest.get_all_craft_recipes(itemname) | ||||
| 				for _, recipe in ipairs(recipes or {}) do | ||||
| 					if is_recipe_craftable(recipe) then | ||||
| 						ui.register_craft(recipe) | ||||
| 					end | ||||
| 				end | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
|  | ||||
| 	table.sort(ui.items_list) | ||||
| 	ui.items_list_size = #ui.items_list | ||||
| 	print("Unified Inventory. inventory size: "..ui.items_list_size) | ||||
| 	print("Unified Inventory. Inventory size: "..ui.items_list_size) | ||||
|  | ||||
| 	-- Analyse dropped items -> custom "digging" recipes | ||||
| 	for _, name in ipairs(ui.items_list) do | ||||
| 		local def = minetest.registered_items[name] | ||||
| 		-- Simple drops | ||||
| @@ -133,29 +146,51 @@ minetest.after(0.01, function() | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
| 	for _, recipes in pairs(ui.crafts_for.recipe) do | ||||
|  | ||||
| 	-- Step 1: Initialize cache for looking up groups | ||||
| 	unified_inventory.init_matching_cache() | ||||
|  | ||||
| 	-- Step 2: Find all matching items for the given spec (groups) | ||||
| 	local get_matching_spec_items = unified_inventory.get_matching_items | ||||
|  | ||||
| 	for outputitemname, recipes in pairs(ui.crafts_for.recipe) do | ||||
| 		-- List of crafts that return this item string (variable "_") | ||||
|  | ||||
| 		-- Problem: The group cache must be initialized after all mods finished loading | ||||
| 		-- thus, invalid recipes might be indexed. Hence perform filtering with `new_recipe_list` | ||||
| 		local new_recipe_list = {} | ||||
| 		for _, recipe in ipairs(recipes) do | ||||
| 			local ingredient_items = {} | ||||
| 			for _, spec in pairs(recipe.items) do | ||||
| 				local matches_spec = ui.canonical_item_spec_matcher(spec) | ||||
| 				for _, name in ipairs(ui.items_list) do | ||||
| 					if matches_spec(name) then | ||||
| 						ingredient_items[name] = true | ||||
| 					end | ||||
| 				-- Get items that fit into this spec (group or item name) | ||||
| 				local specname = ItemStack(spec):get_name() | ||||
| 				for item_name, _ in pairs(get_matching_spec_items(specname)) do | ||||
| 					ingredient_items[item_name] = true | ||||
| 				end | ||||
| 			end | ||||
| 			for name, _ in pairs(ingredient_items) do | ||||
| 				if ui.crafts_for.usage[name] == nil then | ||||
| 				if not ui.crafts_for.usage[name] then | ||||
| 					ui.crafts_for.usage[name] = {} | ||||
| 				end | ||||
| 				table.insert(ui.crafts_for.usage[name], recipe) | ||||
| 			end | ||||
|  | ||||
| 			if next(ingredient_items) then | ||||
| 				-- There's at least one known ingredient: mark as good recipe | ||||
| 				-- PS: What whatll be done about partially incomplete recipes? | ||||
| 				table.insert(new_recipe_list, recipe) | ||||
| 			end | ||||
| 		end | ||||
| 		ui.crafts_for.recipe[outputitemname] = new_recipe_list | ||||
| 	end | ||||
|  | ||||
| 	for _, callback in ipairs(ui.initialized_callbacks) do | ||||
| 		callback() | ||||
| 	end | ||||
| end) | ||||
|  | ||||
| ---------------- Home API ---------------- | ||||
|  | ||||
| -- load_home | ||||
| local function load_home() | ||||
| 	local input = io.open(ui.home_filename, "r") | ||||
| 	if not input then | ||||
| @@ -172,6 +207,7 @@ local function load_home() | ||||
| 	end | ||||
| 	io.close(input) | ||||
| end | ||||
|  | ||||
| load_home() | ||||
|  | ||||
| function ui.set_home(player, pos) | ||||
| @@ -200,7 +236,8 @@ function ui.go_home(player) | ||||
| 	return false | ||||
| end | ||||
|  | ||||
| -- register_craft | ||||
| ---------------- Crafting API ---------------- | ||||
|  | ||||
| function ui.register_craft(options) | ||||
| 	if not options.output then | ||||
| 		return | ||||
| @@ -212,12 +249,16 @@ function ui.register_craft(options) | ||||
| 	if options.type == "normal" and options.width == 0 then | ||||
| 		options = { type = "shapeless", items = options.items, output = options.output, width = 0 } | ||||
| 	end | ||||
| 	if not ui.crafts_for.recipe[itemstack:get_name()] then | ||||
| 		ui.crafts_for.recipe[itemstack:get_name()] = {} | ||||
| 	local item_name = itemstack:get_name() | ||||
| 	if not ui.crafts_for.recipe[item_name] then | ||||
| 		ui.crafts_for.recipe[item_name] = {} | ||||
| 	end | ||||
| 	table.insert(ui.crafts_for.recipe[itemstack:get_name()],options) | ||||
| end | ||||
| 	table.insert(ui.crafts_for.recipe[item_name],options) | ||||
|  | ||||
| 	for _, callback in ipairs(ui.craft_registered_callbacks) do | ||||
| 		callback(item_name, options) | ||||
| 	end | ||||
| end | ||||
|  | ||||
| local craft_type_defaults = { | ||||
| 	width = 3, | ||||
| @@ -225,7 +266,6 @@ local craft_type_defaults = { | ||||
| 	uses_crafting_grid = false, | ||||
| } | ||||
|  | ||||
|  | ||||
| function ui.craft_type_defaults(name, options) | ||||
| 	if not options.description then | ||||
| 		options.description = name | ||||
| @@ -236,8 +276,7 @@ end | ||||
|  | ||||
|  | ||||
| function ui.register_craft_type(name, options) | ||||
| 	ui.registered_craft_types[name] = | ||||
| 			ui.craft_type_defaults(name, options) | ||||
| 	ui.registered_craft_types[name] = ui.craft_type_defaults(name, options) | ||||
| end | ||||
|  | ||||
|  | ||||
| @@ -294,6 +333,8 @@ ui.register_craft_type("digging_chance", { | ||||
| 	height = 1, | ||||
| }) | ||||
|  | ||||
| ---------------- GUI registrations ---------------- | ||||
|  | ||||
| function ui.register_page(name, def) | ||||
| 	ui.pages[name] = def | ||||
| end | ||||
| @@ -309,11 +350,45 @@ function ui.register_button(name, def) | ||||
| 	table.insert(ui.buttons, def) | ||||
| end | ||||
|  | ||||
| ---------------- Callback registrations ---------------- | ||||
|  | ||||
| function ui.register_on_initialized(callback) | ||||
| 	if type(callback) ~= "function" then | ||||
| 		error(("Initialized callback must be a function, %s given."):format(type(callback))) | ||||
| 	end | ||||
| 	table.insert(ui.initialized_callbacks, callback) | ||||
| end | ||||
|  | ||||
| function ui.register_on_craft_registered(callback) | ||||
| 	if type(callback) ~= "function" then | ||||
| 		error(("Craft registered callback must be a function, %s given."):format(type(callback))) | ||||
| 	end | ||||
| 	table.insert(ui.craft_registered_callbacks, callback) | ||||
| end | ||||
|  | ||||
| ---------------- List getters ---------------- | ||||
|  | ||||
| function ui.get_recipe_list(output) | ||||
| 	return ui.crafts_for.recipe[output] | ||||
| end | ||||
|  | ||||
| function ui.get_registered_outputs() | ||||
| 	local outputs = {} | ||||
| 	for item_name, _ in pairs(ui.crafts_for.recipe) do | ||||
| 		table.insert(outputs, item_name) | ||||
| 	end | ||||
| 	return outputs | ||||
| end | ||||
|  | ||||
| ---------------- Player utilities ---------------- | ||||
|  | ||||
| function ui.is_creative(playername) | ||||
| 	return minetest.check_player_privs(playername, {creative=true}) | ||||
| 		or minetest.settings:get_bool("creative_mode") | ||||
| end | ||||
|  | ||||
| ---------------- Formspec helpers ---------------- | ||||
|  | ||||
| function ui.single_slot(xpos, ypos, bright) | ||||
| 	return string.format("background9[%f,%f;%f,%f;ui_single_slot%s.png;false;16]", | ||||
| 	xpos, ypos, ui.imgscale, ui.imgscale, (bright and "_bright" or "") ) | ||||
|   | ||||
							
								
								
									
										141
									
								
								bags.lua
									
									
									
									
									
								
							
							
						
						| @@ -10,25 +10,26 @@ local F = minetest.formspec_escape | ||||
| local ui = unified_inventory | ||||
|  | ||||
| ui.register_page("bags", { | ||||
| 	get_formspec = function(player) | ||||
| 	get_formspec = function(player, perplayer_formspec) | ||||
| 		local player_name = player:get_player_name() | ||||
| 		return { formspec = table.concat({ | ||||
| 			ui.style_full.standard_inv_bg, | ||||
| 			ui.single_slot(0.925, 1.5), | ||||
| 			ui.single_slot(3.425, 1.5), | ||||
| 			ui.single_slot(5.925, 1.5), | ||||
| 			ui.single_slot(8.425, 1.5), | ||||
| 			"label["..ui.style_full.form_header_x..","..ui.style_full.form_header_y..";" .. F(S("Bags")) .. "]", | ||||
| 			"button[0.6125,2.75;1.875,0.75;bag1;" .. F(S("Bag @1", 1)) .. "]", | ||||
| 			"button[3.1125,2.75;1.875,0.75;bag2;" .. F(S("Bag @1", 2)) .. "]", | ||||
| 			"button[5.6125,2.75;1.875,0.75;bag3;" .. F(S("Bag @1", 3)) .. "]", | ||||
| 			"button[8.1125,2.75;1.875,0.75;bag4;" .. F(S("Bag @1", 4)) .. "]", | ||||
| 		local std_inv_x = perplayer_formspec.std_inv_x | ||||
| 		local formspec = { | ||||
| 			perplayer_formspec.standard_inv_bg, | ||||
| 			"label[", perplayer_formspec.form_header_x, ",", | ||||
| 				perplayer_formspec.form_header_y, ";", F(S("Bags")), "]", | ||||
| 			"listcolors[#00000000;#00000000]", | ||||
| 			"list[detached:" .. F(player_name) .. "_bags;bag1;1.075,1.65;1,1;]", | ||||
| 			"list[detached:" .. F(player_name) .. "_bags;bag2;3.575,1.65;1,1;]", | ||||
| 			"list[detached:" .. F(player_name) .. "_bags;bag3;6.075,1.65;1,1;]", | ||||
| 			"list[detached:" .. F(player_name) .. "_bags;bag4;8.575,1.65;1,1;]" | ||||
| 		}) } | ||||
| 		} | ||||
|  | ||||
| 		for i = 1, 4 do | ||||
| 			local x = std_inv_x + i * 2.5 | ||||
| 			formspec[#formspec + 1] = ui.single_slot(x - 1.875, 1.5) | ||||
| 			formspec[#formspec + 1] = string.format("list[detached:%s_bags;bag%i;%.3f,1.65;1,1;]", | ||||
| 				F(player_name), i, x - 1.725) | ||||
| 			formspec[#formspec + 1] = string.format("button[%.4f,2.75;1.875,0.75;bag%i;%s]", | ||||
| 				x - 2.1875, i, F(S("Bag @1", i))) | ||||
| 		end | ||||
|  | ||||
| 		return { formspec = table.concat(formspec) } | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| @@ -36,7 +37,6 @@ ui.register_button("bags", { | ||||
| 	type = "image", | ||||
| 	image = "ui_bags_icon.png", | ||||
| 	tooltip = S("Bags"), | ||||
| 	hide_lite=true | ||||
| }) | ||||
|  | ||||
| local function get_player_bag_stack(player, i) | ||||
| @@ -48,23 +48,38 @@ end | ||||
|  | ||||
| for bag_i = 1, 4 do | ||||
| 	ui.register_page("bag" .. bag_i, { | ||||
| 		get_formspec = function(player) | ||||
| 		get_formspec = function(player, perplayer_formspec) | ||||
| 			local stack = get_player_bag_stack(player, bag_i) | ||||
| 			local image = stack:get_definition().inventory_image | ||||
| 			local slots = stack:get_definition().groups.bagslots | ||||
| 			local std_inv_x = perplayer_formspec.std_inv_x | ||||
| 			local lite_mode = perplayer_formspec.is_lite_mode | ||||
|  | ||||
| 			local bag_inv_y, header_x, header_y = 1.5, 0.3, 0.65 | ||||
| 			if lite_mode then | ||||
| 				bag_inv_y = 0.5 | ||||
| 				header_x = perplayer_formspec.form_header_x | ||||
| 				header_y = perplayer_formspec.form_header_y | ||||
| 			end | ||||
|  | ||||
| 			local formspec = { | ||||
| 				ui.style_full.standard_inv_bg, | ||||
| 				ui.make_inv_img_grid(0.3, 1.5, 8, slots/8), | ||||
| 				"image[9.2,0.4;1,1;" .. image .. "]", | ||||
| 				"label[0.3,0.65;" .. F(S("Bag @1", bag_i)) .. "]", | ||||
| 				perplayer_formspec.standard_inv_bg, | ||||
| 				ui.make_inv_img_grid(std_inv_x, bag_inv_y, 8, slots/8), | ||||
| 				"label[", header_x, ",", header_y, ";", F(S("Bag @1", bag_i)), "]", | ||||
| 				"listcolors[#00000000;#00000000]", | ||||
| 				"listring[current_player;main]", | ||||
| 				string.format("list[current_player;bag%icontents;%f,%f;8,3;]", | ||||
| 				    bag_i, 0.3 + ui.list_img_offset, 1.5 + ui.list_img_offset), | ||||
| 				"listring[current_name;bag" .. bag_i .. "contents]", | ||||
| 				    bag_i, std_inv_x + ui.list_img_offset, bag_inv_y + ui.list_img_offset), | ||||
| 				"listring[current_name;bag", bag_i, "contents]", | ||||
| 			} | ||||
|  | ||||
| 			if lite_mode then | ||||
| 				return { formspec = table.concat(formspec) } | ||||
| 			end | ||||
|  | ||||
| 			local n = #formspec + 1 | ||||
| 			formspec[n] = "image[" .. std_inv_x + 8.9 .. ",0.4;1,1;" .. image .. "]" | ||||
| 			n = n + 1 | ||||
|  | ||||
| 			local player_name = player:get_player_name() -- For if statement. | ||||
| 			if ui.trash_enabled | ||||
| @@ -114,6 +129,8 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) | ||||
| 	end | ||||
| end) | ||||
|  | ||||
| -- Player slots are preserved when unified_inventory is disabled. Do not allow modification. | ||||
| -- Fix: use a detached inventory and store the data separately. | ||||
| local function save_bags_metadata(player, bags_inv) | ||||
| 	local is_empty = true | ||||
| 	local bags = {} | ||||
| @@ -163,7 +180,7 @@ local function load_bags_metadata(player, bags_inv) | ||||
| 		save_bags_metadata(player, bags_inv) | ||||
| 	end | ||||
|  | ||||
| 	-- Clean up deprecated garbage after saving | ||||
| 	-- Legacy: Clean up old player lists | ||||
| 	for i = 1, 4 do | ||||
| 		local bag = "bag" .. i | ||||
| 		player_inv:set_size(bag, 0) | ||||
| @@ -172,46 +189,29 @@ end | ||||
|  | ||||
| minetest.register_on_joinplayer(function(player) | ||||
| 	local player_name = player:get_player_name() | ||||
| 	local bags_inv = minetest.create_detached_inventory(player_name .. "_bags",{ | ||||
| 	local bags_inv = minetest.create_detached_inventory(player_name .. "_bags", { | ||||
| 		allow_put = function(inv, listname, index, stack, player) | ||||
| 			local new_slots = stack:get_definition().groups.bagslots | ||||
| 			if not new_slots then | ||||
| 				return 0 -- ItemStack is not a bag. | ||||
| 			end | ||||
|  | ||||
| 			-- The execution order of `allow_put`/`allow_take` is not defined. | ||||
| 			-- We do not know the replacement ItemStack if the items are swapped. | ||||
| 			-- Hence, bag slot upgrades and downgrades are not possible with the | ||||
| 			-- current API. | ||||
|  | ||||
| 			if not player:get_inventory():is_empty(listname .. "contents") then | ||||
| 				-- Legacy: in case `allow_take` is not executed on old Minetest versions. | ||||
| 				return 0 | ||||
| 			end | ||||
| 			return 1 | ||||
| 		end, | ||||
| 		on_put = function(inv, listname, index, stack, player) | ||||
| 			player:get_inventory():set_size(listname .. "contents", | ||||
| 					stack:get_definition().groups.bagslots) | ||||
| 			save_bags_metadata(player, inv) | ||||
| 		end, | ||||
| 		allow_put = function(inv, listname, index, stack, player) | ||||
| 			local new_slots = stack:get_definition().groups.bagslots | ||||
| 			if not new_slots then | ||||
| 				return 0 | ||||
| 			end | ||||
| 			local player_inv = player:get_inventory() | ||||
| 			local old_slots = player_inv:get_size(listname .. "contents") | ||||
|  | ||||
| 			if new_slots >= old_slots then | ||||
| 				return 1 | ||||
| 			end | ||||
|  | ||||
| 			-- using a smaller bag, make sure it fits | ||||
| 			local old_list = player_inv:get_list(listname .. "contents") | ||||
| 			local new_list = {} | ||||
| 			local slots_used = 0 | ||||
| 			local use_new_list = false | ||||
|  | ||||
| 			for i, v in ipairs(old_list) do | ||||
| 				if v and not v:is_empty() then | ||||
| 					slots_used = slots_used + 1 | ||||
| 					use_new_list = i > new_slots | ||||
| 					new_list[slots_used] = v | ||||
| 				end | ||||
| 			end | ||||
| 			if new_slots >= slots_used then | ||||
| 				if use_new_list then | ||||
| 					player_inv:set_list(listname .. "contents", new_list) | ||||
| 				end | ||||
| 				return 1 | ||||
| 			end | ||||
| 			-- New bag is smaller: Disallow inserting | ||||
| 			return 0 | ||||
| 		end, | ||||
| 		allow_take = function(inv, listname, index, stack, player) | ||||
| 			if player:get_inventory():is_empty(listname .. "contents") then | ||||
| 				return stack:get_count() | ||||
| @@ -221,6 +221,11 @@ minetest.register_on_joinplayer(function(player) | ||||
| 		on_take = function(inv, listname, index, stack, player) | ||||
| 			player:get_inventory():set_size(listname .. "contents", 0) | ||||
| 			save_bags_metadata(player, inv) | ||||
| 			if listname == ui.current_page[player:get_player_name()] then | ||||
| 				-- Bag is currently open: avoid follow-up issues by navigating back | ||||
| 				-- Trick: the list name is the same as the registered page name | ||||
| 				ui.set_inventory_formspec(player, "bags") | ||||
| 			end | ||||
| 		end, | ||||
| 		allow_move = function() | ||||
| 			return 0 | ||||
| @@ -230,6 +235,20 @@ minetest.register_on_joinplayer(function(player) | ||||
| 	load_bags_metadata(player, bags_inv) | ||||
| end) | ||||
|  | ||||
|  | ||||
| minetest.register_allow_player_inventory_action(function(player, action, inventory, info) | ||||
| 	-- From detached inventory -> player inventory: put & take callbacks | ||||
| 	if action ~= "put" or not info.listname:find("bag%dcontents") then | ||||
| 		return | ||||
| 	end | ||||
| 	if info.stack:get_definition().groups.bagslots then | ||||
| 		-- Problem 1: empty bags could be moved into their own slots | ||||
| 		-- Problem 2: cannot reliably keep track of ItemStack ownership due to | ||||
| 		--> Disallow all external bag movements into this list | ||||
| 		return 0 | ||||
| 	end | ||||
| end) | ||||
|  | ||||
| -- register bag tools | ||||
| minetest.register_tool("unified_inventory:bag_small", { | ||||
| 	description = S("Small Bag"), | ||||
|   | ||||
							
								
								
									
										118
									
								
								callbacks.lua
									
									
									
									
									
								
							
							
						
						| @@ -1,3 +1,5 @@ | ||||
| local ui = unified_inventory | ||||
|  | ||||
| local function default_refill(stack) | ||||
| 	stack:set_count(stack:get_stack_max()) | ||||
| 	local itemdef = minetest.registered_items[stack:get_name()] | ||||
| @@ -12,20 +14,17 @@ end | ||||
| minetest.register_on_joinplayer(function(player) | ||||
| 	local player_name = player:get_player_name() | ||||
| 	unified_inventory.players[player_name] = {} | ||||
| 	unified_inventory.current_index[player_name] = 1 | ||||
| 	unified_inventory.current_index[player_name] = 1 -- Item (~page) index | ||||
| 	unified_inventory.filtered_items_list[player_name] = | ||||
| 	unified_inventory.items_list | ||||
| 		unified_inventory.items_list | ||||
| 	unified_inventory.activefilter[player_name] = "" | ||||
| 	unified_inventory.active_search_direction[player_name] = "nochange" | ||||
| 	unified_inventory.apply_filter(player, "", "nochange") | ||||
| 	unified_inventory.current_searchbox[player_name] = "" | ||||
| 	unified_inventory.current_category[player_name] = "all" | ||||
| 	unified_inventory.current_category_scroll[player_name] = 0 | ||||
| 	unified_inventory.alternate[player_name] = 1 | ||||
| 	unified_inventory.current_item[player_name] = nil | ||||
| 	unified_inventory.current_craft_direction[player_name] = "recipe" | ||||
| 	unified_inventory.set_inventory_formspec(player, | ||||
| 	unified_inventory.default) | ||||
|  | ||||
| 	-- Refill slot | ||||
| 	local refill = minetest.create_detached_inventory(player_name.."refill", { | ||||
| @@ -47,30 +46,58 @@ minetest.register_on_joinplayer(function(player) | ||||
| 	refill:set_size("main", 1) | ||||
| end) | ||||
|  | ||||
| minetest.register_on_mods_loaded(function() | ||||
|        minetest.register_on_joinplayer(function(player) | ||||
|                -- After everything is initialized, set up the formspec | ||||
|                ui.apply_filter(player, "", "nochange") | ||||
|                ui.set_inventory_formspec(player, unified_inventory.default) | ||||
|        end) | ||||
| end) | ||||
|  | ||||
| local function apply_new_filter(player, search_text, new_dir) | ||||
| 	local player_name = player:get_player_name() | ||||
| 	minetest.sound_play("click", {to_player=player_name, gain = 0.1}) | ||||
| 	unified_inventory.apply_filter(player, search_text, new_dir) | ||||
| 	unified_inventory.current_searchbox[player_name] = search_text | ||||
| 	unified_inventory.set_inventory_formspec(player, | ||||
| 			unified_inventory.current_page[player_name]) | ||||
|  | ||||
| 	minetest.sound_play("ui_click", {to_player=player_name, gain = 0.1}) | ||||
| 	ui.apply_filter(player, search_text, new_dir) | ||||
| 	ui.current_searchbox[player_name] = search_text | ||||
| 	ui.set_inventory_formspec(player, ui.current_page[player_name]) | ||||
| end | ||||
|  | ||||
| -- Search box handling | ||||
| local function receive_fields_searchbox(player, formname, fields) | ||||
| 	local player_name = player:get_player_name() | ||||
|  | ||||
| 	-- always take new search text, even if not searching on it yet | ||||
| 	if fields.searchbox and fields.searchbox ~= ui.current_searchbox[player_name] then | ||||
| 		ui.current_searchbox[player_name] = fields.searchbox | ||||
| 	end | ||||
|  | ||||
| 	if fields.searchbutton | ||||
| 			or fields.key_enter_field == "searchbox" then | ||||
|  | ||||
| 		if ui.current_searchbox[player_name] ~= ui.activefilter[player_name] then | ||||
| 			ui.apply_filter(player, ui.current_searchbox[player_name], "nochange") | ||||
| 			ui.set_inventory_formspec(player, ui.current_page[player_name]) | ||||
| 			minetest.sound_play("paperflip2", | ||||
| 					{to_player=player_name, gain = 1.0}) | ||||
| 		end | ||||
| 	elseif fields.searchresetbutton then | ||||
| 		if ui.activefilter[player_name] ~= "" then | ||||
| 			apply_new_filter(player, "", "nochange") | ||||
| 		end | ||||
| 	end | ||||
| end | ||||
|  | ||||
| minetest.register_on_player_receive_fields(function(player, formname, fields) | ||||
| 	local player_name = player:get_player_name() | ||||
|  | ||||
| 	local ui_peruser,draw_lite_mode = unified_inventory.get_per_player_formspec(player_name) | ||||
|  | ||||
| 	if formname ~= "" then | ||||
| 		return | ||||
| 	end | ||||
|  | ||||
| 	-- always take new search text, even if not searching on it yet | ||||
| 	if fields.searchbox | ||||
| 	and fields.searchbox ~= unified_inventory.current_searchbox[player_name] then | ||||
| 		unified_inventory.current_searchbox[player_name] = fields.searchbox | ||||
| 	end | ||||
| 	receive_fields_searchbox(player, formname, fields) | ||||
|  | ||||
| 	local player_name = player:get_player_name() | ||||
|  | ||||
| 	local ui_peruser,draw_lite_mode = unified_inventory.get_per_player_formspec(player_name) | ||||
|  | ||||
| 	local clicked_category | ||||
| 	for name, value in pairs(fields) do | ||||
| @@ -89,19 +116,14 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) | ||||
| 				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, | ||||
| 	if fields.next_category or fields.prev_category then | ||||
| 		local step = fields.next_category and 1 or -1 | ||||
| 		local scroll_old = ui.current_category_scroll[player_name] | ||||
| 		local scroll_new = math.max(0, math.min(#ui.category_list - ui_peruser.pagecols, scroll_old + step)) | ||||
|  | ||||
| 		if scroll_old ~= scroll_new then | ||||
| 			ui.current_category_scroll[player_name] = scroll_new | ||||
| 			ui.set_inventory_formspec(player, | ||||
| 					unified_inventory.current_page[player_name]) | ||||
| 		end | ||||
| 	end | ||||
| @@ -109,7 +131,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) | ||||
| 	for i, def in pairs(unified_inventory.buttons) do | ||||
| 		if fields[def.name] then | ||||
| 			def.action(player) | ||||
| 			minetest.sound_play("click", | ||||
| 			minetest.sound_play("ui_click", | ||||
| 					{to_player=player_name, gain = 0.1}) | ||||
| 			return | ||||
| 		end | ||||
| @@ -158,7 +180,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) | ||||
| 	-- Check clicked item image button | ||||
| 	local clicked_item | ||||
| 	for name, value in pairs(fields) do | ||||
| 		local new_dir, mangled_item = string.match(name, "^item_button_([a-z]+)_(.*)$") | ||||
| 		local new_dir, mangled_item = string.match(name, "^[0-9]*_?item_button_([a-z]+)_(.*)$") | ||||
| 		if new_dir and mangled_item then | ||||
| 			clicked_item = unified_inventory.demangle_for_formspec(mangled_item) | ||||
| 			if string.sub(clicked_item, 1, 6) == "group:" then | ||||
| @@ -174,7 +196,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) | ||||
| 		end | ||||
| 	end | ||||
| 	if clicked_item then | ||||
| 		minetest.sound_play("click", | ||||
| 		minetest.sound_play("ui_click", | ||||
| 				{to_player=player_name, gain = 0.1}) | ||||
| 		local page = unified_inventory.current_page[player_name] | ||||
| 		local player_creative = unified_inventory.is_creative(player_name) | ||||
| @@ -196,22 +218,11 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) | ||||
| 		end | ||||
| 	end | ||||
|  | ||||
| 	if fields.searchbutton | ||||
| 			or fields.key_enter_field == "searchbox" then | ||||
| 		unified_inventory.apply_filter(player, unified_inventory.current_searchbox[player_name], "nochange") | ||||
| 		unified_inventory.set_inventory_formspec(player, | ||||
| 				unified_inventory.current_page[player_name]) | ||||
| 		minetest.sound_play("paperflip2", | ||||
| 				{to_player=player_name, gain = 1.0}) | ||||
| 	elseif fields.searchresetbutton then | ||||
| 		apply_new_filter(player, "", "nochange") | ||||
| 	end | ||||
|  | ||||
| 	-- alternate buttons | ||||
| 	if not (fields.alternate or fields.alternate_prev) then | ||||
| 		return | ||||
| 	end | ||||
| 	minetest.sound_play("click", | ||||
| 	minetest.sound_play("ui_click", | ||||
| 			{to_player=player_name, gain = 0.1}) | ||||
| 	local item_name = unified_inventory.current_item[player_name] | ||||
| 	if not item_name then | ||||
| @@ -242,11 +253,8 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) | ||||
| 			unified_inventory.current_page[player_name]) | ||||
| end) | ||||
|  | ||||
| if minetest.delete_detached_inventory then | ||||
| 	minetest.register_on_leaveplayer(function(player) | ||||
| 		local player_name = player:get_player_name() | ||||
| 		minetest.delete_detached_inventory(player_name.."_bags") | ||||
| 		minetest.delete_detached_inventory(player_name.."craftrecipe") | ||||
| 		minetest.delete_detached_inventory(player_name.."refill") | ||||
| 	end) | ||||
| end | ||||
| minetest.register_on_leaveplayer(function(player) | ||||
| 	local player_name = player:get_player_name() | ||||
| 	minetest.remove_detached_inventory(player_name.."_bags") | ||||
| 	minetest.remove_detached_inventory(player_name.."refill") | ||||
| end) | ||||
|   | ||||
| @@ -96,6 +96,9 @@ function unified_inventory.register_category(category_name, config) | ||||
| 	end | ||||
| 	update_category_list() | ||||
| end | ||||
|  | ||||
| -- TODO: Mark these for removal. They are pretty much useless | ||||
|  | ||||
| function unified_inventory.set_category_symbol(category_name, symbol) | ||||
| 	ensure_category_exists(category_name) | ||||
| 	unified_inventory.registered_categories[category_name].symbol = symbol | ||||
|   | ||||
							
								
								
									
										154
									
								
								doc/mod_api.txt
									
									
									
									
									
								
							
							
						
						| @@ -1,7 +1,8 @@ | ||||
| unified_inventory API | ||||
| ===================== | ||||
|  | ||||
| This file provides information about the API of unified_inventory. | ||||
| This file provides information about the API of unified_inventory | ||||
| and can be viewed in Markdown readers. | ||||
|  | ||||
| API revisions within unified_inventory can be checked using: | ||||
|  | ||||
| @@ -20,6 +21,66 @@ Grouped by use-case, afterwards sorted alphabetically. | ||||
| 	* Checks whether creative is enabled or the player has `creative` | ||||
|  | ||||
|  | ||||
| Callbacks | ||||
| --------- | ||||
|  | ||||
| Register a callback that will be run whenever a craft is registered via unified_inventory.register_craft. | ||||
| This callback is run before any recipe ingredients checks, hence it is also executed on recipes that are | ||||
| purged after all mods finished loading. | ||||
|  | ||||
| 	unified_inventory.register_on_craft_registered( | ||||
| 		function (item_name, options) | ||||
| 			-- item_name (string): name of the output item, equivalent to `ItemStack:get_name()` | ||||
| 			-- options (table): definition table of crafts registered by `unified_inventory.register_craft` | ||||
| 		end | ||||
| 	) | ||||
|  | ||||
| Register a callback that will be run after all mods have loaded and after the unified_inventory mod has initialised all its internal structures: | ||||
|  | ||||
| 	unified_inventory.register_on_initialized(callback) | ||||
| 		-- The callback is passed no arguments | ||||
|  | ||||
|  | ||||
| Accessing Data | ||||
| -------------- | ||||
|  | ||||
| These methods should be used instead of accessing the unified_inventory data structures directly - this will ensure your code survives any potential restructuring of the mod. | ||||
|  | ||||
| Get a list of recipes for a particular output item: | ||||
|  | ||||
| 	unified_inventory.get_recipe_list(output_item) | ||||
|  | ||||
| 	Returns a list of tables, each holding a recipe definition, like: | ||||
| 	{ | ||||
| 		{ | ||||
| 			type = "normal", | ||||
| 			items = { "default:stick", "default:stick", "default:stick", "default:stick" }, | ||||
| 			output = "default:wood", | ||||
| 			width = 2 | ||||
| 		}, | ||||
| 		{ | ||||
| 			type = "shapeless", | ||||
| 			items = { "default:tree" }, | ||||
| 			output = "default:wood 4", | ||||
| 			width = 0 | ||||
| 		}, | ||||
| 		... | ||||
| 	} | ||||
|  | ||||
| Get a list of all the output items crafts have been registered for: | ||||
|  | ||||
| 	unified_inventory.get_registered_outputs() | ||||
|  | ||||
| 	Returns a list of item names, like: | ||||
| 	{ | ||||
| 		"default:stone", | ||||
| 		"default:chest", | ||||
| 		"default:brick", | ||||
| 		"doors:door_wood", | ||||
| 		... | ||||
| 	} | ||||
|  | ||||
|  | ||||
| Pages | ||||
| ----- | ||||
|  | ||||
| @@ -105,68 +166,57 @@ Register a non-standard craft 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(name, def)` | ||||
|      * Registers a new category | ||||
|      * `name` (string): internal category name | ||||
|      * `def` (optional, table): also its fields are optional | ||||
|  | ||||
| 	unified_inventory.register_category("category_name", { | ||||
| 		symbol = "mod_name:item_name" or "texture.png", | ||||
| 		symbol = source, | ||||
| 		-- ^ Can be in the format "mod_name:item_name" or "texture.png", | ||||
| 		label = "Human Readable Label", | ||||
| 		index = 5, | ||||
| 		-- ^ Categories are sorted by index. Lower numbers appear before higher ones. | ||||
| 		--   By default, the name is translated to a number: AA -> 0.0101, ZZ -> 0.2626 | ||||
| 		---  Predefined category indices: "all" = -2, "uncategorized" = -1 | ||||
| 		items = { | ||||
| 			"mod_name:item_name", | ||||
| 			"another_mod:different_item" | ||||
| 		} | ||||
| 		-- ^ List of items within this category | ||||
| 	}) | ||||
|  * `unified_inventory.remove_category(name)` | ||||
|      * Removes an entire category | ||||
|  | ||||
| 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" | ||||
| Modifier functions (to be removed) | ||||
|  | ||||
| 	unified_inventory.set_category_symbol("category_name", "mod_name:item_name" or "texture.png") | ||||
|  * `unified_inventory.set_category_symbol(name, source)` | ||||
|      * Changes the symbol of the category. The category does not need to exist yet. | ||||
|      * `name` (string): internal category name | ||||
|      * `source` (string, optional): `"mod_name:item_name"` or `"texture.png"`. | ||||
|        Defaults to `"default:stick"` if not specified. | ||||
|  * `unified_inventory.set_category_label(name, label)` | ||||
|      * Changes the human readable label of the category. | ||||
|      * `name` (string): internal category name | ||||
|      * `label` (string): human readable label. Defaults to the category name. | ||||
|  * `unified_inventory.set_category_index(name, index)` | ||||
|      * Changes the sorting index of the category. | ||||
|      * `name` (string): internal category name | ||||
|      * `index` (numeric): any real number | ||||
|  | ||||
| Add / override the human readable label for a category: | ||||
| 	If unset this will default to the category name | ||||
| Item management | ||||
|  | ||||
| 	unified_inventory.set_category_label("category_name", "Human Readable Label") | ||||
|  * `	unified_inventory.add_category_item(name, itemname)` | ||||
|      * Adds a single item to the category | ||||
|      * `itemname` (string): self-explanatory | ||||
|  * `unified_inventory.add_category_items(name, { itemname1, itemname2, ... }` | ||||
|      * Same as above but with multiple items | ||||
|  * `unified_inventory.remove_category_item(name, itemname)` | ||||
|      * Removes an item from the category | ||||
|  * `unified_inventory.find_category(itemname)` | ||||
|      * Looks up the first category containing this item | ||||
|      * Returns: category name (string) or nil | ||||
|  * `unified_inventory.find_categories(itemname)` | ||||
|      * Looks up the item name within all registered categories | ||||
|      * Returns: array of category names (table) | ||||
|  | ||||
| Add / override the sorting index of the category: | ||||
| 	Must be a number, can also be negative (-5) or fractional (2.345) | ||||
| 	This determines the position the category appears in the list of categories | ||||
| 	The "all" meta-category has index -2, the "misc"/"uncategorized" meta-category has index -1, use a negative number smaller than these to make a category appear before these in the list | ||||
| 	By default categories are sorted alphabetically with an index between 0.0101(AA) and 0.2626(ZZ) | ||||
|  | ||||
| 	unified_inventory.set_category_index("category_name", 5) | ||||
|  | ||||
| Add a single item to a category: | ||||
|  | ||||
| 	unified_inventory.add_category_item("category_name", "mod_name:item_name") | ||||
|  | ||||
| Add multiple items to a category: | ||||
|  | ||||
| 	unified_inventory.add_category_items("category_name", { | ||||
| 		"mod_name:item_name", | ||||
| 		"another_mod:different_item" | ||||
| 	}) | ||||
|  | ||||
| Remove an item from a category: | ||||
|  | ||||
| 	unified_inventory.remove_category_item("category_name", "mod_name:item_name") | ||||
|  | ||||
| Remove a category entirely: | ||||
|  | ||||
| 	unified_inventory.remove_category("category_name") | ||||
|  | ||||
| Finding existing items in categories: | ||||
| 	This will find the first category an item exists in | ||||
| 	It should be used for checking if an item is catgorised | ||||
| 	Returns "category_name" or nil | ||||
|  | ||||
| 	unified_inventory.find_category("mod_name:item_name") | ||||
|  | ||||
|  | ||||
| 	This will find all the categories an item exists in | ||||
| 	Returns a number indexed table (list) of category names | ||||
|  | ||||
| 	unified_inventory.find_categories("mod_name:item_name") | ||||
|   | ||||
							
								
								
									
										101
									
								
								group.lua
									
									
									
									
									
								
							
							
						
						| @@ -1,29 +1,5 @@ | ||||
| local S = minetest.get_translator("unified_inventory") | ||||
|  | ||||
| function unified_inventory.canonical_item_spec_matcher(spec) | ||||
| 	local specname = ItemStack(spec):get_name() | ||||
| 	if specname:sub(1, 6) ~= "group:" then | ||||
| 		return function (itemname) | ||||
| 			return itemname == specname | ||||
| 		end | ||||
| 	end | ||||
|  | ||||
| 	local group_names = specname:sub(7):split(",") | ||||
| 	return function (itemname) | ||||
| 		local itemdef = minetest.registered_items[itemname] | ||||
| 		for _, group_name in ipairs(group_names) do | ||||
| 			if (itemdef.groups[group_name] or 0) == 0 then | ||||
| 				return false | ||||
| 			end | ||||
| 		end | ||||
| 		return true | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function unified_inventory.item_matches_spec(item, spec) | ||||
| 	local itemname = ItemStack(item):get_name() | ||||
| 	return unified_inventory.canonical_item_spec_matcher(spec)(itemname) | ||||
| end | ||||
| local ui = unified_inventory | ||||
|  | ||||
| function unified_inventory.extract_groupnames(groupname) | ||||
| 	local specname = ItemStack(groupname):get_name() | ||||
| @@ -34,22 +10,6 @@ function unified_inventory.extract_groupnames(groupname) | ||||
| 	return table.concat(group_names, S(" and ")), #group_names | ||||
| end | ||||
|  | ||||
| unified_inventory.registered_group_items = { | ||||
| 	mesecon_conductor_craftable = "mesecons:wire_00000000_off", | ||||
| 	stone = "default:cobble", | ||||
| 	wood = "default:wood", | ||||
| 	book = "default:book", | ||||
| 	sand = "default:sand", | ||||
| 	leaves = "default:leaves", | ||||
| 	tree = "default:tree", | ||||
| 	vessel = "vessels:glass_bottle", | ||||
| 	wool = "wool:white", | ||||
| } | ||||
|  | ||||
| function unified_inventory.register_group_item(groupname, itemname) | ||||
| 	unified_inventory.registered_group_items[groupname] = itemname | ||||
| end | ||||
|  | ||||
|  | ||||
| -- This is used when displaying craft recipes, where an ingredient is | ||||
| -- specified by group rather than as a specific item.  A single-item group | ||||
| @@ -67,6 +27,7 @@ end | ||||
| -- It may be a comma-separated list of group names.  This is really a | ||||
| -- "group:..." ingredient specification, minus the "group:" prefix. | ||||
|  | ||||
| -- TODO Replace this with the more efficient spec matcher (below) | ||||
| local function compute_group_item(group_name_list) | ||||
| 	local group_names = group_name_list:split(",") | ||||
| 	local candidate_items = {} | ||||
| @@ -125,3 +86,61 @@ function unified_inventory.get_group_item(group_name) | ||||
| 	return group_item_cache[group_name] | ||||
| end | ||||
|  | ||||
|  | ||||
| --[[ | ||||
| This is for filtering known items by groups | ||||
| e.g. find all items that match "group:flower,yellow" (flower AND yellow groups) | ||||
| ]] | ||||
| local spec_matcher = {} | ||||
| function unified_inventory.init_matching_cache() | ||||
| 	for _, name in ipairs(ui.items_list) do | ||||
| 		-- we only need to care about groups, exact items are handled separately | ||||
| 		for group, value in pairs(minetest.registered_items[name].groups) do | ||||
| 			if value and value ~= 0 then | ||||
| 				if not spec_matcher[group] then | ||||
| 					spec_matcher[group] = {} | ||||
| 				end | ||||
| 				spec_matcher[group][name] = true | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
| end | ||||
|  | ||||
| --[[ | ||||
| Retrieves all matching items | ||||
|  | ||||
| Arguments: | ||||
| 	specname (string): Item name or group(s) to filter | ||||
|  | ||||
| Output: | ||||
| 	{ | ||||
| 		matchingitem1 = true, | ||||
| 		... | ||||
| 	} | ||||
| ]] | ||||
| function unified_inventory.get_matching_items(specname) | ||||
| 	if specname:sub(1,6) ~= "group:" then | ||||
| 		return { [specname] = true } | ||||
| 	end | ||||
|  | ||||
| 	local accepted = {} | ||||
| 	for i, group in ipairs(specname:sub(7):split(",")) do | ||||
| 		if i == 1 then | ||||
| 			-- First step: Copy all possible item names in this group | ||||
| 			for name, _ in pairs(spec_matcher[group] or {}) do | ||||
| 				accepted[name] = true | ||||
| 			end | ||||
| 		else | ||||
| 			-- Perform filtering | ||||
| 			if spec_matcher[group] then | ||||
| 				for name, _ in pairs(accepted) do | ||||
| 					accepted[name] = spec_matcher[group][name] | ||||
| 				end | ||||
| 			else | ||||
| 				-- No matching items | ||||
| 				return {} | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
| 	return accepted | ||||
| end | ||||
|   | ||||
							
								
								
									
										54
									
								
								init.lua
									
									
									
									
									
								
							
							
						
						| @@ -1,5 +1,11 @@ | ||||
| -- Unified Inventory | ||||
|  | ||||
| if not minetest.features.formspec_version_element then | ||||
| 	-- At least formspec_version[] is the minimal feature requirement | ||||
| 	error("Unified Inventory requires Minetest version 5.4.0 or newer.\n" .. | ||||
| 		" Please update Minetest or use an older version of Unified Inventory.") | ||||
| end | ||||
|  | ||||
| local modpath = minetest.get_modpath(minetest.get_current_modname()) | ||||
| local worldpath = minetest.get_worldpath() | ||||
|  | ||||
| @@ -24,6 +30,8 @@ unified_inventory = { | ||||
| 	filtered_items_list = {}, | ||||
| 	pages = {}, | ||||
| 	buttons = {}, | ||||
| 	initialized_callbacks = {}, | ||||
| 	craft_registered_callbacks = {}, | ||||
|  | ||||
| 	-- Homepos stuff | ||||
| 	home_pos = {}, | ||||
| @@ -42,8 +50,12 @@ unified_inventory = { | ||||
| 	trash_enabled = (minetest.settings:get_bool("unified_inventory_trash") ~= false), | ||||
| 	imgscale = 1.25, | ||||
| 	list_img_offset = 0.13, | ||||
| 	standard_background = "background9[0,0;1,1;ui_formbg_9_sliced.png;true;16]", | ||||
| 	version = 2 | ||||
| 	standard_background = "bgcolor[#0000]background9[0,0;1,1;ui_formbg_9_sliced.png;true;16]", | ||||
|  | ||||
| 	hide_disabled_buttons = minetest.settings:get_bool("unified_inventory_hide_disabled_buttons", false), | ||||
| 	hide_uncraftable_items = minetest.settings:get_bool("unified_inventory_hide_uncraftable_items", false), | ||||
|  | ||||
| 	version = 5 | ||||
| } | ||||
|  | ||||
| local ui = unified_inventory | ||||
| @@ -56,10 +68,16 @@ ui.style_full = { | ||||
| 	formspec_y = 1, | ||||
| 	formw = 17.75, | ||||
| 	formh = 12.25, | ||||
| 	-- Item browser size, pos | ||||
| 	pagecols = 8, | ||||
| 	pagerows = 9, | ||||
| 	page_x = 10.75, | ||||
| 	page_y = 2.30, | ||||
| 	-- Item browser controls | ||||
| 	page_buttons_x = 11.60, | ||||
| 	page_buttons_y = 10.15, | ||||
| 	searchwidth = 3.4, | ||||
| 	-- Crafting grid positions | ||||
| 	craft_x = 2.8, | ||||
| 	craft_y = 1.15, | ||||
| 	craftresult_x = 7.8, | ||||
| @@ -71,13 +89,15 @@ ui.style_full = { | ||||
| 	craft_guide_resultstr_x = 0.3, | ||||
| 	craft_guide_resultstr_y = 0.6, | ||||
| 	give_btn_x = 0.25, | ||||
| 	-- Tab switching buttons | ||||
| 	main_button_x = 0.4, | ||||
| 	main_button_y = 11.0, | ||||
| 	page_buttons_x = 11.60, | ||||
| 	page_buttons_y = 10.15, | ||||
| 	searchwidth = 3.4, | ||||
| 	main_button_cols = 12, | ||||
| 	main_button_rows = 1, | ||||
| 	-- Tab title position | ||||
| 	form_header_x = 0.4, | ||||
| 	form_header_y = 0.4, | ||||
| 	-- Generic sizes | ||||
| 	btn_spc = 0.85, | ||||
| 	btn_size = 0.75, | ||||
| 	std_inv_x = 0.3, | ||||
| @@ -89,10 +109,16 @@ ui.style_lite = { | ||||
| 	formspec_y =  0.6, | ||||
| 	formw = 14, | ||||
| 	formh = 9.75, | ||||
| 	-- Item browser size, pos | ||||
| 	pagecols = 4, | ||||
| 	pagerows = 5, | ||||
| 	page_x = 10.5, | ||||
| 	page_y = 2.15, | ||||
| 	-- Item browser controls | ||||
| 	page_buttons_x = 10.5, | ||||
| 	page_buttons_y = 6.15, | ||||
| 	searchwidth = 1.6, | ||||
| 	-- Crafting grid positions | ||||
| 	craft_x = 2.6, | ||||
| 	craft_y = 0.75, | ||||
| 	craftresult_x = 5.75, | ||||
| @@ -104,13 +130,15 @@ ui.style_lite = { | ||||
| 	craft_guide_resultstr_x = 0.15, | ||||
| 	craft_guide_resultstr_y = 0.35, | ||||
| 	give_btn_x = 0.15, | ||||
| 	-- Tab switching buttons | ||||
| 	main_button_x = 10.5, | ||||
| 	main_button_y = 8.15, | ||||
| 	page_buttons_x = 10.5, | ||||
| 	page_buttons_y = 6.15, | ||||
| 	searchwidth = 1.6, | ||||
| 	main_button_cols = 4, | ||||
| 	main_button_rows = 2, | ||||
| 	-- Tab title position | ||||
| 	form_header_x =  0.2, | ||||
| 	form_header_y =  0.2, | ||||
| 	-- Generic sizes | ||||
| 	btn_spc = 0.8, | ||||
| 	btn_size = 0.7, | ||||
| 	std_inv_x = 0.1, | ||||
| @@ -164,6 +192,10 @@ dofile(modpath.."/register.lua") | ||||
| if minetest.settings:get_bool("unified_inventory_bags") ~= false then | ||||
| 	dofile(modpath.."/bags.lua") | ||||
| end | ||||
|  | ||||
| dofile(modpath.."/item_names.lua") | ||||
| dofile(modpath.."/waypoints.lua") | ||||
| if minetest.settings:get_bool("unified_inventory_item_names") ~= false then | ||||
| 	dofile(modpath.."/item_names.lua") | ||||
| end | ||||
| if minetest.settings:get_bool("unified_inventory_waypoints") ~= false then | ||||
| 	dofile(modpath.."/waypoints.lua") | ||||
| end | ||||
| dofile(modpath.."/legacy.lua") -- mod compatibility | ||||
|   | ||||
							
								
								
									
										476
									
								
								internal.lua
									
									
									
									
									
								
							
							
						
						| @@ -18,13 +18,16 @@ function ui.demangle_for_formspec(str) | ||||
| 	return string.gsub(str, "_([0-9]+)_", function (v) return string.char(v) end) | ||||
| end | ||||
|  | ||||
|  | ||||
| -- Get the player-specific unified_inventory style | ||||
| function ui.get_per_player_formspec(player_name) | ||||
| 	local draw_lite_mode = ui.lite_mode and not minetest.check_player_privs(player_name, {ui_full=true}) | ||||
|  | ||||
| 	return table.copy(draw_lite_mode and ui.style_lite or ui.style_full), draw_lite_mode | ||||
| 	local style = table.copy(draw_lite_mode and ui.style_lite or ui.style_full) | ||||
| 	style.is_lite_mode = draw_lite_mode | ||||
| 	return style | ||||
| end | ||||
|  | ||||
| -- Creates an item image or regular image button with a tooltip | ||||
| local function formspec_button(ui_peruser, name, image, offset, pos, scale, label) | ||||
| 	local element = 'image_button' | ||||
| 	if minetest.registered_items[image] then | ||||
| @@ -41,113 +44,103 @@ local function formspec_button(ui_peruser, name, image, offset, pos, scale, labe | ||||
| 		string.format("tooltip[%s;%s]", name, F(label or name)) | ||||
| end | ||||
|  | ||||
| function ui.get_formspec(player, page) | ||||
|  | ||||
| 	if not player then | ||||
| 		return "" | ||||
| 	end | ||||
|  | ||||
| 	local player_name = player:get_player_name() | ||||
| 	local ui_peruser,draw_lite_mode = ui.get_per_player_formspec(player_name) | ||||
|  | ||||
| 	ui.current_page[player_name] = page | ||||
| 	local pagedef = ui.pages[page] | ||||
|  | ||||
| 	if not pagedef then | ||||
| 		return "" -- Invalid page name | ||||
| 	end | ||||
|  | ||||
| 	local formspec = { | ||||
| 		"formspec_version[4]", | ||||
| 		"size["..ui_peruser.formw..","..ui_peruser.formh.."]", | ||||
| 		pagedef.formspec_prepend and "" or "no_prepend[]", | ||||
| 		ui.standard_background | ||||
| 	} | ||||
|  | ||||
| 	local n = 5 | ||||
|  | ||||
| 	local perplayer_formspec = ui.get_per_player_formspec(player_name) | ||||
| 	local fsdata = pagedef.get_formspec(player, perplayer_formspec) | ||||
|  | ||||
| 	formspec[n] = fsdata.formspec | ||||
| 	n = n+1 | ||||
|  | ||||
| 	local button_row = 0 | ||||
| 	local button_col = 0 | ||||
| -- Add registered buttons (tabs) | ||||
| local function formspec_tab_buttons(player, formspec, style) | ||||
| 	local n = #formspec + 1 | ||||
|  | ||||
| 	-- Main buttons | ||||
|  | ||||
| 	local filtered_inv_buttons = {} | ||||
|  | ||||
| 	for i, def in pairs(ui.buttons) do | ||||
| 		if not (draw_lite_mode and def.hide_lite) then | ||||
| 			table.insert(filtered_inv_buttons, def) | ||||
| 		end | ||||
| 	end | ||||
|  | ||||
| 	for i, def in pairs(filtered_inv_buttons) do | ||||
|  | ||||
| 		if draw_lite_mode and i > 4 then | ||||
| 			button_row = 1 | ||||
| 			button_col = 1 | ||||
| 		end | ||||
|  | ||||
| 		if def.type == "image" then | ||||
| 			if (def.condition == nil or def.condition(player) == true) then | ||||
| 				formspec[n] = string.format("image_button[%f,%f;%f,%f;%s;%s;]", | ||||
| 					ui_peruser.main_button_x + ui_peruser.btn_spc * (i - 1) - button_col * ui_peruser.btn_spc * 4, | ||||
| 					ui_peruser.main_button_y + button_row * ui_peruser.btn_spc, | ||||
| 					ui_peruser.btn_size,ui_peruser.btn_size, | ||||
| 					F(def.image), | ||||
| 					F(def.name)) | ||||
| 				formspec[n+1] = "tooltip["..F(def.name)..";"..(def.tooltip or "").."]" | ||||
| 				n = n+2 | ||||
| 			else | ||||
| 				formspec[n] = string.format("image[%f,%f;%f,%f;%s^[colorize:#808080:alpha]", | ||||
| 				ui_peruser.main_button_x + ui_peruser.btn_spc * (i - 1) - button_col * ui_peruser.btn_spc * 4, | ||||
| 				ui_peruser.main_button_y + button_row * ui_peruser.btn_spc, | ||||
| 				ui_peruser.btn_size,ui_peruser.btn_size,def.image) | ||||
| 				n = n+1 | ||||
| 	for _, def in pairs(ui.buttons) do | ||||
| 		if not (style.is_lite_mode and def.hide_lite) then | ||||
| 			if def.condition == nil or def.condition(player) or not ui.hide_disabled_buttons then | ||||
| 				table.insert(filtered_inv_buttons, def) | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
|  | ||||
| 	if fsdata.draw_inventory ~= false then | ||||
| 		-- Player inventory | ||||
| 		formspec[n] = "listcolors[#00000000;#00000000]" | ||||
| 		formspec[n+1] = ui_peruser.standard_inv | ||||
| 		n = n+2 | ||||
| 	local needs_scrollbar = #filtered_inv_buttons > style.main_button_cols * style.main_button_rows | ||||
|  | ||||
| 	formspec[n] = ("scroll_container[%g,%g;%g,%g;tabbtnscroll;vertical]"):format( | ||||
| 		style.main_button_x, style.main_button_y, -- position | ||||
| 		style.main_button_cols * style.btn_spc, style.main_button_rows -- size | ||||
| 	) | ||||
| 	n = n + 1 | ||||
|  | ||||
| 	for i, def in pairs(filtered_inv_buttons) do | ||||
| 		local pos_x =           ((i - 1) % style.main_button_cols) * style.btn_spc | ||||
| 		local pos_y = math.floor((i - 1) / style.main_button_cols) * style.btn_spc | ||||
|  | ||||
| 		if def.type == "image" then | ||||
| 			if (def.condition == nil or def.condition(player)) then | ||||
| 				formspec[n] = string.format("image_button[%g,%g;%g,%g;%s;%s;]", | ||||
| 					pos_x, pos_y, style.btn_size, style.btn_size, | ||||
| 					F(def.image), | ||||
| 					F(def.name)) | ||||
| 				formspec[n+1] = "tooltip["..F(def.name)..";"..(def.tooltip or "").."]" | ||||
| 				n = n+2 | ||||
|  | ||||
| 			else | ||||
| 				formspec[n] = string.format("image[%g,%g;%g,%g;%s^[colorize:#808080:alpha]", | ||||
| 					pos_x, pos_y, style.btn_size, style.btn_size, | ||||
| 					def.image) | ||||
| 				n = n+1 | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
|  | ||||
| 	if fsdata.draw_item_list == false then | ||||
| 		return table.concat(formspec, "") | ||||
| 	formspec[n] = "scroll_container_end[]" | ||||
| 	if needs_scrollbar then | ||||
| 		local total_rows = math.ceil(#filtered_inv_buttons / style.main_button_cols) | ||||
| 		formspec[n+1] = ("scrollbaroptions[max=%i;arrows=hide]"):format( | ||||
| 			-- This calculation is not 100% accurate but "good enough" | ||||
| 			(total_rows - style.main_button_rows) * style.btn_spc * 10 | ||||
| 		) | ||||
| 		formspec[n+2] = ("scrollbar[%g,%g;0.4,%g;vertical;tabbtnscroll;0]"):format( | ||||
| 			style.main_button_x + style.main_button_cols * style.btn_spc - 0.1, -- x pos | ||||
| 			style.main_button_y, -- y pos | ||||
| 			style.main_button_rows * style.btn_spc -- height | ||||
| 		) | ||||
| 		formspec[n+3] = "scrollbaroptions[max=1000;arrows=default]" | ||||
| 	end | ||||
| end | ||||
|  | ||||
| 	-- Category filters | ||||
| -- Add category GUI elements (top right) | ||||
| local function formspec_add_categories(player, formspec, ui_peruser) | ||||
| 	local player_name = player:get_player_name() | ||||
| 	local n = #formspec + 1 | ||||
|  | ||||
| 	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) } | ||||
| 	local categories_pos = { | ||||
| 		ui_peruser.page_x, | ||||
| 		ui_peruser.page_y-ui_peruser.btn_spc-0.5 | ||||
| 	} | ||||
| 	local categories_scroll_pos = { | ||||
| 		ui_peruser.page_x, | ||||
| 		ui_peruser.form_header_y - (ui_peruser.is_lite_mode and 0 or 0.2) | ||||
| 	} | ||||
|  | ||||
| 	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), | ||||
| 	formspec[n] = string.format("background9[%f,%f;%f,%f;%s;false;16]", | ||||
| 		ui_peruser.page_x-0.15, categories_scroll_pos[2], | ||||
| 		(ui_peruser.btn_spc * ui_peruser.pagecols) + 0.2, 1.4 + (ui_peruser.is_lite_mode and 0 or 0.2), | ||||
| 		"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:") | ||||
| 	formspec[n] = string.format("label[%f,%f;%s]", | ||||
| 		ui_peruser.page_x, | ||||
| 		ui_peruser.form_header_y + (ui_peruser.is_lite_mode and 0.3 or 0.2), F(S("Category:"))) | ||||
| 	n = n + 1 | ||||
|  | ||||
| 	local scroll_offset = 0 | ||||
| 	local category_count = #unified_inventory.category_list | ||||
| 	local category_count = #ui.category_list | ||||
| 	if category_count > ui_peruser.pagecols then | ||||
| 		scroll_offset = unified_inventory.current_category_scroll[player_name] | ||||
| 		scroll_offset = ui.current_category_scroll[player_name] | ||||
| 	end | ||||
|  | ||||
| 	for index, category in ipairs(unified_inventory.category_list) do | ||||
| 	for index, category in ipairs(ui.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 | ||||
| 			if ui.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) | ||||
| @@ -162,10 +155,13 @@ function ui.get_formspec(player, page) | ||||
| 	if category_count > ui_peruser.pagecols and category_count - scroll_offset > ui_peruser.pagecols then | ||||
| 		-- next | ||||
| 		formspec[n] = formspec_button(ui_peruser, "next_category", "ui_right_icon.png", categories_scroll_pos, {ui_peruser.pagecols - 1, 0}, 0.8, S("Scroll categories right")) | ||||
| 		n = n + 1 | ||||
| 	end | ||||
| end | ||||
|  | ||||
| local function formspec_add_search_box(player, formspec, ui_peruser) | ||||
| 	local player_name = player:get_player_name() | ||||
| 	local n = #formspec + 1 | ||||
|  | ||||
| 	-- Search box | ||||
| 	formspec[n] = "field_close_on_enter[searchbox;false]" | ||||
|  | ||||
| 	formspec[n+1] = string.format("field[%f,%f;%f,%f;searchbox;;%s]", | ||||
| @@ -182,7 +178,16 @@ function ui.get_formspec(player, page) | ||||
| 		ui_peruser.btn_size, ui_peruser.btn_size) | ||||
| 	formspec[n+5] = "tooltip[searchresetbutton;"..F(S("Reset search and display everything")).."]" | ||||
|  | ||||
| 	n = n + 6 | ||||
| 	if ui.activefilter[player_name] ~= "" then | ||||
| 		formspec[n+6] = string.format("label[%f,%f;%s: %s]", | ||||
| 			ui_peruser.page_x, ui_peruser.page_y - 0.25, | ||||
| 			F(S("Filter")), F(ui.activefilter[player_name])) | ||||
| 	end | ||||
| end | ||||
|  | ||||
| local function formspec_add_item_browser(player, formspec, ui_peruser) | ||||
| 	local player_name = player:get_player_name() | ||||
| 	local n = #formspec + 1 | ||||
|  | ||||
| 	-- Controls to flip items pages | ||||
|  | ||||
| @@ -195,9 +200,9 @@ function ui.get_formspec(player, page) | ||||
| 		{ "ui_skip_forward_icon.png",  "end_list",   S("Last page") }, | ||||
| 	} | ||||
|  | ||||
| 	if draw_lite_mode then | ||||
| 		btnlist[5] = nil | ||||
| 	if ui_peruser.is_lite_mode then | ||||
| 		btnlist[2] = nil | ||||
| 		btnlist[5] = nil | ||||
| 	end | ||||
|  | ||||
| 	local bn = 0 | ||||
| @@ -212,69 +217,116 @@ function ui.get_formspec(player, page) | ||||
| 		n = n + 2 | ||||
| 	end | ||||
|  | ||||
| 	local no_matches = S("No matching items") | ||||
| 	if draw_lite_mode then | ||||
| 		no_matches = S("No matches.") | ||||
| 	end | ||||
|  | ||||
| 	-- Items list | ||||
| 	if #ui.filtered_items_list[player_name] == 0 then | ||||
| 		formspec[n] = "label["..ui_peruser.page_x..","..(ui_peruser.page_y+0.15)..";" .. F(no_matches) .. "]" | ||||
| 	else | ||||
| 		local dir = ui.active_search_direction[player_name] | ||||
| 		local list_index = ui.current_index[player_name] | ||||
| 		local page2 = math.floor(list_index / (ui_peruser.items_per_page) + 1) | ||||
| 		local pagemax = math.floor( | ||||
| 			(#ui.filtered_items_list[player_name] - 1) | ||||
| 				/ (ui_peruser.items_per_page) + 1) | ||||
| 		for y = 0, ui_peruser.pagerows - 1 do | ||||
| 			for x = 0, ui_peruser.pagecols - 1 do | ||||
| 				local name = ui.filtered_items_list[player_name][list_index] | ||||
| 				local item = minetest.registered_items[name] | ||||
| 				if item then | ||||
| 					-- Clicked on current item: Flip crafting direction | ||||
| 					if name == ui.current_item[player_name] then | ||||
| 						local cdir = ui.current_craft_direction[player_name] | ||||
| 						if cdir == "recipe" then | ||||
| 							dir = "usage" | ||||
| 						elseif cdir == "usage" then | ||||
| 							dir = "recipe" | ||||
| 						end | ||||
| 					else | ||||
| 					-- Default: use active search direction by default | ||||
| 						dir = ui.active_search_direction[player_name] | ||||
| 					end | ||||
|  | ||||
| 					local button_name = "item_button_" .. dir .. "_" | ||||
| 						.. ui.mangle_for_formspec(name) | ||||
| 					formspec[n] = ("item_image_button[%f,%f;%f,%f;%s;%s;]"):format( | ||||
| 						ui_peruser.page_x + x * ui_peruser.btn_spc, | ||||
| 						ui_peruser.page_y + y * ui_peruser.btn_spc, | ||||
| 						ui_peruser.btn_size, ui_peruser.btn_size, | ||||
| 						name, button_name | ||||
| 					) | ||||
| 					formspec[n + 1] = ("tooltip[%s;%s \\[%s\\]]"):format( | ||||
| 						button_name, minetest.formspec_escape(item.description), | ||||
| 						item.mod_origin or "??" | ||||
| 					) | ||||
| 					n = n + 2 | ||||
| 					list_index = list_index + 1 | ||||
| 				end | ||||
| 			end | ||||
| 		local no_matches = S("No matching items") | ||||
| 		if ui_peruser.is_lite_mode then | ||||
| 			no_matches = S("No matches.") | ||||
| 		end | ||||
| 		formspec[n] = string.format("label[%f,%f;%s: %s]", | ||||
| 			ui_peruser.page_buttons_x + ui_peruser.btn_spc * (draw_lite_mode and 1 or 2), | ||||
| 			ui_peruser.page_buttons_y + 0.1 + ui_peruser.btn_spc * 2, | ||||
| 			F(S("Page")), S("@1 of @2",page2,pagemax)) | ||||
| 	end | ||||
| 	n= n+1 | ||||
|  | ||||
| 	if ui.activefilter[player_name] ~= "" then | ||||
| 		formspec[n] = string.format("label[%f,%f;%s: %s]", | ||||
| 			ui_peruser.page_x, ui_peruser.page_y - 0.25, | ||||
| 			F(S("Filter")), F(ui.activefilter[player_name])) | ||||
| 		formspec[n] = "label["..ui_peruser.page_x..","..(ui_peruser.page_y+0.15)..";" .. F(no_matches) .. "]" | ||||
| 		return | ||||
| 	end | ||||
| 	return table.concat(formspec, "") | ||||
|  | ||||
| 	local dir = ui.active_search_direction[player_name] | ||||
| 	local list_index = ui.current_index[player_name] | ||||
| 	local page2 = math.floor(list_index / (ui_peruser.items_per_page) + 1) | ||||
| 	local pagemax = math.floor( | ||||
| 		(#ui.filtered_items_list[player_name] - 1) | ||||
| 			/ (ui_peruser.items_per_page) + 1) | ||||
| 	for y = 0, ui_peruser.pagerows - 1 do | ||||
| 		for x = 0, ui_peruser.pagecols - 1 do | ||||
| 			local name = ui.filtered_items_list[player_name][list_index] | ||||
| 			local item = minetest.registered_items[name] | ||||
| 			if item then | ||||
| 				-- Clicked on current item: Flip crafting direction | ||||
| 				if name == ui.current_item[player_name] then | ||||
| 					local cdir = ui.current_craft_direction[player_name] | ||||
| 					if cdir == "recipe" then | ||||
| 						dir = "usage" | ||||
| 					elseif cdir == "usage" then | ||||
| 						dir = "recipe" | ||||
| 					end | ||||
| 				else | ||||
| 				-- Default: use active search direction by default | ||||
| 					dir = ui.active_search_direction[player_name] | ||||
| 				end | ||||
|  | ||||
| 				local button_name = "item_button_" .. dir .. "_" | ||||
| 					.. ui.mangle_for_formspec(name) | ||||
| 				formspec[n] = ("item_image_button[%f,%f;%f,%f;%s;%s;]"):format( | ||||
| 					ui_peruser.page_x + x * ui_peruser.btn_spc, | ||||
| 					ui_peruser.page_y + y * ui_peruser.btn_spc, | ||||
| 					ui_peruser.btn_size, ui_peruser.btn_size, | ||||
| 					name, button_name | ||||
| 				) | ||||
| 				local tooltip = item.description | ||||
| 				if item.mod_origin then | ||||
| 					-- "mod_origin" may not be specified for items that were | ||||
| 					-- registered in a callback (during or before ServerEnv init) | ||||
| 					tooltip = tooltip .. " [" .. item.mod_origin .. "]" | ||||
| 				end | ||||
| 				formspec[n + 1] = ("tooltip[%s;%s]"):format( | ||||
| 					button_name, minetest.formspec_escape(tooltip) | ||||
| 				) | ||||
| 				n = n + 2 | ||||
| 			end | ||||
| 			list_index = list_index + 1 | ||||
| 		end | ||||
| 	end | ||||
| 	formspec[n] = "style[page_number;content_offset=0]" | ||||
| 	formspec[n + 1] = string.format("image_button[%f,%f;%f,0.4;;page_number;%s: %s;false;false;]", | ||||
| 		ui_peruser.page_buttons_x, | ||||
| 		ui_peruser.page_buttons_y + ui_peruser.btn_spc * 2 - 0.1, | ||||
| 		ui_peruser.btn_spc * (bn - 1) + ui_peruser.btn_size, | ||||
| 		F(S("Page")), S("@1 of @2",page2,pagemax)) | ||||
| end | ||||
|  | ||||
| function ui.get_formspec(player, page) | ||||
|  | ||||
| 	if not player then | ||||
| 		return "" | ||||
| 	end | ||||
|  | ||||
| 	local player_name = player:get_player_name() | ||||
| 	local ui_peruser = ui.get_per_player_formspec(player_name) | ||||
|  | ||||
| 	ui.current_page[player_name] = page | ||||
| 	local pagedef = ui.pages[page] | ||||
|  | ||||
| 	if not pagedef then | ||||
| 		return "" -- Invalid page name | ||||
| 	end | ||||
|  | ||||
| 	local fs = { | ||||
| 		"formspec_version[4]", | ||||
| 		"size["..ui_peruser.formw..","..ui_peruser.formh.."]", | ||||
| 		pagedef.formspec_prepend and "" or "no_prepend[]", | ||||
| 		ui.standard_background | ||||
| 	} | ||||
|  | ||||
| 	local perplayer_formspec = ui.get_per_player_formspec(player_name) | ||||
| 	local fsdata = pagedef.get_formspec(player, perplayer_formspec) | ||||
|  | ||||
| 	fs[#fs + 1] = fsdata.formspec | ||||
|  | ||||
| 	formspec_tab_buttons(player, fs, ui_peruser) | ||||
|  | ||||
| 	if fsdata.draw_inventory ~= false then | ||||
| 		-- Player inventory | ||||
| 		fs[#fs + 1] = "listcolors[#00000000;#00000000]" | ||||
| 		fs[#fs + 1] = ui_peruser.standard_inv | ||||
| 	end | ||||
|  | ||||
| 	if fsdata.draw_item_list == false then | ||||
| 		return table.concat(fs, "") | ||||
| 	end | ||||
|  | ||||
| 	formspec_add_categories(player, fs, ui_peruser) | ||||
| 	formspec_add_search_box(player, fs, ui_peruser) | ||||
| 	formspec_add_item_browser(player, fs, ui_peruser) | ||||
|  | ||||
| 	return table.concat(fs) | ||||
| end | ||||
|  | ||||
| function ui.set_inventory_formspec(player, page) | ||||
| @@ -283,7 +335,7 @@ function ui.set_inventory_formspec(player, page) | ||||
| 	end | ||||
| end | ||||
|  | ||||
| local function valid_def(def) | ||||
| function ui.is_itemdef_listable(def) | ||||
| 	return (not def.groups.not_in_creative_inventory | ||||
| 			or def.groups.not_in_creative_inventory == 0) | ||||
| 		and def.description | ||||
| @@ -296,11 +348,26 @@ function ui.apply_filter(player, filter, search_dir) | ||||
| 		return false | ||||
| 	end | ||||
| 	local player_name = player:get_player_name() | ||||
|  | ||||
| 	-- Whether to show uncraftable items | ||||
| 	local fprefilter = function(_) | ||||
| 		return true | ||||
| 	end | ||||
| 	if ui.hide_uncraftable_items and not ui.is_creative(player_name) then | ||||
| 		fprefilter = function(name) | ||||
| 			return ui.get_recipe_list(name) | ||||
| 		end | ||||
| 	end | ||||
|  | ||||
| 	local registered_items = minetest.registered_items | ||||
| 	local lfilter = string.lower(filter) | ||||
| 	local ffilter | ||||
|  | ||||
| 	if lfilter:sub(1, 6) == "group:" then | ||||
| 		-- Group filter: all groups of the item must match | ||||
| 		local groups = lfilter:sub(7):split(",") | ||||
| 		ffilter = function(name, def) | ||||
| 		ffilter = function(name) | ||||
| 			local def = registered_items[name] | ||||
| 			for _, group in ipairs(groups) do | ||||
| 				if not def.groups[group] | ||||
| 				or def.groups[group] <= 0 then | ||||
| @@ -310,8 +377,12 @@ function ui.apply_filter(player, filter, search_dir) | ||||
| 			return true | ||||
| 		end | ||||
| 	else | ||||
| 		local lang = minetest.get_player_information(player_name).lang_code | ||||
| 		ffilter = function(name, def) | ||||
| 		-- Name filter: fuzzy match item names and descriptions | ||||
| 		local player_info = minetest.get_player_information(player_name) | ||||
| 		local lang = player_info and player_info.lang_code or "" | ||||
|  | ||||
| 		ffilter = function(name) | ||||
| 			local def = registered_items[name] | ||||
| 			local lname = string.lower(name) | ||||
| 			local ldesc = string.lower(def.description) | ||||
| 			local llocaldesc = minetest.get_translated_string | ||||
| @@ -320,95 +391,50 @@ function ui.apply_filter(player, filter, search_dir) | ||||
| 				or llocaldesc and string.find(llocaldesc, lfilter, 1, true) | ||||
| 		end | ||||
| 	end | ||||
| 	ui.filtered_items_list[player_name]={} | ||||
|  | ||||
| 	local filtered_items = {} | ||||
|  | ||||
| 	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) | ||||
| 		for _, name in ipairs(ui.items_list) do | ||||
| 			if fprefilter(name) and ffilter(name) then | ||||
| 				table.insert(filtered_items, 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) | ||||
| 		for _, name in ipairs(ui.items_list) do | ||||
| 			if not ui.find_category(name) | ||||
| 					and fprefilter(name) | ||||
| 					and ffilter(name) then | ||||
| 				table.insert(filtered_items, 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) | ||||
| 		-- Any other category is selected | ||||
| 		for name, exists in pairs(ui.registered_category_items[category]) do | ||||
| 			if exists | ||||
| 					and fprefilter(name) | ||||
| 					and ffilter(name) then | ||||
| 				table.insert(filtered_items, name) | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
| 	table.sort(ui.filtered_items_list[player_name]) | ||||
| 	ui.filtered_items_list_size[player_name] = #ui.filtered_items_list[player_name] | ||||
| 	table.sort(filtered_items) | ||||
|  | ||||
| 	ui.filtered_items_list_size[player_name] = #filtered_items | ||||
| 	ui.filtered_items_list[player_name] = filtered_items | ||||
| 	ui.current_index[player_name] = 1 | ||||
| 	ui.activefilter[player_name] = filter | ||||
| 	ui.active_search_direction[player_name] = search_dir | ||||
| 	ui.set_inventory_formspec(player, | ||||
| 	ui.current_page[player_name]) | ||||
| 	ui.set_inventory_formspec(player, ui.current_page[player_name]) | ||||
| end | ||||
|  | ||||
| function ui.items_in_group(groups) | ||||
| 	local items = {} | ||||
| 	for name, item in pairs(minetest.registered_items) do | ||||
| 		for _, group in pairs(groups:split(',')) do | ||||
| 			if item.groups[group] then | ||||
| 				table.insert(items, name) | ||||
| 			end | ||||
| 		end | ||||
| -- Inform players about potential visual issues | ||||
| minetest.register_on_joinplayer(function(player) | ||||
| 	local player_name = player:get_player_name() | ||||
| 	local info = minetest.get_player_information(player_name) | ||||
| 	if info and (info.formspec_version or 0) < 4 then | ||||
| 		minetest.chat_send_player(player_name, S("Unified Inventory: Your game version is too old" | ||||
| 			.. " and does not support the GUI requirements. You might experience visual issues.")) | ||||
| 	end | ||||
| 	return items | ||||
| end | ||||
|  | ||||
| function ui.sort_inventory(inv) | ||||
| 	local inlist = inv:get_list("main") | ||||
| 	local typecnt = {} | ||||
| 	local typekeys = {} | ||||
| 	for _, st in ipairs(inlist) do | ||||
| 		if not st:is_empty() then | ||||
| 			local n = st:get_name() | ||||
| 			local w = st:get_wear() | ||||
| 			local m = st:get_metadata() | ||||
| 			local k = string.format("%s %05d %s", n, w, m) | ||||
| 			if not typecnt[k] then | ||||
| 				typecnt[k] = { | ||||
| 					name = n, | ||||
| 					wear = w, | ||||
| 					metadata = m, | ||||
| 					stack_max = st:get_stack_max(), | ||||
| 					count = 0, | ||||
| 				} | ||||
| 				table.insert(typekeys, k) | ||||
| 			end | ||||
| 			typecnt[k].count = typecnt[k].count + st:get_count() | ||||
| 		end | ||||
| 	end | ||||
| 	table.sort(typekeys) | ||||
| 	local outlist = {} | ||||
| 	for _, k in ipairs(typekeys) do | ||||
| 		local tc = typecnt[k] | ||||
| 		while tc.count > 0 do | ||||
| 			local c = math.min(tc.count, tc.stack_max) | ||||
| 			table.insert(outlist, ItemStack({ | ||||
| 				name = tc.name, | ||||
| 				wear = tc.wear, | ||||
| 				metadata = tc.metadata, | ||||
| 				count = c, | ||||
| 			})) | ||||
| 			tc.count = tc.count - c | ||||
| 		end | ||||
| 	end | ||||
| 	if #outlist > #inlist then return end | ||||
| 	while #outlist < #inlist do | ||||
| 		table.insert(outlist, ItemStack(nil)) | ||||
| 	end | ||||
| 	inv:set_list("main", outlist) | ||||
| end | ||||
| end) | ||||
|   | ||||
| @@ -3,6 +3,8 @@ | ||||
| local item_names = {} -- [player_name] = { hud, dtime, itemname } | ||||
| local dlimit = 3  -- HUD element will be hidden after this many seconds | ||||
| local hudbars_mod = minetest.get_modpath("hudbars") | ||||
| local only_names = minetest.settings:get_bool("unified_inventory_only_names", true) | ||||
| local max_length = tonumber(minetest.settings:get("unified_inventory_max_item_name_length")) or 80 | ||||
|  | ||||
| local function set_hud(player) | ||||
| 	local player_name = player:get_player_name() | ||||
| @@ -60,6 +62,7 @@ minetest.register_globalstep(function(dtime) | ||||
| 			data.itemname = itemname | ||||
| 			data.index = index | ||||
| 			data.dtime = 0 | ||||
| 			local lang_code = minetest.get_player_information(player:get_player_name()).lang_code | ||||
|  | ||||
| 			local desc = stack.get_meta | ||||
| 				and stack:get_meta():get_string("description") | ||||
| @@ -69,6 +72,14 @@ minetest.register_globalstep(function(dtime) | ||||
| 				local def = minetest.registered_items[itemname] | ||||
| 				desc = def and def.description or "" | ||||
| 			end | ||||
| 			if only_names and desc and string.find(desc, "\n") then | ||||
| 				desc = string.match(desc, "([^\n]*)") | ||||
| 			end | ||||
| 			desc = minetest.get_translated_string(lang_code, desc) | ||||
| 			desc = minetest.strip_colors(desc) | ||||
| 			if string.len(desc) > max_length and max_length > 0 then | ||||
| 				desc = string.sub(desc, 1, max_length) .. " [...]" | ||||
| 			end | ||||
| 			player:hud_change(data.hud, 'text', desc) | ||||
| 		end | ||||
| 	end | ||||
|   | ||||
							
								
								
									
										55
									
								
								legacy.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,55 @@ | ||||
| -- Inefficient pattern matching | ||||
|  | ||||
| local warned_funcs = {} | ||||
| local function LOG_ONCE(funcname) | ||||
| 	if warned_funcs[funcname] then return end | ||||
| 	warned_funcs[funcname] = true | ||||
| 	minetest.log("error", "Call to undocumented, deprecated API '" .. funcname .. "'." | ||||
| 		.. " In a future version of Unified Inventory this will result in a real error.") | ||||
| end | ||||
|  | ||||
| function unified_inventory.canonical_item_spec_matcher(spec) | ||||
| 	LOG_ONCE("canonical_item_spec_matcher") | ||||
| 	local specname = ItemStack(spec):get_name() | ||||
| 	if specname:sub(1, 6) ~= "group:" then | ||||
| 		return function (itemname) | ||||
| 			return itemname == specname | ||||
| 		end | ||||
| 	end | ||||
|  | ||||
| 	local group_names = specname:sub(7):split(",") | ||||
| 	return function (itemname) | ||||
| 		local itemdef = minetest.registered_items[itemname] | ||||
| 		for _, group_name in ipairs(group_names) do | ||||
| 			if (itemdef.groups[group_name] or 0) == 0 then | ||||
| 				return false | ||||
| 			end | ||||
| 		end | ||||
| 		return true | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function unified_inventory.item_matches_spec(item, spec) | ||||
| 	LOG_ONCE("item_matches_spec") | ||||
| 	local itemname = ItemStack(item):get_name() | ||||
| 	return unified_inventory.canonical_item_spec_matcher(spec)(itemname) | ||||
| end | ||||
|  | ||||
|  | ||||
| unified_inventory.registered_group_items = { | ||||
| 	mesecon_conductor_craftable = "mesecons:wire_00000000_off", | ||||
| 	stone = "default:cobble", | ||||
| 	wood = "default:wood", | ||||
| 	book = "default:book", | ||||
| 	sand = "default:sand", | ||||
| 	leaves = "default:leaves", | ||||
| 	tree = "default:tree", | ||||
| 	vessel = "vessels:glass_bottle", | ||||
| 	wool = "wool:white", | ||||
| } | ||||
|  | ||||
| function unified_inventory.register_group_item(groupname, itemname) | ||||
| 	LOG_ONCE("register_group_item") | ||||
| 	unified_inventory.registered_group_items[groupname] = itemname | ||||
| end | ||||
|  | ||||
| @@ -7,17 +7,17 @@ Bag @1=Tasche @1 | ||||
| Small Bag=Kleine Tasche | ||||
| Medium Bag=Mittelgroße Tasche | ||||
| Large Bag=Große Tasche | ||||
| All Items= | ||||
| Misc. Items= | ||||
| Plant Life= | ||||
| Building Materials= | ||||
| Tools= | ||||
| Minerals and Metals= | ||||
| Environment and Worldgen= | ||||
| Lighting= | ||||
| All Items=Alle Gegenstände | ||||
| Misc. Items=Sonstige Gegenstände | ||||
| Plant Life=Pfanzenwelt | ||||
| Building Materials=Baumaterialien | ||||
| Tools=Werkzeuge | ||||
| Minerals and Metals=Minerale und Metalle | ||||
| Environment and Worldgen=Umwelt und Welterstellung | ||||
| Lighting=Beleuchtung | ||||
|  and = und  | ||||
| Scroll categories left= | ||||
| Scroll categories right= | ||||
| Scroll categories left=Kategorien nach links blättern | ||||
| Scroll categories right=Kategorien nach rechts blättern | ||||
| Search=Suchen | ||||
| Reset search and display everything=Suche zurücksetzen und alles anzeigen | ||||
| First page=Erste Seite | ||||
| @@ -76,10 +76,10 @@ Waypoints=Wegpunkte | ||||
| Select Waypoint #@1=Wegpunkt Nr. @1 auswählen | ||||
| Waypoint @1=Wegpunkt Nr. @1 | ||||
| Set waypoint to current location=Setze Wegpunkt zur derzeitigen Position | ||||
| Hide waypoint= | ||||
| Show waypoint= | ||||
| Hide coordinates= | ||||
| Show coordinates= | ||||
| Hide waypoint=Wegpunkt verstecken | ||||
| Show waypoint=Wegpunkt zeigen | ||||
| Hide coordinates=Koordinaten verstecken | ||||
| Show coordinates=Koordinaten zeigen | ||||
| Change color of waypoint display=Farbe der Darstellung der Wegpunkte ändern | ||||
| Edit waypoint name=Name des Wegpunkts ändern | ||||
| Waypoint active=Wegpunkt aktiv | ||||
|   | ||||
| @@ -1,98 +1,91 @@ | ||||
| # textdomain: unified_inventory | ||||
| Mixing= | ||||
| Cooking= | ||||
| Digging= | ||||
| Category:=Kategorie: | ||||
| Mixing=Miksowanie | ||||
| Cooking=Gotowanie | ||||
| Digging=Kopanie | ||||
| Bags=Plecaki | ||||
| Bag @1=Plecak @1 | ||||
| Small Bag=Maly plecak | ||||
| Medium Bag=Sredni plecak | ||||
| Large Bag=Duzy plecak | ||||
| All Items= | ||||
| Misc. Items= | ||||
| Plant Life= | ||||
| Building Materials= | ||||
| Tools= | ||||
| Minerals and Metals= | ||||
| Environment and Worldgen= | ||||
| Lighting= | ||||
| Small Bag=Mały plecak | ||||
| Medium Bag=Średni plecak | ||||
| Large Bag=Duży plecak | ||||
| All Items=Wszystkie przedmioty | ||||
| Misc. Items=Różne przedmioty | ||||
| Plant Life=Życie roślin | ||||
| Building Materials=Materiały budowlane | ||||
| Tools=Narzędzia | ||||
| Minerals and Metals=Minerały i metale | ||||
| Environment and Worldgen=Otoczenie i generowanie świata | ||||
| Lighting=Oświetlenie | ||||
|  and = i  | ||||
| Scroll categories left= | ||||
| Scroll categories right= | ||||
| Scroll categories left=Przewiń kategorię w lewo | ||||
| Scroll categories right=Przewiń kategorię w prawo | ||||
| Search=Szukaj | ||||
| Reset search and display everything= | ||||
| Reset search and display everything=Zresetuj wyszukiwanie i pokaż wszystko | ||||
| First page=Pierwsza strona | ||||
| Back three pages=3 strony w tyl | ||||
| Back one page=1 strona w tyl | ||||
| Forward one page=1 strona do przodu | ||||
| Forward three pages=3 strony do przodu | ||||
| Back three pages=Trzy strony do tyłu | ||||
| Back one page=Stronę do tyłu | ||||
| Forward one page=Stronę do przodu | ||||
| Forward three pages=Trzy strony do przodu | ||||
| Last page=Ostatnia strona | ||||
| No matching items=Brak pasujacych przedmiotow | ||||
| No matching items=Brak pasujących przedmiotów | ||||
| No matches.=Brak wyników | ||||
| Page=Strona | ||||
| @1 of @2=@1 z @2 | ||||
| Filter=Filtr | ||||
| Can use the creative inventory= | ||||
| Forces Unified Inventory to be displayed in Full mode if Lite mode is configured globally= | ||||
| Crafting Grid= | ||||
| Crafting Guide= | ||||
| Set home position=Ustaw pozycję wyjściową | ||||
| Home position set to: @1=Pozycja domowa ustawiona na: @1 | ||||
| You don't have the "home" privilege!=Nie masz uprawnien do zmiany czasu "home"! | ||||
| Can use the creative inventory=Może używać kreatywnego ekwipunku | ||||
| Forces Unified Inventory to be displayed in Full mode if Lite mode is configured globally=Wymusza wyświetlanie Unified Inventory w trybie Full jeżeli tryb Lite jest skonfigurowany globalnie | ||||
| Crafting Grid=Siatka craftingu | ||||
| Crafting Guide=Przewodnik craftingu | ||||
| Set home position=Ustaw pozycję domu | ||||
| Home position set to: @1=Pozycja domu ustawiona na: @1 | ||||
| You don't have the "home" privilege!=Brak uprawnień "home"! | ||||
| Go home=Idź do domu | ||||
| Set time to day=Ustaw czas na dzień | ||||
| Time of day set to 6am=Czas ustawiony na 6:00 | ||||
| You don't have the settime privilege!=Nie masz uprawnien do zmiany czasu "settime"! | ||||
| You don't have the settime privilege!=Brak uprawnień "settime"! | ||||
| Set time to night=Ustaw czas na noc | ||||
| Time of day set to 9pm=Czas ustawiony na 21:00 | ||||
| Clear inventory=Wyczyść zapasy | ||||
| This button has been disabled outside of creative mode to prevent accidental inventory trashing.@nUse the trash slot instead.= | ||||
| Inventory cleared!=Zapasy zostały wyczyszczone! | ||||
| Trash:=Smietnik: | ||||
| Refill:=Uzupelnianie: | ||||
| Any item belonging to the @1 group= | ||||
| Any item belonging to the groups @1= | ||||
| Clear inventory=Wyczyść ekwipunek | ||||
| This button has been disabled outside of creative mode to prevent accidental inventory trashing.@nUse the trash slot instead.=Aby zapobiec przypadkowemu skasowaniu ekwipunku, ten przycisk został wyłączony poza trybem kreatywnym.@nUżyj zamiast tego ikony śmietnika. | ||||
| Inventory cleared!=Ekwipunek został wyczyszczony! | ||||
| Trash:=Śmietnik: | ||||
| Refill:=Uzupełnianie: | ||||
| Any item belonging to the @1 group=Każdy przedmiot należący do @1 grupy | ||||
| Any item belonging to the groups @1=Każdy przedmiot należacy do grup @1 | ||||
| Recipe @1 of @2=Recepta @1 z @2 | ||||
| Usage @1 of @2=Użycie @1 z @2 | ||||
| No recipes=Brak recepty | ||||
| No usages=Bez użycia | ||||
| Result=Wynik | ||||
| Ingredient=Składnik | ||||
| Show next recipe= | ||||
| Show next usage= | ||||
| Show previous recipe= | ||||
| Show previous usage= | ||||
| @1 (@2)= | ||||
| Show next recipe=Pokaż nastepną recepturę | ||||
| Show next usage=Pokaż następne użycie | ||||
| Show previous recipe=Pokaż poprzednią recepturę | ||||
| Show previous usage=Pokaż poprzednie użycie | ||||
| @1 (@2)=@1 (@2) | ||||
| Give me:=Daj mi: | ||||
| This recipe is too@@large to be displayed.= | ||||
| To craft grid:= | ||||
| This recipe is too@@large to be displayed.=Receptura jest zbyt@@duża aby ją wyświetlić. | ||||
| To craft grid:=Do siatki craftingu. | ||||
| All=Wszystko | ||||
| Crafting= | ||||
| White=Bialy | ||||
| Yellow=Zolty | ||||
| Crafting=Crafting | ||||
| White=Biały | ||||
| Yellow=Zółty | ||||
| Red=Czerwony | ||||
| Green=Zielony | ||||
| Blue=Niebieski | ||||
| Waypoints=Punkty orientacyjne | ||||
| Select Waypoint #@1=Wybierz punkt #@1 | ||||
| Waypoint @1=Punkty orientacyjne @1 | ||||
| Set waypoint to current location=Ustaw punkt orientacyjny na biezacej pozycji | ||||
| Hide waypoint= | ||||
| Show waypoint= | ||||
| Hide coordinates= | ||||
| Show coordinates= | ||||
| Change color of waypoint display=Zmien kolor punktu | ||||
| Edit waypoint name=Edytuj nazwe punktu | ||||
| Waypoint active=Punkt wlaczony | ||||
| Waypoint inactive=Punkt wylaczony | ||||
| Finish editing=Zakoncz edycje | ||||
| Set waypoint to current location=Ustaw punkt orientacyjny na bieżacej pozycji | ||||
| Hide waypoint=Ukryj punkt orientacyjny | ||||
| Show waypoint=Pokaż punkt orientacyjny | ||||
| Hide coordinates=Ukryj koordynaty | ||||
| Show coordinates=Pokaż koordynaty | ||||
| Change color of waypoint display=Zmień kolor punktu | ||||
| Edit waypoint name=Edytuj nazwę punktu | ||||
| Waypoint active=Punkt włączony | ||||
| Waypoint inactive=Punkt wyłączony | ||||
| Finish editing=Zakończ edycję | ||||
| World position=Pozycja | ||||
| Name=Nazwa | ||||
| HUD text color=Kolor tekstu HUD | ||||
|  | ||||
|  | ||||
| ##### not used anymore ##### | ||||
|  | ||||
| invisible=niewidzialny | ||||
| visible=widomy | ||||
| Make waypoint @1=Robić punkt @1 | ||||
| @1 display of waypoint coordinates=@1 koordynatow punktu | ||||
| HUD text color=Kolor tekstu HUD | ||||
| @@ -7,17 +7,17 @@ Bag @1=背包@1 | ||||
| Small Bag=小背包 | ||||
| Medium Bag=中背包 | ||||
| Large Bag=大背包 | ||||
| All Items= | ||||
| Misc. Items= | ||||
| Plant Life= | ||||
| Building Materials= | ||||
| Tools= | ||||
| Minerals and Metals= | ||||
| Environment and Worldgen= | ||||
| Lighting= | ||||
| All Items=所有物品 | ||||
| Misc. Items=杂项 | ||||
| Plant Life=植物 | ||||
| Building Materials=建材 | ||||
| Tools=工具 | ||||
| Minerals and Metals=矿物与金属 | ||||
| Environment and Worldgen=自然环境 | ||||
| Lighting=光源 | ||||
|  and = 和  | ||||
| Scroll categories left= | ||||
| Scroll categories right= | ||||
| Scroll categories left=向左滚动分类栏 | ||||
| Scroll categories right=向右滚动分类栏 | ||||
| Search=搜索 | ||||
| Reset search and display everything=重置搜索并显示所有物品 | ||||
| First page=第一页 | ||||
| @@ -32,7 +32,7 @@ Page=页面 | ||||
| @1 of @2=第@1页,共@2页 | ||||
| Filter=过滤器 | ||||
| Can use the creative inventory=可以使用创造背包 | ||||
| Forces Unified Inventory to be displayed in Full mode if Lite mode is configured globally= | ||||
| Forces Unified Inventory to be displayed in Full mode if Lite mode is configured globally=如果轻量模式被全局配置,强迫Unified Inventory以完全模式展现。 | ||||
| Crafting Grid=合成表 | ||||
| Crafting Guide=合成指南 | ||||
| Set home position=设置家的位置 | ||||
| @@ -45,7 +45,7 @@ You don't have the settime privilege!=你没有“settime”权限! | ||||
| Set time to night=设置时间到晚上 | ||||
| Time of day set to 9pm=时间设置到晚上9点 | ||||
| Clear inventory=清空背包 | ||||
| This button has been disabled outside of creative mode to prevent accidental inventory trashing.@nUse the trash slot instead.= | ||||
| This button has been disabled outside of creative mode to prevent accidental inventory trashing.@nUse the trash slot instead.=此按钮已在非创造模式中禁用以防止意外的背包清空。@n请使用垃圾桶栏。 | ||||
| Inventory cleared!=清空背包 | ||||
| Trash:=丢弃: | ||||
| Refill:=填满: | ||||
| @@ -57,13 +57,13 @@ No recipes=没有配方 | ||||
| No usages=没有用法 | ||||
| Result=结果 | ||||
| Ingredient=原料 | ||||
| Show next recipe= | ||||
| Show next usage= | ||||
| Show previous recipe= | ||||
| Show previous usage= | ||||
| @1 (@2)= | ||||
| Show next recipe=显示下一个配方 | ||||
| Show next usage=显示下一个用法 | ||||
| Show previous recipe=显示前一个配方 | ||||
| Show previous usage=显示前一个用法 | ||||
| @1 (@2)=@1 (@2) | ||||
| Give me:=给予: | ||||
| This recipe is too@@large to be displayed.= | ||||
| This recipe is too@@large to be displayed.=该配方太@@大,不能显示。 | ||||
| To craft grid:=填充物品到合成表 | ||||
| All=全部 | ||||
| Crafting=合成 | ||||
| @@ -76,10 +76,10 @@ Waypoints=航路点 | ||||
| Select Waypoint #@1=查询航路点 #@1 | ||||
| Waypoint @1=航路点 @1 | ||||
| Set waypoint to current location=将航路点设置到当前位置 | ||||
| Hide waypoint= | ||||
| Show waypoint= | ||||
| Hide coordinates= | ||||
| Show coordinates= | ||||
| Hide waypoint=隐藏航路点 | ||||
| Show waypoint=显示航路点 | ||||
| Hide coordinates=隐藏坐标 | ||||
| Show coordinates=显示坐标 | ||||
| Change color of waypoint display=改变航路点显示的颜色 | ||||
| Edit waypoint name=编辑航路点名称 | ||||
| Waypoint active=航路点已激活 | ||||
|   | ||||
| @@ -7,17 +7,17 @@ Bag @1=揹包@1 | ||||
| Small Bag=小揹包 | ||||
| Medium Bag=中揹包 | ||||
| Large Bag=大揹包 | ||||
| All Items= | ||||
| Misc. Items= | ||||
| Plant Life= | ||||
| Building Materials= | ||||
| Tools= | ||||
| Minerals and Metals= | ||||
| Environment and Worldgen= | ||||
| Lighting= | ||||
| All Items=所有物品 | ||||
| Misc. Items=雜項 | ||||
| Plant Life=植物 | ||||
| Building Materials=建材 | ||||
| Tools=工具 | ||||
| Minerals and Metals=礦物與金屬 | ||||
| Environment and Worldgen=自然環境 | ||||
| Lighting=光源 | ||||
|  and = 和  | ||||
| Scroll categories left= | ||||
| Scroll categories right= | ||||
| Scroll categories left=向左滾動分類欄 | ||||
| Scroll categories right=向右滾動分類欄 | ||||
| Search=搜索 | ||||
| Reset search and display everything=重置搜索並顯示所有物品 | ||||
| First page=第一頁 | ||||
| @@ -32,7 +32,7 @@ Page=頁面 | ||||
| @1 of @2=第@1頁,共@2頁 | ||||
| Filter=過濾器 | ||||
| Can use the creative inventory=可以使用創造揹包 | ||||
| Forces Unified Inventory to be displayed in Full mode if Lite mode is configured globally= | ||||
| Forces Unified Inventory to be displayed in Full mode if Lite mode is configured globally=如果輕量模式被全局配置,強迫Unified Inventory以完全模式展現。 | ||||
| Crafting Grid=合成表 | ||||
| Crafting Guide=合成指南 | ||||
| Set home position=設置家的位置 | ||||
| @@ -45,7 +45,7 @@ You don't have the settime privilege!=你沒有“settime”權限! | ||||
| Set time to night=設置時間到晚上 | ||||
| Time of day set to 9pm=時間設置到晚上9點 | ||||
| Clear inventory=清空揹包 | ||||
| This button has been disabled outside of creative mode to prevent accidental inventory trashing.@nUse the trash slot instead.= | ||||
| This button has been disabled outside of creative mode to prevent accidental inventory trashing.@nUse the trash slot instead.=此按鈕已在非創造模式中禁用以防止意外的背包清空。@n請使用垃圾桶欄。 | ||||
| Inventory cleared!=清空揹包 | ||||
| Trash:=丟棄: | ||||
| Refill:=填滿: | ||||
| @@ -57,13 +57,13 @@ No recipes=沒有配方 | ||||
| No usages=沒有用法 | ||||
| Result=結果 | ||||
| Ingredient=原料 | ||||
| Show next recipe= | ||||
| Show next usage= | ||||
| Show previous recipe= | ||||
| Show previous usage= | ||||
| @1 (@2)= | ||||
| Show next recipe=顯示下一個配方 | ||||
| Show next usage=顯示下一個用法 | ||||
| Show previous recipe=顯示上一個配方 | ||||
| Show previous usage=顯示上一個用法 | ||||
| @1 (@2)=@1 (@2) | ||||
| Give me:=給予: | ||||
| This recipe is too@@large to be displayed.= | ||||
| This recipe is too@@large to be displayed.=該配方太@@大,不能顯示。 | ||||
| To craft grid:=填充物品到合成表 | ||||
| All=全部 | ||||
| Crafting=合成 | ||||
| @@ -76,10 +76,10 @@ Waypoints=航路點 | ||||
| Select Waypoint #@1=查詢航路點 #@1 | ||||
| Waypoint @1=航路點 @1 | ||||
| Set waypoint to current location=將航路點設置到當前位置 | ||||
| Hide waypoint= | ||||
| Show waypoint= | ||||
| Hide coordinates= | ||||
| Show coordinates= | ||||
| Hide waypoint=隱藏航路點 | ||||
| Show waypoint=顯示航路點 | ||||
| Hide coordinates=隱藏坐標 | ||||
| Show coordinates=顯示坐標 | ||||
| Change color of waypoint display=改變航路點顯示的顏色 | ||||
| Edit waypoint name=編輯航路點名稱 | ||||
| Waypoint active=航路點已激活 | ||||
|   | ||||
| @@ -126,25 +126,18 @@ Example output: | ||||
| 	} | ||||
| --]] | ||||
| function unified_inventory.find_usable_items(inv_items, craft_items) | ||||
| 	local get_group = minetest.get_item_group | ||||
| 	local result = {} | ||||
|  | ||||
| 	for craft_item in pairs(craft_items) do | ||||
| 		local group = craft_item:match("^group:(.+)") | ||||
| 		local found = {} | ||||
| 		-- may specify group:type1,type2 | ||||
| 		local items = unified_inventory.get_matching_items(craft_item) | ||||
|  | ||||
| 		if group ~= nil then | ||||
| 			for inv_item in pairs(inv_items) do | ||||
| 				if get_group(inv_item, group) > 0 then | ||||
| 					found[inv_item] = true | ||||
| 				end | ||||
| 			end | ||||
| 		else | ||||
| 			if inv_items[craft_item] ~= nil then | ||||
| 				found[craft_item] = true | ||||
| 		local found = {} | ||||
| 		for itemname, _ in pairs(items) do | ||||
| 			if inv_items[itemname] then | ||||
| 				found[itemname] = true | ||||
| 			end | ||||
| 		end | ||||
|  | ||||
| 		result[craft_item] = found | ||||
| 	end | ||||
|  | ||||
|   | ||||
							
								
								
									
										2
									
								
								mod.conf
									
									
									
									
									
								
							
							
						
						| @@ -1,6 +1,6 @@ | ||||
| name = unified_inventory | ||||
|  | ||||
| optional_depends = default, creative, sfinv, datastorage, farming | ||||
| optional_depends = default, creative, sfinv, datastorage | ||||
| description = """ | ||||
| Unified Inventory replaces the default survival and creative inventory. | ||||
| It adds a nicer interface and a number of features, such as a crafting guide. | ||||
|   | ||||
							
								
								
									
										29
									
								
								register.lua
									
									
									
									
									
								
							
							
						
						| @@ -98,7 +98,7 @@ ui.register_button("misc_set_day", { | ||||
| 	action = function(player) | ||||
| 		local player_name = player:get_player_name() | ||||
| 		if minetest.check_player_privs(player_name, {settime=true}) then | ||||
| 			minetest.sound_play("birds", | ||||
| 			minetest.sound_play("ui_morning", | ||||
| 					{to_player=player_name, gain = 1.0}) | ||||
| 			minetest.set_timeofday((6000 % 24000) / 24000) | ||||
| 			minetest.chat_send_player(player_name, | ||||
| @@ -122,7 +122,7 @@ ui.register_button("misc_set_night", { | ||||
| 	action = function(player) | ||||
| 		local player_name = player:get_player_name() | ||||
| 		if minetest.check_player_privs(player_name, {settime=true}) then | ||||
| 			minetest.sound_play("owl", | ||||
| 			minetest.sound_play("ui_owl", | ||||
| 					{to_player=player_name, gain = 1.0}) | ||||
| 			minetest.set_timeofday((21000 % 24000) / 24000) | ||||
| 			minetest.chat_send_player(player_name, | ||||
| @@ -183,14 +183,14 @@ ui.register_page("craft", { | ||||
| 		local n=#formspec+1 | ||||
|  | ||||
| 		if ui.trash_enabled or ui.is_creative(player_name) or minetest.get_player_privs(player_name).give then | ||||
| 			formspec[n] = string.format("label[%f,%f;%s]", craftx + 6.45, crafty + 2.4, F(S("Trash:"))) | ||||
| 			formspec[n] = string.format("label[%f,%f;%s]", craftx + 6.35, crafty + 2.3, F(S("Trash:"))) | ||||
| 			formspec[n+1] = ui.make_trash_slot(craftx + 6.25, crafty + 2.5) | ||||
| 			n=n + 2 | ||||
| 		end | ||||
|  | ||||
| 		if ui.is_creative(player_name) then | ||||
| 			formspec[n] = ui.single_slot(craftx - 2.5, crafty + 2.5) | ||||
| 			formspec[n+1] = string.format("label[%f,%f;%s]", craftx - 2.3, crafty + 2.4,F(S("Refill:"))) | ||||
| 			formspec[n+1] = string.format("label[%f,%f;%s]", craftx - 2.4, crafty + 2.3, F(S("Refill:"))) | ||||
| 			formspec[n+2] = string.format("list[detached:%srefill;main;%f,%f;1,1;]", | ||||
| 				F(player_name), craftx - 2.5 + ui.list_img_offset, crafty + 2.5 + ui.list_img_offset) | ||||
| 		end | ||||
| @@ -207,9 +207,9 @@ ui.register_page("craft", { | ||||
|  | ||||
| local function stack_image_button(x, y, w, h, buttonname_prefix, item) | ||||
| 	local name = item:get_name() | ||||
| 	local count = item:get_count() | ||||
| 	local description = item:get_meta():get_string("description") | ||||
| 	local show_is_group = false | ||||
| 	local displayitem = name.." "..count | ||||
| 	local displayitem = item:to_string() | ||||
| 	local selectitem = name | ||||
| 	if name:sub(1, 6) == "group:" then | ||||
| 		local group_name = name:sub(7) | ||||
| @@ -219,7 +219,9 @@ local function stack_image_button(x, y, w, h, buttonname_prefix, item) | ||||
| 		selectitem = group_item.sole and displayitem or name | ||||
| 	end | ||||
| 	local label = show_is_group and "G" or "" | ||||
| 	local buttonname = F(buttonname_prefix..ui.mangle_for_formspec(selectitem)) | ||||
| 	-- Unique id to prevent tooltip being overridden | ||||
| 	local id = string.format("%i%i_", x*10, y*10) | ||||
| 	local buttonname = F(id..buttonname_prefix..ui.mangle_for_formspec(selectitem)) | ||||
| 	local button = string.format("item_image_button[%f,%f;%f,%f;%s;%s;%s]", | ||||
| 			x, y, w, h, | ||||
| 			F(displayitem), buttonname, label) | ||||
| @@ -235,10 +237,15 @@ local function stack_image_button(x, y, w, h, buttonname_prefix, item) | ||||
| 		if andcount >= 1 then | ||||
| 			button = button  .. string.format("tooltip[%s;%s]", buttonname, grouptip) | ||||
| 		end | ||||
| 	elseif description ~= "" then | ||||
| 		button = button  .. string.format("tooltip[%s;%s]", buttonname, F(description)) | ||||
| 	end | ||||
| 	return button | ||||
| end | ||||
|  | ||||
| -- The recipe text contains parameters, hence they can yet not be translated. | ||||
| -- Instead, use a dummy translation call so that it can be picked up by the | ||||
| -- static parsing of the translation string update script | ||||
| local recipe_text = { | ||||
| 	recipe = NS("Recipe @1 of @2"), | ||||
| 	usage = NS("Usage @1 of @2"), | ||||
| @@ -493,6 +500,14 @@ local function craftguide_craft(player, formname, fields) | ||||
| 	local alternate = ui.alternate[player_name] | ||||
|  | ||||
| 	local craft = crafts[alternate] | ||||
| 	if not craft.width then | ||||
| 		if not craft.output then | ||||
| 			minetest.log("warning", "[unified_inventory] Craft has no output.") | ||||
| 		else | ||||
| 			minetest.log("warning", ("[unified_inventory] Craft for '%s' has no width."):format(craft.output)) | ||||
| 		end | ||||
| 		return | ||||
| 	end | ||||
| 	if craft.width > 3 then return end | ||||
|  | ||||
| 	ui.craftguide_match_craft(player, "main", "craft", craft, amount) | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								screenshot.png
									
									
									
									
									
								
							
							
						
						| Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 220 KiB | 
| @@ -1,14 +1,36 @@ | ||||
| #Enabling lite mode enables a smaller and simpler version of the Unified | ||||
| #Inventory, optimized for small displays. | ||||
| # Reduced formspec layout, optimized for smaller displays. | ||||
| # Note: This may also disable some features to free up visual space. | ||||
| unified_inventory_lite (Lite mode) bool false | ||||
|  | ||||
| #If enabled, bags will be made available which can be used to extend | ||||
| #inventory storage size. | ||||
| # Provides craftable bag items to extend the inventory space. | ||||
| unified_inventory_bags (Enable bags) bool true | ||||
|  | ||||
| #If enabled, the trash slot can be used by those without both creative | ||||
| #and the give privilege. | ||||
| # Shows the trash slot to everyone. | ||||
| # When disabled, only players with the privilege "creative" or "give" will | ||||
| # have this slot shown in their inventory. | ||||
| unified_inventory_trash (Enable trash) bool true | ||||
|  | ||||
| # Provides waypoints on a per-player basis to remember positions on the map. | ||||
| unified_inventory_waypoints (Enable waypoints) bool true | ||||
|  | ||||
| unified_inventory_automatic_categorization (Items automatically added to categories) bool true | ||||
| # If enabled, disabled buttons will be hidden instead of grayed out. | ||||
| unified_inventory_hide_disabled_buttons (Hide disabled buttons) bool false | ||||
|  | ||||
| # Hides items with no known craft recipe from the category "all" (default). | ||||
| # This setting has no effect on players in creative mode. | ||||
| unified_inventory_hide_uncraftable_items (Hide uncraftable items) bool false | ||||
|  | ||||
| # Automatically categorizes registered items based on their | ||||
| # groups. This is based on a fuzzy match, thus is not 100% accurate. | ||||
| unified_inventory_automatic_categorization (Categories: add items automatically) bool true | ||||
|  | ||||
| # Shows the selected wielded item description in the HUD for a few seconds. | ||||
| unified_inventory_item_names (Enable HUD item names) bool true | ||||
|  | ||||
| # Trims the shown wielded item description to the first line. | ||||
| unified_inventory_only_names (HUD item name: first line only) bool true | ||||
|  | ||||
| # Hard character limit of the wielded item description. | ||||
| # Crops the shown description to the specified length. | ||||
| # 0 disables this functionality. | ||||
| unified_inventory_max_item_name_length (HUD item names: character limit) int 80 | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								sounds/birds.ogg
									
									
									
									
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								sounds/owl.ogg
									
									
									
									
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								sounds/ui_click.ogg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								sounds/ui_morning.ogg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								sounds/ui_owl.ogg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| Before Width: | Height: | Size: 240 B After Width: | Height: | Size: 510 B | 
| Before Width: | Height: | Size: 139 B After Width: | Height: | Size: 551 B | 
							
								
								
									
										
											BIN
										
									
								
								textures/ui_teleport.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.2 KiB | 
| @@ -140,36 +140,49 @@ ui.register_page("waypoints", { | ||||
|  | ||||
| 		-- Main buttons: | ||||
| 		local btnlist = { | ||||
| 			set_waypoint = { | ||||
| 				"ui_waypoint_set_icon.png", | ||||
| 				S("Set waypoint to current location") | ||||
| 			}, | ||||
| 			toggle_waypoint = { | ||||
| 			-- 1. formspec name | ||||
| 			-- 2. button image | ||||
| 			-- 3. translation text | ||||
| 			{ | ||||
| 				"toggle_waypoint", | ||||
| 				waypoint.active and "ui_on_icon.png" or "ui_off_icon.png", | ||||
| 				waypoint.active and S("Hide waypoint") or S("Show waypoint") | ||||
| 			}, | ||||
| 			toggle_display_pos = { | ||||
| 			{ | ||||
| 				"rename_waypoint", | ||||
| 				"ui_pencil_icon.png", | ||||
| 				S("Edit waypoint name") | ||||
| 			}, | ||||
| 			{ | ||||
| 				"set_waypoint", | ||||
| 				"ui_waypoint_set_icon.png", | ||||
| 				S("Set waypoint to current location") | ||||
| 			}, | ||||
| 			{ | ||||
| 				"toggle_display_pos", | ||||
| 				waypoint.display_pos and "ui_green_icon_background.png^ui_xyz_icon.png" or "ui_red_icon_background.png^ui_xyz_icon.png^(ui_no.png^[transformR90)", | ||||
| 				waypoint.display_pos and S("Hide coordinates") or S("Show coordinates") | ||||
| 			}, | ||||
| 			toggle_color = { | ||||
| 			{ | ||||
| 				"toggle_color", | ||||
| 				"ui_circular_arrows_icon.png", | ||||
| 				S("Change color of waypoint display") | ||||
| 			}, | ||||
| 			rename_waypoint = { | ||||
| 				"ui_pencil_icon.png", | ||||
| 				S("Edit waypoint name") | ||||
| 			} | ||||
| 		} | ||||
| 		if minetest.get_player_privs(player_name).teleport then | ||||
| 			table.insert(btnlist, { | ||||
| 				"teleport_waypoint", | ||||
| 				"ui_teleport.png", | ||||
| 				S("Teleport to waypoint") | ||||
| 			}) | ||||
| 		end | ||||
|  | ||||
| 		local x = 4 | ||||
| 		for name, def in pairs(btnlist) do | ||||
| 		for i, def in pairs(btnlist) do | ||||
| 			formspec[n] = string.format("image_button[%f,%f;%f,%f;%s;%s%i;]", | ||||
| 				wp_buttons_rj - ui.style_full.btn_spc * x, wp_bottom_row, | ||||
| 				wp_buttons_rj + ui.style_full.btn_spc * (i - #btnlist), wp_bottom_row, | ||||
| 				ui.style_full.btn_size, ui.style_full.btn_size, | ||||
| 				def[1], name, sel) | ||||
| 			formspec[n+1] = "tooltip["..name..sel..";"..F(def[2]).."]" | ||||
| 			x = x - 1 | ||||
| 				def[2], def[1], sel) | ||||
| 			formspec[n+1] = "tooltip["..def[1]..sel..";"..F(def[3]).."]" | ||||
| 			n = n + 2 | ||||
| 		end | ||||
|  | ||||
| @@ -313,6 +326,13 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) | ||||
| 			update_formspec = true | ||||
| 		end | ||||
|  | ||||
| 		if fields["teleport_waypoint" .. i] and waypoint.world_pos then | ||||
| 			if minetest.get_player_privs(player_name).teleport then | ||||
| 				minetest.sound_play("teleport", {to_player = player_name}) | ||||
| 				player:set_pos(waypoint.world_pos) | ||||
| 			end | ||||
| 		end | ||||
|  | ||||
| 		if hit then | ||||
| 			-- Save first | ||||
| 			waypoints.data[i] = waypoint | ||||
| @@ -323,6 +343,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) | ||||
| 			update_hud(player, waypoints, temp, i) | ||||
| 		end | ||||
| 		if update_formspec then | ||||
| 			minetest.sound_play("ui_click", {to_player=player_name, gain = 0.1}) | ||||
| 			ui.set_inventory_formspec(player, "waypoints") | ||||
| 		end | ||||
|  | ||||
| @@ -330,8 +351,9 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) | ||||
| 	end | ||||
| end) | ||||
|  | ||||
|  | ||||
| minetest.register_on_joinplayer(function(player) | ||||
| -- waypoints_temp must be initialized before the general unified_inventory | ||||
| -- joinplayer callback is run for updating the inventory | ||||
| table.insert(minetest.registered_on_joinplayers, 1, function(player) | ||||
| 	local player_name = player:get_player_name() | ||||
| 	local waypoints = get_waypoint_data(player) | ||||
|  | ||||
|   | ||||