Subversion Repositories fei

Rev

Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

-- Finally Enough Items
-- Copyright (C) 2026 mrkubax10

-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Affero General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.

-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU Affero General Public License for more details.

-- You should have received a copy of the GNU Affero General Public License
-- along with this program. If not, see <https://www.gnu.org/licenses/>.

local prev_inventory_formspec_values={}
local update_elapsed = 0

-- Extracts next number from string from the beginning
local function scan_number(str)
        local begin_index = 0
        local end_index = 0
        local collect = false
        local index = 1
        for ch in string.gmatch(str, ".") do
                if collect then
                        if not string.match(ch, "%d") and ch~="." then
                                end_index = index
                                break
                        end
                else
                        if string.match(ch, "%d") then
                                begin_index = index
                                collect = true
                        end
                end
                index = index+1
        end
        if begin_index==end_index then
                return 0, str
        end
        return tonumber(string.sub(str, begin_index, end_index-1)), string.sub(str, end_index+1)
end

local function parse_formspec_information(formspec)
        local width = 0
        local height = 0
        local _,size_end = string.find(formspec, "size%[")
        if size_end then
                local size_pattern = string.sub(formspec, size_end)
                width, size_pattern = scan_number(size_pattern)
                height, size_pattern = scan_number(size_pattern)
        end

        return {
                width = width,
                height = height,
                -- TODO: Look for real_coordinates and formspec_version elements to determine this
                legacy_coordinates = true
        }
end

local function determine_anchor(window_info, inv_formspec_info)
        local element_spacing = inv_formspec_info.legacy_coordinates and 0.25 or 0
        local formspec_center_x = inv_formspec_info.width/2
        local formspec_center_y = inv_formspec_info.height/2
        local window_center_x = window_info.max_formspec_size.x/2
        local window_center_y = window_info.max_formspec_size.y/2
        return {
                x = formspec_center_x-element_spacing+window_center_x-window_center_x*element_spacing,
                y = formspec_center_y-element_spacing-window_center_y+window_center_y*element_spacing
        }
end

local function create_items_view(player, prev_inventory_formspec)
        local window_info = minetest.get_player_window_information(player:get_player_name())
        -- There is a brief moment when the player is still joining where get_player_window_information returns nil
        if not window_info then
                return ""
        end
        local inv_formspec_info = parse_formspec_information(player:get_formspec_prepend()..prev_inventory_formspec)
        local items_anchor = determine_anchor(window_info, inv_formspec_info)
        local formspec = {
                "style_type[label;noclip=true]",
                "style_type[image_button;noclip=true]",
                string.format("image_button[%f,%f;1,1;default_furnace_front.png;button1;]", items_anchor.x, items_anchor.y),
        }
        return table.concat(formspec, "")
end

local function update_if_needed(player, prev_inventory_formspec)
        player:set_inventory_formspec(prev_inventory_formspec..create_items_view(player, prev_inventory_formspec))
end

-- This is a kind of hacky way of registering in on_mods_loaded to make sure that our on_joinplayer handler is called last
minetest.register_on_mods_loaded(function()
        minetest.register_on_joinplayer(function(player)
                prev_inventory_formspec_values[player:get_player_name()] = player:get_inventory_formspec()
        end)
end)

minetest.register_on_leaveplayer(function(player)
        prev_inventory_formspec_values[player:get_player_name()] = nil
end)

minetest.register_on_player_receive_fields(function(player, formname, fields)
        -- If player inventory formspec was somehow updated, then change the previous formspec value too
        if formname~="" then
                return
        end
        prev_inventory_formspec_values[player:get_player_name()] = player:get_inventory_formspec()
        update_if_needed(player, player:get_inventory_formspec())
end)

minetest.register_globalstep(function(dtime)
        update_elapsed = update_elapsed+dtime
        -- TODO: Introduce option which will allow to adjust item list updating frequency
        if update_elapsed<0.5 then
                return
        end
        update_elapsed = 0
        local players = minetest.get_connected_players()
        for _,player in ipairs(players) do
                local prev_inventory_formspec = prev_inventory_formspec_values[player:get_player_name()]
                if prev_inventory_formspec then
                        update_if_needed(player, prev_inventory_formspec)
                end
        end
end)

minetest.register_chatcommand("formspec",{
        func=function(name)
                local window_info = minetest.get_player_window_information(name)
                local formspec={
                        --"formspec_version[2]",
                        "size[5,5]",
                        "style_type[button;noclip=true]",
                        "button[0,0;1,1;button1;]",
                        "button[1,0;1,1;button2;]",
                        "button[2,0;1,1;button2;]",
                        "button[2.25,1;1,1;button2;]"
                        --string.format("button[%f,0;1,1;button1;]", window_info.max_formspec_size.x/2+2.5)
                }
                minetest.show_formspec(name, "fei:formspec", table.concat(formspec, ""))
        end
})