This repository has been archived on 2021-03-28. You can view files and clone it, but cannot push or open issues or pull requests.
formspec_anim/init.lua

209 lines
7.1 KiB
Lua

--
-- Animated formspecs
--
-- Copyright © 2019 by luk3yx
--
formspec_anim = {}
sscsm.register({
name = 'formspec_anim',
file = minetest.get_modpath('formspec_anim') .. '/sscsm.lua',
})
local get_player_by_name
if minetest.global_exists('cloaking') then
get_player_by_name = cloaking.get_player_by_name
else
get_player_by_name = minetest.get_player_by_name
end
local clients = {}
local open = {}
minetest.register_on_leaveplayer(function(player)
local name = player:get_player_name()
clients[name] = nil
open[name] = nil
end)
-- Convert animated_image[] to a special image[] that can then be found and
-- replaced inside the SSCSM using a pattern.
local function parse_texture(raw, parse)
local texture, count, frame_duration =
parse.string(raw[3]):match('^(.*):([0-9]+),([0-9]+)$')
count, frame_duration = tonumber(count), tonumber(frame_duration)
assert(texture and count and frame_duration and
not texture:find('/', nil, true),
'Expected <texture name>:<frame count>,<frame duration>')
count = math.floor(count)
assert(count > 0 and frame_duration > 0,
'Frame count and duration must be above 0!')
assert(not texture:find('\001', nil, true),
'animated_image[] textures cannot contain U+0001!')
local texture_modifier = ''
local s, e = texture:find('%s', nil, true)
if s and e and not texture:find('%s', e + 1, true) then
-- The string contains a single %s, use this as a replacement character.
texture_modifier = texture:sub(e + 1)
texture = texture:sub(1, s - 1)
else
-- Otherwise just append _%s.png to the base texture
local s, e = texture:find('^', nil, true)
if s then
texture_modifier = texture:sub(s)
texture = texture:sub(1, s - 1)
end
texture = texture .. '_'
texture_modifier = '.png' .. texture_modifier
end
return texture, texture_modifier, count, frame_duration / 1000,
parse.boolean(raw[4] or '')
end
local custom_handlers = {}
function custom_handlers.animated_image(raw, parse)
local texture, texture_modifier, count, duration, one_shot =
parse_texture(raw, parse)
return {
type = 'image',
x = parse.number(raw[1][1]),
y = parse.number(raw[1][2]),
w = parse.number(raw[2][1]),
h = parse.number(raw[2][2]),
texture_name = '[\001formspec_anim\001:' .. count ..
(one_shot and '.' or '+') .. ',' .. duration .. ',' .. texture ..
'\001' .. texture_modifier,
}
end
custom_handlers['formspec_anim:animated_image'] =
custom_handlers.animated_image
-- If the client doesn't support SSCSMs, show a fallback.
-- Looping animations will have their first frame displayed and one-shot
-- animations will show their last.
local fallback_handlers = {}
function fallback_handlers.animated_image(raw, parse)
local texture, texture_modifier, count, duration, one_shot =
parse_texture(raw, parse)
return {
type = 'image',
x = parse.number(raw[1][1]),
y = parse.number(raw[1][2]),
w = parse.number(raw[2][1]),
h = parse.number(raw[2][2]),
texture_name = texture .. (one_shot and count or 1)
.. texture_modifier,
}
end
fallback_handlers['formspec_anim:animated_image'] =
fallback_handlers.animated_image
-- Show formspecs
-- ^AFORMSPEC_ANIM^A<formname>^A<formspec>
local sscsm_msg_prefix = '\001FORMSPEC_ANIM\001'
function formspec_anim.show_formspec(pname, formname, formspec)
if minetest.is_player(pname) then
pname = pname:get_player_name()
elseif not get_player_by_name(pname) then
return false, 'Unknown player!'
elseif formname:find('\001', nil, true) then
return false, 'The formname cannot contain \\x01!'
end
local handlers = clients[pname] and custom_handlers or fallback_handlers
local formspec, err = formspec_ast.interpret(formspec, handlers)
if not formspec then
return false, err
end
if clients[pname] then
open[pname] = formname
minetest.chat_send_player(pname, sscsm_msg_prefix .. formname ..
'\001' .. formspec)
else
minetest.show_formspec(pname, formname, formspec)
end
return true
end
-- Override minetest.show_formspec so that the SSCSM has time to stop the
-- animation.
local show_formspec = minetest.show_formspec
function minetest.show_formspec(pname, ...)
if open[pname] then
open[pname] = nil
minetest.chat_send_player(pname, sscsm_msg_prefix)
minetest.after(0, show_formspec, pname, ...)
else
return show_formspec(pname, ...)
end
end
-- Handle formspec input
minetest.register_chatcommand('formspec_anim', {
description = 'Displays an animation if your client supports it.',
func = function(name, param)
-- Just show a formspec if the SSCSM didn't send this command.
if param:sub(1, 1) ~= '\001' then
local err, msg = formspec_anim.show_formspec(name,
'formspec_anim:test',
-- 'size[8,9]animated_image[0,0;5,5;formspec_anim_water:16,50]'
-- .. 'field[5,0;3,1;name;label;Default text]'
-- .. 'button[5,1;3,1;button1;Button 1]'
-- .. 'button[5,2;3,1;quit;Stop animation]'
-- .. 'list[current_player;main;0,5;8,4;]')
'formspec_version[2]size[10,4]'
-- .. 'animated_image[0.25,3;9.5,0.6;busy^no_screenshot.png:72,25;' ..
-- tostring(param == 'oneshot') .. ']'
.. 'animated_image[4.25,2;1.5,1.5;gui_furnace_arrow_bg.png'
.. '^[lowpart:%s:gui_furnace_arrow_fg.png'
.. '^[transformR270:100,10;'
.. tostring(param == 'oneshot') .. ']'
.. 'formspec_ast:centered_label[0,1;10,1;Loading...]')
return err, err and 'Opened formspec!' or tostring(msg)
end
local player = get_player_by_name(name)
if not player then return end
param = param:sub(2)
if param == 'OK' then
clients[name] = true
return
end
-- Otherwise handle formspec input.
local s, e = param:find('\001', nil, true)
if not s or not e then
return false, '[formspec_anim] Invalid data sent!'
end
local formname = param:sub(1, s - 1)
if open[name] ~= formname then return end
local fields = minetest.parse_json(param:sub(e + 1))
if type(fields) ~= 'table' then return end
-- Ensure all fields are strings.
for k, v in pairs(fields) do
if type(k) ~= 'string' or type(v) ~= 'string' then
fields[k] = nil
end
end
-- "Close" the formspec on fields.quit
if fields.quit then
open[name] = nil
end
-- Call all register_on_player_receive_fields functions.
local funcs = minetest.registered_on_player_receive_fields
for i = #funcs, 1, -1 do
if funcs[i](player, formname, fields) then return end
end
end,
})