форк від mtcontrib/led_marquee
		
	
		
			
				
	
	
		
			467 рядки
		
	
	
		
			12 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			467 рядки
		
	
	
		
			12 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| -- simple LED marquee mod
 | |
| -- by Vanessa Dannenberg
 | |
| 
 | |
| led_marquee = {}
 | |
| led_marquee.scheduled_messages = {}
 | |
| 
 | |
| -- settings
 | |
| led_marquee.message_minimum_time = tonumber(minetest.settings:get("led_marquee_message_minimum_time")) or 0.5
 | |
| led_marquee.message_schedule_dtime = tonumber(minetest.settings:get("led_marquee_message_schedule_dtime")) or 0.2
 | |
| led_marquee.message_schedule_size = tonumber(minetest.settings:get("led_marquee_message_schedule_size")) or 10
 | |
| 
 | |
| led_marquee.relay_timer = 0
 | |
| 
 | |
| local S = minetest.get_translator(minetest.get_current_modname())
 | |
| local FS = function(...) return minetest.formspec_escape(S(...)) end
 | |
| 
 | |
| local color_to_char = {
 | |
| 	"0",
 | |
| 	"1",
 | |
| 	"2",
 | |
| 	"3",
 | |
| 	"4",
 | |
| 	"5",
 | |
| 	"6",
 | |
| 	"7",
 | |
| 	"8",
 | |
| 	"9",
 | |
| 	"A",
 | |
| 	"B",
 | |
| 	"C",
 | |
| 	"D",
 | |
| 	"E",
 | |
| 	"F",
 | |
| 	"G",
 | |
| 	"H",
 | |
| 	"I",
 | |
| 	"J",
 | |
| 	"K",
 | |
| 	"L",
 | |
| 	"M",
 | |
| 	"N",
 | |
| 	"O",
 | |
| 	"P",
 | |
| 	"Q",
 | |
| 	"R"
 | |
| }
 | |
| 
 | |
| local char_to_color = {
 | |
| 	["0"] = 0,
 | |
| 	["1"] = 1,
 | |
| 	["2"] = 2,
 | |
| 	["3"] = 3,
 | |
| 	["4"] = 4,
 | |
| 	["5"] = 5,
 | |
| 	["6"] = 6,
 | |
| 	["7"] = 7,
 | |
| 	["8"] = 8,
 | |
| 	["9"] = 9,
 | |
| 
 | |
| 	["A"] = 10,
 | |
| 	["B"] = 11,
 | |
| 	["C"] = 12,
 | |
| 	["D"] = 13,
 | |
| 	["E"] = 14,
 | |
| 	["F"] = 15,
 | |
| 	["G"] = 16,
 | |
| 	["H"] = 17,
 | |
| 	["I"] = 18,
 | |
| 	["J"] = 19,
 | |
| 	["K"] = 20,
 | |
| 	["L"] = 21,
 | |
| 	["M"] = 22,
 | |
| 	["N"] = 23,
 | |
| 	["O"] = 24,
 | |
| 	["P"] = 25,
 | |
| 	["Q"] = 26,
 | |
| 	["R"] = 27,
 | |
| 
 | |
| 	["a"] = 10,
 | |
| 	["b"] = 11,
 | |
| 	["c"] = 12,
 | |
| 	["d"] = 13,
 | |
| 	["e"] = 14,
 | |
| 	["f"] = 15,
 | |
| 	["g"] = 16,
 | |
| 	["h"] = 17,
 | |
| 	["i"] = 18,
 | |
| 	["j"] = 19,
 | |
| 	["k"] = 20,
 | |
| 	["l"] = 21,
 | |
| 	["m"] = 22,
 | |
| 	["n"] = 23,
 | |
| 	["o"] = 24,
 | |
| 	["p"] = 25,
 | |
| 	["q"] = 26,
 | |
| 	["r"] = 27
 | |
| 
 | |
| }
 | |
| 
 | |
| -- 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",
 | |
| 			"formspec_version[4]"..
 | |
| 			"size[8,4]"..
 | |
| 			"button_exit[3,2.5;2,0.5;proceed;"..FS("Proceed").."]"..
 | |
| 			"field[1.75,1.5;4.5,0.5;channel;"..FS("Channel")..";${channel}]"
 | |
| 	)
 | |
| end
 | |
| 
 | |
| -- convert Lua's idea of a UTF-8 char to ISO-8859-1
 | |
| local get_iso = function(c)
 | |
| 	local hb = string.byte(c,1) or 0
 | |
| 	local lb = string.byte(c,2) or 0
 | |
| 	local dec = lb+hb*256
 | |
| 	local char = dec - 49664
 | |
| 	if dec > 49855 then char = dec - 49856 end
 | |
| 	return char
 | |
| end
 | |
| 
 | |
| local make_iso = function(s)
 | |
| 	local i = 1
 | |
| 	local s2 = ""
 | |
| 	while i <= string.len(s) do
 | |
| 		if string.byte(s,i) > 159 then
 | |
| 			local ciso = get_iso(string.sub(s, i, i+1))
 | |
| 			if ciso >= 0 and ciso < 256 then
 | |
| 				s2 = s2..string.char(ciso)
 | |
| 			else
 | |
| 				s2 = s2..string.char(127)
 | |
| 			end
 | |
| 			i = i + 2
 | |
| 		else
 | |
| 			s2 = s2..string.sub(s, i, i)
 | |
| 			i = i + 1
 | |
| 		end
 | |
| 	end
 | |
| 	return s2
 | |
| end
 | |
| 
 | |
| -- scrolling
 | |
| 
 | |
| led_marquee.set_timer = function(pos, timeout)
 | |
| 	local timer = minetest.get_node_timer(pos)
 | |
| 	timer:stop()
 | |
| 	if not timeout or timeout < led_marquee.message_minimum_time or timeout > 5 then return false end
 | |
| 
 | |
| 	if timeout > 0 then
 | |
| 		local meta = minetest.get_meta(pos)
 | |
| 		meta:set_int("timeout", timeout)
 | |
| 		timer:start(timeout)
 | |
| 	end
 | |
| end
 | |
| 
 | |
| led_marquee.scroll_text = function(pos, elapsed, skip)
 | |
| 	skip = skip or 1
 | |
| 	local meta = minetest.get_meta(pos)
 | |
| 	local msg = meta:get_string("last_msg")
 | |
| 	local channel = meta:get_string("channel")
 | |
| 	local index = meta:get_int("index")
 | |
| 	local color = meta:get_int("last_color")
 | |
| 	local colorchar = color_to_char[color+1]
 | |
| 	if not index or index < 1 then index = 1 end
 | |
| 	local len = string.len(msg)
 | |
| 	index = index + skip
 | |
| 	if index > len then index = 1 end
 | |
| 
 | |
| 	-- search backward to find the most recent color code in the string
 | |
| 	local r = index
 | |
| 	while r > 0 and not string.match(string.sub(msg, r, r+1), "/[0-9A-Ra-r]") do
 | |
| 		r = r - 1
 | |
| 	end
 | |
| 	if r == 0 then r = 1 end
 | |
| 	if string.match(string.sub(msg, r, r+1), "/[0-9A-Ra-r]") then
 | |
| 		colorchar = string.sub(msg, r+1, r+1)
 | |
| 	end
 | |
| 
 | |
| 	-- search forward to find the next printable symbol after the current index
 | |
| 	local f = index
 | |
| 	while f < len do
 | |
| 		if string.match(string.sub(msg, f-1, f), "/[0-9A-Ra-r]") then
 | |
| 			f = f + 2
 | |
| 		else
 | |
| 			break
 | |
| 		end
 | |
| 	end
 | |
| 	led_marquee.schedule_msg(pos, channel, "/"..colorchar..string.sub(msg, f)..string.rep(" ", skip + 1))
 | |
| 
 | |
| 	meta:set_int("index", f)
 | |
| 	if not elapsed or elapsed < 0.2 then return false end
 | |
| 	return true
 | |
| 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 }
 | |
| }
 | |
| 
 | |
| led_marquee.decode_color = function(msg)
 | |
| 
 | |
| end
 | |
| 
 | |
| minetest.register_globalstep(function(dtime)
 | |
| 	if dtime <= led_marquee.message_schedule_dtime
 | |
| 	  and (#led_marquee.scheduled_messages) > 0 then
 | |
| 		led_marquee.display_msg(
 | |
| 			led_marquee.scheduled_messages[1].pos,
 | |
| 			led_marquee.scheduled_messages[1].channel,
 | |
| 			led_marquee.scheduled_messages[1].msg
 | |
| 		)
 | |
| 	end
 | |
| 	table.remove(led_marquee.scheduled_messages, 1)
 | |
| end)
 | |
| 
 | |
| led_marquee.schedule_msg = function(pos, channel, msg)
 | |
| 	local idx = #led_marquee.scheduled_messages
 | |
| 	led_marquee.scheduled_messages[idx+1] = { pos=pos, channel=channel, msg=msg }
 | |
| 
 | |
| 	if idx >= led_marquee.message_schedule_size then
 | |
| 		table.remove(led_marquee.scheduled_messages, 1)
 | |
| 	end
 | |
| end
 | |
| 
 | |
| led_marquee.display_msg = function(pos, channel, msg)
 | |
| 	msg = string.sub(msg, 1, 6144).." "
 | |
| 	if string.sub(msg,1,1) == string.char(255) then -- treat it as incoming UTF-8
 | |
| 		msg = make_iso(string.sub(msg, 2, 6144))
 | |
| 	end
 | |
| 
 | |
| 	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(msg)
 | |
| 	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(msg, 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 == 10 then
 | |
| 			pos2.x = pos.x
 | |
| 			pos2.y = pos2.y-1
 | |
| 			pos2.z = pos.z
 | |
| 			i = i + 1
 | |
| 			wrapped = nil
 | |
| 		elseif asc == 29 then
 | |
| 			local c = string.byte(msg, i+1, i+1) or 0
 | |
| 			local r = string.byte(msg, i+2, i+2) or 0
 | |
| 			pos2.x = pos.x + (fdir_to_right[fdir+1][1])*c
 | |
| 			pos2.y = pos.y - r
 | |
| 			pos2.z = pos.z + (fdir_to_right[fdir+1][2])*c
 | |
| 			i = i + 3
 | |
| 			wrapped = nil
 | |
| 		elseif asc == 30 then -- translate to slash for printing
 | |
| 			minetest.swap_node(pos2, { name = "led_marquee:char_47", 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
 | |
| 		elseif asc == 47 then -- slash
 | |
| 			local ccode = string.sub(msg, i+1, i+1)
 | |
| 			if ccode then
 | |
| 				if char_to_color[ccode] then
 | |
| 					last_color = char_to_color[ccode]
 | |
| 					i = i + 2
 | |
| 				else
 | |
| 					minetest.swap_node(pos2, { name = "led_marquee:char_47", 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
 | |
| 				end
 | |
| 			end
 | |
| 			master_meta:set_int("last_color", last_color)
 | |
| 			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
 | |
| 		else
 | |
| 			i = i + 1
 | |
| 		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 == "clear" then
 | |
| 				led_marquee.set_timer(pos, 0)
 | |
| 				msg = string.rep(" ", 2048)
 | |
| 				meta:set_string("last_msg", msg)
 | |
| 				led_marquee.schedule_msg(pos, channel, msg)
 | |
| 				meta:set_int("index", 1)
 | |
| 			elseif msg == "allon" then
 | |
| 				led_marquee.set_timer(pos, 0)
 | |
| 				msg = string.rep(string.char(144), 2048)
 | |
| 				meta:set_string("last_msg", msg)
 | |
| 				led_marquee.schedule_msg(pos, channel, msg)
 | |
| 				meta:set_int("index", 1)
 | |
| 			elseif msg == "start_scroll" then
 | |
| 				local timeout = meta:get_int("timeout")
 | |
| 				led_marquee.set_timer(pos, timeout)
 | |
| 			elseif msg == "stop_scroll" then
 | |
| 				led_marquee.set_timer(pos, 0)
 | |
| 				return
 | |
| 			elseif string.sub(msg, 1, 12) == "scroll_speed" then
 | |
| 				local timeout = tonumber(string.sub(msg, 13)) or 0
 | |
| 				led_marquee.set_timer(pos, math.max(timeout, led_marquee.message_minimum_time))
 | |
| 			elseif string.sub(msg, 1, 11) == "scroll_step" then
 | |
| 				local skip = tonumber(string.sub(msg, 12))
 | |
| 				led_marquee.scroll_text(pos, nil, skip)
 | |
| 			elseif msg == "get" then -- get the master panel's displayed char as ASCII numerical value
 | |
| 				digilines.receptor_send(pos, digilines.rules.default, channel,
 | |
| 					tonumber(string.match(minetest.get_node(pos).name,"led_marquee:char_(.+)")))
 | |
| 			elseif msg == "getstr" then -- get the last stored message
 | |
| 				digilines.receptor_send(pos, digilines.rules.default, channel, meta:get_string("last_msg"))
 | |
| 			elseif msg == "getindex" then -- get the scroll index
 | |
| 				digilines.receptor_send(pos, digilines.rules.default, channel, meta:get_int("index"))
 | |
| 			else
 | |
| 				msg = string.gsub(msg, "//", string.char(30))
 | |
| 				led_marquee.set_timer(pos, 0)
 | |
| 				local last_msg = meta:get_string("last_msg")
 | |
| 				meta:set_string("last_msg", msg)
 | |
| 				led_marquee.schedule_msg(pos, channel, msg)
 | |
| 				if last_msg ~= msg then
 | |
| 					meta:set_int("index", 1)
 | |
| 				end
 | |
| 			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)})
 | |
| 				meta:set_string("last_msg", tostring(msg))
 | |
| 				meta:set_int("index", 1)
 | |
| 			end
 | |
| 		end
 | |
| 	elseif msg and type(msg) == "number" then
 | |
| 		meta:set_string("last_msg", tostring(msg))
 | |
| 		led_marquee.schedule_msg(pos, channel, tostring(msg))
 | |
| 		meta:set_int("index", 1)
 | |
| 	end
 | |
| end
 | |
| 
 | |
| -- the nodes!
 | |
| 
 | |
| for i = 31, 255 do
 | |
| 	local groups = { cracky = 2, not_in_creative_inventory = 1}
 | |
| 	local light = minetest.LIGHT_MAX-2
 | |
| 	local description = S("LED marquee panel (@1)", i)
 | |
| 	local leds = "led_marquee_char_"..i..".png^[mask:led_marquee_leds_on.png"
 | |
| 
 | |
| 	if i == 31 then
 | |
| 		leds ={
 | |
| 			name = "led_marquee_char_31.png^[mask:led_marquee_leds_on_cursor.png",
 | |
| 			animation = {type = "vertical_frames", aspect_w = 32, aspect_h = 32, length = 0.75}
 | |
| 		}
 | |
| 	end
 | |
| 
 | |
| 	local wimage
 | |
| 
 | |
| 	if i == 32 then
 | |
| 		groups = {cracky = 2}
 | |
| 		light = nil
 | |
| 		description = S("LED marquee panel")
 | |
| 		wimage = "led_marquee_leds_off.png^(led_marquee_char_155.png^[multiply:red)"
 | |
| 	end
 | |
| 
 | |
| 	minetest.register_node("led_marquee:char_"..i, {
 | |
| 		description = description,
 | |
| 		drawtype = "mesh",
 | |
| 		mesh = "led_marquee.obj",
 | |
| 		tiles = {
 | |
| 			{ name = "led_marquee_base.png", color = "white" },
 | |
| 			{ name = "led_marquee_leds_off.png", color = "white" }
 | |
| 		},
 | |
| 		overlay_tiles = { "", leds },
 | |
| 		inventory_image = wimage,
 | |
| 		is_ground_content = false,
 | |
| 		wield_image = wimage,
 | |
| 		palette="led_marquee_palette.png",
 | |
| 		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)
 | |
| 			local name = sender:get_player_name()
 | |
| 			if minetest.is_protected(pos, name) and not minetest.check_player_privs(name, {protection_bypass=true}) then
 | |
| 				minetest.record_protection_violation(pos, name)
 | |
| 				return
 | |
| 			end
 | |
| 			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",
 | |
| 		on_timer = led_marquee.scroll_text
 | |
| 	})
 | |
| 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" }
 | |
| 	},
 | |
| })
 | |
| 
 | |
| minetest.log("action", "[led_marquee] loaded.")
 |