Subversion Repositories fei

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 mrkubax10 1
-- Finally Enough Items
2
-- Copyright (C) 2026 mrkubax10
3
 
4
-- This program is free software: you can redistribute it and/or modify
5
-- it under the terms of the GNU Affero General Public License as published by
6
-- the Free Software Foundation, either version 3 of the License, or
7
-- (at your option) any later version.
8
 
9
-- This program is distributed in the hope that it will be useful,
10
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
11
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
-- GNU Affero General Public License for more details.
13
 
14
-- You should have received a copy of the GNU Affero General Public License
15
-- along with this program. If not, see <https://www.gnu.org/licenses/>.
16
 
17
local prev_inventory_formspec_values={}
18
local update_elapsed = 0
19
 
20
-- Extracts next number from string from the beginning
21
local function scan_number(str)
22
	local begin_index = 0
23
	local end_index = 0
24
	local collect = false
25
	local index = 1
26
	for ch in string.gmatch(str, ".") do
27
		if collect then
28
			if not string.match(ch, "%d") and ch~="." then
29
				end_index = index
30
				break
31
			end
32
		else
33
			if string.match(ch, "%d") then
34
				begin_index = index
35
				collect = true
36
			end
37
		end
38
		index = index+1
39
	end
40
	if begin_index==end_index then
41
		return 0, str
42
	end
43
	return tonumber(string.sub(str, begin_index, end_index-1)), string.sub(str, end_index+1)
44
end
45
 
46
local function parse_formspec_information(formspec)
47
	local width = 0
48
	local height = 0
49
	local _,size_end = string.find(formspec, "size%[")
50
	if size_end then
51
		local size_pattern = string.sub(formspec, size_end)
52
		width, size_pattern = scan_number(size_pattern)
53
		height, size_pattern = scan_number(size_pattern)
54
	end
55
 
56
	return {
57
		width = width,
58
		height = height,
59
		-- TODO: Look for real_coordinates and formspec_version elements to determine this
60
		legacy_coordinates = true
61
	}
62
end
63
 
64
local function determine_anchor(window_info, inv_formspec_info)
65
	local element_spacing = inv_formspec_info.legacy_coordinates and 0.25 or 0
66
	local formspec_center_x = inv_formspec_info.width/2
67
	local formspec_center_y = inv_formspec_info.height/2
68
	local window_center_x = window_info.max_formspec_size.x/2
69
	local window_center_y = window_info.max_formspec_size.y/2
70
	return {
71
		x = formspec_center_x-element_spacing+window_center_x-window_center_x*element_spacing,
72
		y = formspec_center_y-element_spacing-window_center_y+window_center_y*element_spacing
73
	}
74
end
75
 
76
local function create_items_view(player, prev_inventory_formspec)
77
	local window_info = minetest.get_player_window_information(player:get_player_name())
78
	-- There is a brief moment when the player is still joining where get_player_window_information returns nil
79
	if not window_info then
80
		return ""
81
	end
82
	local inv_formspec_info = parse_formspec_information(player:get_formspec_prepend()..prev_inventory_formspec)
83
	local items_anchor = determine_anchor(window_info, inv_formspec_info)
84
	local formspec = {
85
		"style_type[label;noclip=true]",
86
		"style_type[image_button;noclip=true]",
87
		string.format("image_button[%f,%f;1,1;default_furnace_front.png;button1;]", items_anchor.x, items_anchor.y),
88
	}
89
	return table.concat(formspec, "")
90
end
91
 
92
local function update_if_needed(player, prev_inventory_formspec)
93
	player:set_inventory_formspec(prev_inventory_formspec..create_items_view(player, prev_inventory_formspec))
94
end
95
 
96
-- This is a kind of hacky way of registering in on_mods_loaded to make sure that our on_joinplayer handler is called last
97
minetest.register_on_mods_loaded(function()
98
	minetest.register_on_joinplayer(function(player)
99
		prev_inventory_formspec_values[player:get_player_name()] = player:get_inventory_formspec()
100
	end)
101
end)
102
 
103
minetest.register_on_leaveplayer(function(player)
104
	prev_inventory_formspec_values[player:get_player_name()] = nil
105
end)
106
 
107
minetest.register_on_player_receive_fields(function(player, formname, fields)
108
	-- If player inventory formspec was somehow updated, then change the previous formspec value too
109
	if formname~="" then
110
		return
111
	end
112
	prev_inventory_formspec_values[player:get_player_name()] = player:get_inventory_formspec()
113
	update_if_needed(player, player:get_inventory_formspec())
114
end)
115
 
116
minetest.register_globalstep(function(dtime)
117
	update_elapsed = update_elapsed+dtime
118
	-- TODO: Introduce option which will allow to adjust item list updating frequency
119
	if update_elapsed<0.5 then
120
		return
121
	end
122
	update_elapsed = 0
123
	local players = minetest.get_connected_players()
124
	for _,player in ipairs(players) do
125
		local prev_inventory_formspec = prev_inventory_formspec_values[player:get_player_name()]
126
		if prev_inventory_formspec then
127
			update_if_needed(player, prev_inventory_formspec)
128
		end
129
	end
130
end)
131
 
132
minetest.register_chatcommand("formspec",{
133
	func=function(name)
134
		local window_info = minetest.get_player_window_information(name)
135
		local formspec={
136
			--"formspec_version[2]",
137
			"size[5,5]",
138
			"style_type[button;noclip=true]",
139
			"button[0,0;1,1;button1;]",
140
			"button[1,0;1,1;button2;]",
141
			"button[2,0;1,1;button2;]",
142
			"button[2.25,1;1,1;button2;]"
143
			--string.format("button[%f,0;1,1;button1;]", window_info.max_formspec_size.x/2+2.5)
144
		}
145
		minetest.show_formspec(name, "fei:formspec", table.concat(formspec, ""))
146
	end
147
})