207 lines
7.0 KiB
Lua
207 lines
7.0 KiB
Lua
|
--
|
||
|
-- Animated formspecs
|
||
|
--
|
||
|
|
||
|
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,
|
||
|
})
|