| 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
|
| 3 |
mrkubax10 |
49 |
local _, size_end = string.find(formspec, "size%[")
|
| 2 |
mrkubax10 |
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 |
|
| 3 |
mrkubax10 |
56 |
local version = 1
|
|
|
57 |
local _, version_end = string.find(formspec, "formspec_version%[")
|
|
|
58 |
if version_end then
|
|
|
59 |
local version_pattern = string.sub(formspec, version_end)
|
|
|
60 |
version, _ = scan_number(version_pattern)
|
|
|
61 |
end
|
|
|
62 |
|
|
|
63 |
local real_coordinates = false
|
|
|
64 |
local _, real_coordinates_end = string.find(formspec, "real_coordinates%[")
|
|
|
65 |
if real_coordinates_end then
|
|
|
66 |
-- Check for true keyword
|
|
|
67 |
local real_coordinates_pattern = string.sub(formspec, real_coordinates_end, real_coordinates_end+4)
|
|
|
68 |
real_coordinates = real_coordinates_pattern=="true"
|
|
|
69 |
end
|
|
|
70 |
|
| 2 |
mrkubax10 |
71 |
return {
|
|
|
72 |
width = width,
|
|
|
73 |
height = height,
|
| 3 |
mrkubax10 |
74 |
legacy_coordinates = version==1 and not real_coordinates
|
| 2 |
mrkubax10 |
75 |
}
|
|
|
76 |
end
|
|
|
77 |
|
|
|
78 |
local function determine_anchor(window_info, inv_formspec_info)
|
|
|
79 |
local element_spacing = inv_formspec_info.legacy_coordinates and 0.25 or 0
|
|
|
80 |
local formspec_center_x = inv_formspec_info.width/2
|
|
|
81 |
local formspec_center_y = inv_formspec_info.height/2
|
|
|
82 |
local window_center_x = window_info.max_formspec_size.x/2
|
|
|
83 |
local window_center_y = window_info.max_formspec_size.y/2
|
|
|
84 |
return {
|
|
|
85 |
x = formspec_center_x-element_spacing+window_center_x-window_center_x*element_spacing,
|
|
|
86 |
y = formspec_center_y-element_spacing-window_center_y+window_center_y*element_spacing
|
|
|
87 |
}
|
|
|
88 |
end
|
|
|
89 |
|
|
|
90 |
local function create_items_view(player, prev_inventory_formspec)
|
|
|
91 |
local window_info = minetest.get_player_window_information(player:get_player_name())
|
|
|
92 |
-- There is a brief moment when the player is still joining where get_player_window_information returns nil
|
|
|
93 |
if not window_info then
|
|
|
94 |
return ""
|
|
|
95 |
end
|
|
|
96 |
local inv_formspec_info = parse_formspec_information(player:get_formspec_prepend()..prev_inventory_formspec)
|
|
|
97 |
local items_anchor = determine_anchor(window_info, inv_formspec_info)
|
|
|
98 |
local formspec = {
|
|
|
99 |
"style_type[label;noclip=true]",
|
|
|
100 |
"style_type[image_button;noclip=true]",
|
|
|
101 |
string.format("image_button[%f,%f;1,1;default_furnace_front.png;button1;]", items_anchor.x, items_anchor.y),
|
|
|
102 |
}
|
|
|
103 |
return table.concat(formspec, "")
|
|
|
104 |
end
|
|
|
105 |
|
|
|
106 |
local function update_if_needed(player, prev_inventory_formspec)
|
|
|
107 |
player:set_inventory_formspec(prev_inventory_formspec..create_items_view(player, prev_inventory_formspec))
|
|
|
108 |
end
|
|
|
109 |
|
|
|
110 |
-- This is a kind of hacky way of registering in on_mods_loaded to make sure that our on_joinplayer handler is called last
|
|
|
111 |
minetest.register_on_mods_loaded(function()
|
|
|
112 |
minetest.register_on_joinplayer(function(player)
|
|
|
113 |
prev_inventory_formspec_values[player:get_player_name()] = player:get_inventory_formspec()
|
|
|
114 |
end)
|
|
|
115 |
end)
|
|
|
116 |
|
|
|
117 |
minetest.register_on_leaveplayer(function(player)
|
|
|
118 |
prev_inventory_formspec_values[player:get_player_name()] = nil
|
|
|
119 |
end)
|
|
|
120 |
|
|
|
121 |
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
|
|
122 |
-- If player inventory formspec was somehow updated, then change the previous formspec value too
|
|
|
123 |
if formname~="" then
|
|
|
124 |
return
|
|
|
125 |
end
|
|
|
126 |
prev_inventory_formspec_values[player:get_player_name()] = player:get_inventory_formspec()
|
|
|
127 |
update_if_needed(player, player:get_inventory_formspec())
|
|
|
128 |
end)
|
|
|
129 |
|
|
|
130 |
minetest.register_globalstep(function(dtime)
|
|
|
131 |
update_elapsed = update_elapsed+dtime
|
|
|
132 |
-- TODO: Introduce option which will allow to adjust item list updating frequency
|
|
|
133 |
if update_elapsed<0.5 then
|
|
|
134 |
return
|
|
|
135 |
end
|
|
|
136 |
update_elapsed = 0
|
|
|
137 |
local players = minetest.get_connected_players()
|
|
|
138 |
for _,player in ipairs(players) do
|
|
|
139 |
local prev_inventory_formspec = prev_inventory_formspec_values[player:get_player_name()]
|
|
|
140 |
if prev_inventory_formspec then
|
|
|
141 |
update_if_needed(player, prev_inventory_formspec)
|
|
|
142 |
end
|
|
|
143 |
end
|
|
|
144 |
end)
|
|
|
145 |
|
|
|
146 |
minetest.register_chatcommand("formspec",{
|
|
|
147 |
func=function(name)
|
|
|
148 |
local window_info = minetest.get_player_window_information(name)
|
|
|
149 |
local formspec={
|
|
|
150 |
--"formspec_version[2]",
|
|
|
151 |
"size[5,5]",
|
|
|
152 |
"style_type[button;noclip=true]",
|
|
|
153 |
"button[0,0;1,1;button1;]",
|
|
|
154 |
"button[1,0;1,1;button2;]",
|
|
|
155 |
"button[2,0;1,1;button2;]",
|
|
|
156 |
"button[2.25,1;1,1;button2;]"
|
|
|
157 |
--string.format("button[%f,0;1,1;button1;]", window_info.max_formspec_size.x/2+2.5)
|
|
|
158 |
}
|
|
|
159 |
minetest.show_formspec(name, "fei:formspec", table.concat(formspec, ""))
|
|
|
160 |
end
|
|
|
161 |
})
|