led_marquee/init.lua
Vanessa Dannenberg 4bb68ec009 basic multi-line support
Stack up a wall of LED panels, any horizontal and vertical amount

Set a channel on the upper left panel, leave the others un-set, and
connect a LuaC to it via digilines.

Long strings sent to that channel will be displayed using all panels,
starting at the upper-left and working from left to right, top to
bottom, wrapping from line to line as appropriate (similar to printing
to a shell terminal).

string.char(28) can be inserted anywhere to force a linefeed.

As usual, printing continues from node to node until the program either
finds a panel with a different non-empty channel than the first one, or
it finds a panel that's facing the wrong way.

If the program finds something other than a panel, it wraps to the next
line.  If it finds something other than a panel twice in a row, that
signals that text has wrapped off of the last row, and printing is cut
off.

Lines of panels don't need to be the same length, the program will wrap
as needed.

This commit also gets rid of the end-of-line padding, uses proper
string.rep() to create the all-off and all-on fills (each of which is
1kB), and fixes one or two other minor issues.

Strings are trimmed to 1 kB.

Panels are not erased between prints.
2018-08-16 01:46:03 -04:00

223 lines
6.7 KiB
Lua

-- simple LED marquee mod
-- by Vanessa Dannenberg
local S
if minetest.get_modpath("intllib") then
S = intllib.make_gettext_pair()
else
S = function(s) return s end
end
-- the following functions based on the so-named ones in Jeija's digilines mod
local reset_meta = function(pos)
minetest.get_meta(pos):set_string("formspec", "field[channel;Channel;${channel}]")
end
local on_digiline_receive_std = function(pos, node, channel, msg)
local meta = minetest.get_meta(pos)
local setchan = meta:get_string("channel")
if setchan ~= channel then return end
local num = tonumber(msg)
if msg == "colon" or msg == "period" or msg == "off" or (num and (num >= 0 and num <= 9)) then
minetest.swap_node(pos, { name = "led_marquee:marquee_"..msg, param2 = node.param2})
end
end
-- the nodes:
local fdir_to_right = {
{ 0, -1 },
{ 0, -1 },
{ 0, -1 },
{ 0, 1 },
{ 1, 0 },
{ -1, 0 },
}
local cbox = {
type = "wallmounted",
wall_top = { -8/16, 7/16, -8/16, 8/16, 8/16, 8/16 },
wall_bottom = { -8/16, -8/16, -8/16, 8/16, -7/16, 8/16 },
wall_side = { -8/16, -8/16, -8/16, -7/16, 8/16, 8/16 }
}
local display_string = function(pos, channel, string)
if string == "off_multi" then
string = string.rep(" ", 1024)
elseif string == "allon_multi" then
string = string.rep(string.char(144), 1024)
end
string = string.sub(string, 1, 1024)
local master_fdir = minetest.get_node(pos).param2 % 8
local master_meta = minetest.get_meta(pos)
local last_color = master_meta:get_int("last_color")
local pos2 = table.copy(pos)
if not last_color or last_color < 0 or last_color > 27 then
last_color = 0
master_meta:set_int("last_color", 0)
end
local i = 1
local len = string.len(string)
local wrapped = nil
while i <= len do
local node = minetest.get_node(pos2)
local fdir = node.param2 % 8
local meta = minetest.get_meta(pos2)
local setchan = nil
if meta then setchan = meta:get_string("channel") end
local asc = string.byte(string, i, i)
if not string.match(node.name, "led_marquee:char_") then
if not wrapped then
pos2.x = pos.x
pos2.y = pos2.y-1
pos2.z = pos.z
wrapped = true
else
break
end
elseif string.match(node.name, "led_marquee:char_")
and fdir ~= master_fdir or (setchan ~= nil and setchan ~= "" and setchan ~= channel) then
break
elseif asc == 28 then
pos2.x = pos.x
pos2.y = pos2.y-1
pos2.z = pos.z
i = i + 1
wrapped = nil
elseif asc > 30 and asc < 256 then
minetest.swap_node(pos2, { name = "led_marquee:char_"..asc, param2 = master_fdir + (last_color*8)})
pos2.x = pos2.x + fdir_to_right[fdir+1][1]
pos2.z = pos2.z + fdir_to_right[fdir+1][2]
i = i + 1
wrapped = nil
elseif asc < 28 then
last_color = asc
master_meta:set_int("last_color", asc)
i = i + 1
wrapped = nil
end
end
end
local on_digiline_receive_string = function(pos, node, channel, msg)
local meta = minetest.get_meta(pos)
local setchan = meta:get_string("channel")
local last_color = meta:get_int("last_color")
if not last_color or last_color < 0 or last_color > 27 then
last_color = 0
meta:set_int("last_color", 0)
end
local fdir = node.param2 % 8
if setchan ~= channel then return end
if msg and msg ~= "" and type(msg) == "string" then
if string.len(msg) > 1 then
if msg == "off" then
minetest.swap_node(pos, { name = "led_marquee:char_32", param2 = fdir + (last_color*8)})
elseif msg == "colon" then
minetest.swap_node(pos, { name = "led_marquee:char_58", param2 = fdir + (last_color*8)})
elseif msg == "period" then
minetest.swap_node(pos, { name = "led_marquee:char_46", param2 = fdir + (last_color*8)})
elseif msg == "del" then
minetest.swap_node(pos, { name = "led_marquee:char_127", param2 = fdir + (last_color*8)})
elseif msg == "allon" then
minetest.swap_node(pos, { name = "led_marquee:char_144", param2 = fdir + (last_color*8)})
elseif msg == "cursor" then
minetest.swap_node(pos, { name = "led_marquee:char_31", param2 = fdir + (last_color*8)})
else
display_string(pos, channel, msg)
end
else
local asc = string.byte(msg)
if asc > 30 and asc < 256 then
minetest.swap_node(pos, { name = "led_marquee:char_"..asc, param2 = fdir + (last_color*8)})
elseif asc < 28 then
last_color = asc
meta:set_int("last_color", asc)
elseif msg == "get" then -- get value as ASCII numerical value
digiline:receptor_send(pos, digiline.rules.default, channel, tonumber(string.match(minetest.get_node(pos).name,"led_marquee:char_(.+)"))) -- wonderfully horrible string manipulaiton
elseif msg == "getstr" then -- get actual char
digiline:receptor_send(pos, digiline.rules.default, channel, string.char(tonumber(string.match(minetest.get_node(pos).name,"led_marquee:char_(.+)"))))
end
end
elseif msg and type(msg) == "number" then
if msg == 0 then
minetest.swap_node(pos, { name = "led_marquee:char_32", param2 = fdir + (last_color*8)})
elseif msg > 30 then
minetest.swap_node(pos, { name = "led_marquee:char_"..tostring(msg), param2 = fdir + (last_color*8)})
end
end
end
for i = 31, 255 do
local groups = { cracky = 2, not_in_creative_inventory = 1}
local light = LIGHT_MAX-2
local description = S("Alphanumeric LED marquee panel ("..i..")")
local tiles = {
{ name="led_marquee_base.png", color="white"},
{ name="led_marquee_leds_off.png", color="white"},
"led_marquee_char_"..i..".png",
}
if i == 31 then
tiles = {
{ name="led_marquee_base.png", color="white"},
{ name="led_marquee_leds_off.png", color="white"},
{
name = "led_marquee_char_31.png",
animation = {type = "vertical_frames", aspect_w = 32, aspect_h = 32, length = 0.75}
}
}
end
if i == 32 then
groups = {cracky = 2}
light = nil
description = S("Alphanumeric LED marquee panel")
end
minetest.register_node("led_marquee:char_"..i, {
description = description,
drawtype = "mesh",
mesh = "led_marquee.obj",
tiles = tiles,
palette="led_marquee_palette.png",
use_texture_alpha = true,
groups = groups,
paramtype = "light",
paramtype2 = "colorwallmounted",
light_source = light,
selection_box = cbox,
node_box = cbox,
on_construct = function(pos)
reset_meta(pos)
end,
on_receive_fields = function(pos, formname, fields, sender)
if (fields.channel) then
minetest.get_meta(pos):set_string("channel", fields.channel)
end
end,
digiline = {
receptor = {},
effector = {
action = on_digiline_receive_string,
},
},
drop = "led_marquee:char_32"
})
end
-- crafts
minetest.register_craft({
output = "led_marquee:char_32 6",
recipe = {
{ "default:glass", "default:glass", "default:glass" },
{ "mesecons_lamp:lamp_off", "mesecons_lamp:lamp_off", "mesecons_lamp:lamp_off" },
{ "group:wood", "mesecons_microcontroller:microcontroller0000", "group:wood" }
},
})