Initial commit
This commit is contained in:
commit
2e12f34c0d
|
@ -0,0 +1,12 @@
|
||||||
|
max_line_length = 80
|
||||||
|
|
||||||
|
globals = {
|
||||||
|
'formspec_ast',
|
||||||
|
'fs51',
|
||||||
|
'minetest',
|
||||||
|
}
|
||||||
|
|
||||||
|
read_globals = {
|
||||||
|
string = {fields = {'split', 'trim'}},
|
||||||
|
table = {fields = {'copy'}}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
|
||||||
|
# The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright © 2019-2021 by luk3yx.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,47 @@
|
||||||
|
# fs51
|
||||||
|
|
||||||
|
A compatibility layer that makes formspec_version 3 (and later) formspecs
|
||||||
|
render more correctly in Minetest 5.1.0 and earlier.
|
||||||
|
|
||||||
|
This will work with most mods without any additional configuration. If you want
|
||||||
|
to disable automatic formspec translation, add
|
||||||
|
`fs51.disable_monkey_patching = true` to minetest.conf.
|
||||||
|
|
||||||
|
## Why?
|
||||||
|
|
||||||
|
Minetest 5.1.0 introduced changes to formspecs that made them much less painful
|
||||||
|
to create and work with. However, formspecs are interpreted client-side and to
|
||||||
|
take advantage of these changes you would normally need to force everyone to
|
||||||
|
upgrade Minetest. This mod detects these older clients and modifies formspecs
|
||||||
|
sent to them to try and make sure they are at least usable.
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
1. Install the mod
|
||||||
|
2. Hope it works properly and doesn't break anything
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- If your mod stores `minetest.show_formspec` during load time, you'll need to
|
||||||
|
add `fs51` as an optional dependency to `mod.conf` so it can use the patched
|
||||||
|
show_formspec code.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
This mod depends on my [formspec_ast] library.
|
||||||
|
|
||||||
|
## API functions
|
||||||
|
|
||||||
|
You probably don't need to use these unless you're embedding fs51 outside of
|
||||||
|
Minetest or are using node formspecs.
|
||||||
|
|
||||||
|
- `fs51.backport(tree)`: Applies backports to a [formspec_ast] tree and
|
||||||
|
returns the modified tree. This does not modify the existing tree in place.
|
||||||
|
- `fs51.backport_string(formspec)`: Similar to
|
||||||
|
`formspec_ast.unparse(fs51.backport(formspec_ast.parse(formspec)))`.
|
||||||
|
|
||||||
|
*Unlike the automatic backporting, these functions will preserve newer elements
|
||||||
|
such as hypertext and background9 so the formspec will still work properly with
|
||||||
|
newer clients.*
|
||||||
|
|
||||||
|
[formspec_ast]: https://git.minetest.land/luk3yx/formspec_ast
|
|
@ -0,0 +1 @@
|
||||||
|
formspec_ast
|
|
@ -0,0 +1,210 @@
|
||||||
|
--
|
||||||
|
-- fs51 - Compatibility layer for Minetest formspecs
|
||||||
|
--
|
||||||
|
-- Copyright © 2019-2021 by luk3yx.
|
||||||
|
--
|
||||||
|
|
||||||
|
fs51 = {}
|
||||||
|
local fs51 = fs51
|
||||||
|
|
||||||
|
local padding, spacing_x, spacing_y = 3/8, 5/4, 15/13
|
||||||
|
|
||||||
|
-- Random offsets
|
||||||
|
local random_offsets = {
|
||||||
|
-- box = {{0, 0}, {0.2, 0.125}},
|
||||||
|
label = {{0, 0.3}},
|
||||||
|
field = {{-padding, -0.33}, {-0.25, -0.2}},
|
||||||
|
pwdfield = {{-padding, -0.33}, {-0.25, 0}},
|
||||||
|
-- textarea = {{-0.3, -0.33}, {-0.2, 0}},
|
||||||
|
textarea = {{-padding, 0}, {-0.25, -padding}},
|
||||||
|
dropdown = {{0, 0}, {-0.25, 0}},
|
||||||
|
checkbox = {{0, 0.5}},
|
||||||
|
background = {{(1 - spacing_x) / 2, (1 - spacing_y) / 2}},
|
||||||
|
tabheader = {{-padding, -padding}},
|
||||||
|
}
|
||||||
|
|
||||||
|
local fixers = {}
|
||||||
|
|
||||||
|
local function fix_pos(elem, random_offset)
|
||||||
|
if type(elem.x) == 'number' and type(elem.y) == 'number' then
|
||||||
|
if random_offset then
|
||||||
|
elem.x = elem.x - random_offset[1][1]
|
||||||
|
elem.y = elem.y - random_offset[1][2]
|
||||||
|
end
|
||||||
|
|
||||||
|
elem.x = (elem.x - padding) / spacing_x
|
||||||
|
elem.y = (elem.y - padding) / spacing_y
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function default_fixer(elem)
|
||||||
|
local random_offset = random_offsets[elem.type]
|
||||||
|
fix_pos(elem, random_offset)
|
||||||
|
|
||||||
|
if type(elem.w) == 'number' then
|
||||||
|
if random_offset and random_offset[2] then
|
||||||
|
elem.w = elem.w - random_offset[2][1]
|
||||||
|
end
|
||||||
|
elem.w = elem.w / spacing_x
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(elem.h) == 'number' then
|
||||||
|
if random_offset and random_offset[2] then
|
||||||
|
elem.h = elem.h - random_offset[2][2]
|
||||||
|
end
|
||||||
|
elem.h = elem.h / spacing_y
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Other fixers
|
||||||
|
function fixers.image_button(elem)
|
||||||
|
fix_pos(elem, random_offsets[elem.type])
|
||||||
|
elem.w = elem.w * 0.8 + 0.205
|
||||||
|
elem.h = elem.h * 0.866 + 0.134
|
||||||
|
end
|
||||||
|
fixers.item_image_button = fixers.image_button
|
||||||
|
fixers.image_button_exit = fixers.image_button
|
||||||
|
|
||||||
|
function fixers.textarea(elem)
|
||||||
|
local h = elem.h
|
||||||
|
default_fixer(elem)
|
||||||
|
elem.h = h + 0.15
|
||||||
|
end
|
||||||
|
|
||||||
|
fixers.image = fix_pos
|
||||||
|
fixers.item_image = fixers.image
|
||||||
|
|
||||||
|
function fixers.button(elem)
|
||||||
|
elem.type = 'image_' .. elem.type
|
||||||
|
elem.texture_name = 'blank.png'
|
||||||
|
return fixers.image_button(elem)
|
||||||
|
end
|
||||||
|
fixers.button_exit = fixers.button
|
||||||
|
|
||||||
|
function fixers.size(elem)
|
||||||
|
elem.w = elem.w / spacing_x - padding * 2 + 0.36
|
||||||
|
elem.h = elem.h / spacing_y - padding * 2
|
||||||
|
end
|
||||||
|
|
||||||
|
function fixers.list(elem, next_elem)
|
||||||
|
fix_pos(elem)
|
||||||
|
if elem.h < 2 then return end
|
||||||
|
|
||||||
|
-- Split the list[] into multiple list[]s.
|
||||||
|
local start = math.max(elem.starting_item_index or 0, 0)
|
||||||
|
for row = 1, elem.h do
|
||||||
|
local r = row - 1
|
||||||
|
elem[row] = {
|
||||||
|
type = 'list',
|
||||||
|
inventory_location = elem.inventory_location,
|
||||||
|
list_name = elem.list_name,
|
||||||
|
x = 0,
|
||||||
|
y = (r * 1.25) / spacing_y,
|
||||||
|
w = elem.w,
|
||||||
|
h = 1,
|
||||||
|
starting_item_index = start + (elem.w * r),
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Swap the second element and any listring[]
|
||||||
|
if row == 2 and next_elem and next_elem.type == 'listring' and
|
||||||
|
not next_elem.inventory_location then
|
||||||
|
for k, v in pairs(elem[2]) do
|
||||||
|
next_elem[k] = v
|
||||||
|
end
|
||||||
|
next_elem.x = elem.x
|
||||||
|
next_elem.y = elem.y + next_elem.y
|
||||||
|
elem[2] = {type = 'listring'}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Convert the base element to a container
|
||||||
|
for k, _ in pairs(elem) do
|
||||||
|
if type(k) ~= 'number' and k ~= 'x' and k ~= 'y' then
|
||||||
|
elem[k] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elem.type = 'container'
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Remove the "height" attribute on dropdowns.
|
||||||
|
function fixers.dropdown(elem)
|
||||||
|
fix_pos(elem)
|
||||||
|
elem.w = elem.w / spacing_y
|
||||||
|
elem.h = nil
|
||||||
|
elem.index_event = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
local pre_types = {size = true, position = true, anchor = true,
|
||||||
|
no_prepend = true}
|
||||||
|
local xywh = {'x', 'y', 'w', 'h'}
|
||||||
|
function fs51.backport(tree)
|
||||||
|
-- Flatten the tree (this will also copy it).
|
||||||
|
tree = formspec_ast.flatten(tree)
|
||||||
|
local real_coordinates = type(tree.formspec_version) == 'number' and
|
||||||
|
tree.formspec_version >= 2
|
||||||
|
tree.formspec_version = 1
|
||||||
|
|
||||||
|
-- Check for an initial real_coordinates[].
|
||||||
|
if not real_coordinates then
|
||||||
|
for _, elem in ipairs(tree) do
|
||||||
|
if elem.type == 'real_coordinates' then
|
||||||
|
real_coordinates = elem.bool
|
||||||
|
break
|
||||||
|
elseif not pre_types[elem.type] then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Allow deletion of real_coordinates[]
|
||||||
|
local i = 1
|
||||||
|
while tree[i] ~= nil do
|
||||||
|
local elem = tree[i]
|
||||||
|
if elem.type == 'real_coordinates' then
|
||||||
|
real_coordinates = elem.bool
|
||||||
|
table.remove(tree, i)
|
||||||
|
i = i - 1
|
||||||
|
elseif elem.type == 'list' and real_coordinates then
|
||||||
|
fixers.list(elem, tree[i + 1])
|
||||||
|
|
||||||
|
-- Flatten containers
|
||||||
|
if elem.type == 'container' and elem[1] then
|
||||||
|
formspec_ast.apply_offset(elem, elem.x, elem.y)
|
||||||
|
tree[i] = elem[1]
|
||||||
|
for j = 2, #elem do
|
||||||
|
i = i + 1
|
||||||
|
table.insert(tree, i, elem[j])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif real_coordinates then
|
||||||
|
(fixers[elem.type] or default_fixer)(elem)
|
||||||
|
for _, n in ipairs(xywh) do
|
||||||
|
if elem[n] then
|
||||||
|
elem[n] = math.floor(elem[n] * 1000) / 1000
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return tree
|
||||||
|
end
|
||||||
|
|
||||||
|
local minetest_log = rawget(_G, 'minetest') and minetest.log or print
|
||||||
|
function fs51.backport_string(formspec)
|
||||||
|
local fs, err = formspec_ast.parse(formspec)
|
||||||
|
if not fs then
|
||||||
|
minetest_log('warning', '[fs51] Error parsing formspec: ' ..
|
||||||
|
tostring(err))
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
return formspec_ast.unparse(fs51.backport(fs))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Monkey patch Minetest's code
|
||||||
|
if rawget(_G, 'minetest') and minetest.register_on_player_receive_fields and
|
||||||
|
not minetest.settings:get_bool('fs51.disable_monkey_patching') then
|
||||||
|
dofile(minetest.get_modpath('fs51') .. '/monkey_patching.lua')
|
||||||
|
end
|
|
@ -0,0 +1,120 @@
|
||||||
|
--
|
||||||
|
-- fs51 - Compatibility layer for Minetest formspecs
|
||||||
|
--
|
||||||
|
-- Copyright © 2021 by luk3yx.
|
||||||
|
--
|
||||||
|
|
||||||
|
local get_player_information = minetest.get_player_information
|
||||||
|
local type = type
|
||||||
|
local function backport_for(name, formspec)
|
||||||
|
local info = get_player_information(name)
|
||||||
|
local formspec_version = info and info.formspec_version or 1
|
||||||
|
if formspec_version >= 3 then return formspec end
|
||||||
|
|
||||||
|
local tree, err = formspec_ast.parse(formspec)
|
||||||
|
if not tree then
|
||||||
|
minetest.log('warning', '[fs51] Error parsing formspec (in ' ..
|
||||||
|
'monkey_patching.lua): ' .. tostring(err))
|
||||||
|
return formspec
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Add some placeholders
|
||||||
|
local modified
|
||||||
|
for node in formspec_ast.walk(tree) do
|
||||||
|
local node_type = node.type
|
||||||
|
if formspec_version == 1 and node_type == 'background9' then
|
||||||
|
-- No need to set modified here
|
||||||
|
node.type = 'background'
|
||||||
|
node.middle_x, node.middle_y = nil, nil
|
||||||
|
node.middle_x2, node.middle_y2 = nil, nil
|
||||||
|
elseif node_type == 'animated_image' then
|
||||||
|
modified = true
|
||||||
|
node.type = 'image'
|
||||||
|
local frame_start = node.frame_start or 1
|
||||||
|
node.texture_name = ('(%s)^[verticalframe:%d:%d'):format(
|
||||||
|
node.texture_name, node.frame_count, frame_start - 1)
|
||||||
|
elseif node_type == 'model' and node.textures[1] then
|
||||||
|
modified = true
|
||||||
|
node.type = 'image'
|
||||||
|
node.texture_name = node.textures[1]
|
||||||
|
elseif node_type == 'hypertext' then
|
||||||
|
-- Convert hypertext elements to regular textareas
|
||||||
|
modified = true
|
||||||
|
node.type = 'textarea'
|
||||||
|
node.name = ''
|
||||||
|
node.label = ''
|
||||||
|
node.default = node.text:gsub('<[^>]+>', '')
|
||||||
|
node.text = nil
|
||||||
|
elseif node_type == 'scroll_container' then
|
||||||
|
modified = true
|
||||||
|
node.type = 'container'
|
||||||
|
-- Scroll containers are always going to be broken on older clients
|
||||||
|
for i = #node, 1, -1 do
|
||||||
|
local inner_node = node[i]
|
||||||
|
if inner_node.x and inner_node.y and
|
||||||
|
(inner_node.x >= node.w or inner_node.y >= node.h) then
|
||||||
|
table.remove(node, i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif formspec_version == 1 and node_type == 'tabheader' then
|
||||||
|
node.w, node.h = nil, nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if formspec_version == 1 then
|
||||||
|
modified = true
|
||||||
|
tree = fs51.backport(tree)
|
||||||
|
end
|
||||||
|
|
||||||
|
if modified then
|
||||||
|
return assert(formspec_ast.unparse(tree))
|
||||||
|
end
|
||||||
|
return formspec
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Patch minetest.show_formspec()
|
||||||
|
local show_formspec = minetest.show_formspec
|
||||||
|
function minetest.show_formspec(pname, formname, formspec)
|
||||||
|
return show_formspec(pname, formname, backport_for(pname, formspec))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Patch player:set_inventory_formspec()
|
||||||
|
local old_set_inventory_formspec
|
||||||
|
local function new_set_inventory_formspec(self, formspec, ...)
|
||||||
|
return old_set_inventory_formspec(self,
|
||||||
|
backport_for(self:get_player_name(), formspec), ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
minetest.register_on_joinplayer(function(player)
|
||||||
|
if old_set_inventory_formspec == nil then
|
||||||
|
assert(type(player) == 'userdata', 'Fake player object?')
|
||||||
|
local cls = getmetatable(player)
|
||||||
|
old_set_inventory_formspec = cls.set_inventory_formspec
|
||||||
|
cls.set_inventory_formspec = new_set_inventory_formspec
|
||||||
|
|
||||||
|
-- In case the inventory formspec has been set in the meantime
|
||||||
|
player:set_inventory_formspec(player:get_inventory_formspec())
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Patch minetest.get_meta()
|
||||||
|
-- Inspired by https://gitlab.com/sztest/nodecore/-/blob/master/mods/nc_api
|
||||||
|
local old_nodemeta_set_string
|
||||||
|
local function new_nodemeta_set_string(self, k, v)
|
||||||
|
if k == 'formspec' and type(v) == 'string' then
|
||||||
|
v = fs51.backport_string(v) or v
|
||||||
|
end
|
||||||
|
return old_nodemeta_set_string(self, k, v)
|
||||||
|
end
|
||||||
|
|
||||||
|
local get_meta = minetest.get_meta
|
||||||
|
function minetest.get_meta(...)
|
||||||
|
local meta = get_meta(...)
|
||||||
|
if type(meta) == 'userdata' then
|
||||||
|
minetest.get_meta = get_meta
|
||||||
|
local cls = getmetatable(meta)
|
||||||
|
old_nodemeta_set_string = cls.set_string
|
||||||
|
cls.set_string = new_nodemeta_set_string
|
||||||
|
end
|
||||||
|
return meta
|
||||||
|
end
|
Loading…
Reference in New Issue