1 -- This mod provides the visible text on signs library used by Home Decor
2 -- and perhaps other mods at some point in the future. Forked from thexyz's/
3 -- PilzAdam's original text-on-signs mod and rewritten by Vanessa Ezekowitz
7 -- { delta = {entity position for 0° yaw}, exact yaw expression }
8 -- { delta = {entity position for 180° yaw}, exact yaw expression }
9 -- { delta = {entity position for 270° yaw}, exact yaw expression }
10 -- { delta = {entity position for 90° yaw}, exact yaw expression }
12 -- Made colored metal signs optionals
13 local enable_colored_metal_signs = true
15 -- CWz's keyword interact mod uses this setting.
16 local current_keyword = minetest.settings:get("interact_keyword") or "iaccept"
19 signs_lib.path = minetest.get_modpath(minetest.get_current_modname())
20 screwdriver = screwdriver or {}
22 -- Load support for intllib.
23 local S, NS = dofile(signs_lib.path .. "/intllib.lua")
27 dofile(signs_lib.path .. "/encoding.lua");
30 local wall_dir_change = {
40 signs_lib.wallmounted_rotate = function(pos, node, user, mode)
41 if mode ~= screwdriver.ROTATE_FACE then return false end
42 minetest.swap_node(pos, { name = node.name, param2 = wall_dir_change[node.param2 % 6] })
43 for _, v in ipairs(minetest.get_objects_inside_radius(pos, 0.5)) do
44 local e = v:get_luaentity()
45 if e and e.name == "signs:text" then
49 signs_lib.update_sign(pos)
53 signs_lib.facedir_rotate = function(pos, node, user, mode)
54 if mode ~= screwdriver.ROTATE_FACE then return false end
55 newparam2 = (node.param2 %8) + 1
56 if newparam2 == 5 then
58 elseif newparam2 > 6 then
61 minetest.swap_node(pos, { name = node.name, param2 = newparam2 })
62 for _, v in ipairs(minetest.get_objects_inside_radius(pos, 0.5)) do
63 local e = v:get_luaentity()
64 if e and e.name == "signs:text" then
68 signs_lib.update_sign(pos)
72 signs_lib.facedir_rotate_simple = function(pos, node, user, mode)
73 if mode ~= screwdriver.ROTATE_FACE then return false end
74 newparam2 = (node.param2 %8) + 1
75 if newparam2 > 3 then newparam2 = 0 end
76 minetest.swap_node(pos, { name = node.name, param2 = newparam2 })
77 for _, v in ipairs(minetest.get_objects_inside_radius(pos, 0.5)) do
78 local e = v:get_luaentity()
79 if e and e.name == "signs:text" then
83 signs_lib.update_sign(pos)
92 signs_lib.modpath = minetest.get_modpath("signs_lib")
94 local DEFAULT_TEXT_SCALE = {x=0.8, y=0.5}
96 signs_lib.regular_wall_sign_model = {
99 wall_side = { -0.5, -0.25, -0.4375, -0.4375, 0.375, 0.4375 },
100 wall_bottom = { -0.4375, -0.5, -0.25, 0.4375, -0.4375, 0.375 },
101 wall_top = { -0.4375, 0.4375, -0.375, 0.4375, 0.5, 0.25 }
106 {delta = { x = 0.41, y = 0.07, z = 0 }, yaw = math.pi / -2},
107 {delta = { x = -0.41, y = 0.07, z = 0 }, yaw = math.pi / 2},
108 {delta = { x = 0, y = 0.07, z = 0.41 }, yaw = 0},
109 {delta = { x = 0, y = 0.07, z = -0.41 }, yaw = math.pi},
113 signs_lib.metal_wall_sign_model = {
116 fixed = {-0.4375, -0.25, 0.4375, 0.4375, 0.375, 0.5}
119 {delta = { x = 0, y = 0.07, z = 0.41 }, yaw = 0},
120 {delta = { x = 0.41, y = 0.07, z = 0 }, yaw = math.pi / -2},
121 {delta = { x = 0, y = 0.07, z = -0.41 }, yaw = math.pi},
122 {delta = { x = -0.41, y = 0.07, z = 0 }, yaw = math.pi / 2},
126 signs_lib.yard_sign_model = {
130 {-0.4375, -0.25, -0.0625, 0.4375, 0.375, 0},
131 {-0.0625, -0.5, -0.0625, 0.0625, -0.1875, 0},
135 {delta = { x = 0, y = 0.07, z = -0.08 }, yaw = 0},
136 {delta = { x = -0.08, y = 0.07, z = 0 }, yaw = math.pi / -2},
137 {delta = { x = 0, y = 0.07, z = 0.08 }, yaw = math.pi},
138 {delta = { x = 0.08, y = 0.07, z = 0 }, yaw = math.pi / 2},
142 signs_lib.hanging_sign_model = {
146 {-0.4375, -0.3125, -0.0625, 0.4375, 0.3125, 0},
147 {-0.4375, 0.25, -0.03125, 0.4375, 0.5, -0.03125},
151 {delta = { x = 0, y = -0.02, z = -0.08 }, yaw = 0},
152 {delta = { x = -0.08, y = -0.02, z = 0 }, yaw = math.pi / -2},
153 {delta = { x = 0, y = -0.02, z = 0.08 }, yaw = math.pi},
154 {delta = { x = 0.08, y = -0.02, z = 0 }, yaw = math.pi / 2},
158 signs_lib.sign_post_model = {
162 {-0.4375, -0.25, -0.1875, 0.4375, 0.375, -0.125},
163 {-0.125, -0.5, -0.125, 0.125, 0.5, 0.125},
167 {delta = { x = 0, y = 0.07, z = -0.2 }, yaw = 0},
168 {delta = { x = -0.2, y = 0.07, z = 0 }, yaw = math.pi / -2},
169 {delta = { x = 0, y = 0.07, z = 0.2 }, yaw = math.pi},
170 {delta = { x = 0.2, y = 0.07, z = 0 }, yaw = math.pi / 2},
174 -- the list of standard sign nodes
176 signs_lib.sign_node_list = {
177 "default:sign_wall_wood",
178 "default:sign_wall_steel",
180 "signs:sign_hanging",
181 "signs:sign_wall_green",
182 "signs:sign_wall_yellow",
183 "signs:sign_wall_red",
184 "signs:sign_wall_white_red",
185 "signs:sign_wall_white_black",
186 "signs:sign_wall_orange",
187 "signs:sign_wall_blue",
188 "signs:sign_wall_brown",
189 "locked_sign:sign_wall_locked"
192 local default_sign, default_sign_image
194 -- Default sign was renamed in 0.4.14. Support both & old versions.
195 if minetest.registered_nodes["default:sign_wall_wood"] then
196 default_sign = "default:sign_wall_wood"
197 default_sign_image = "default_sign_wood.png"
199 default_sign = "default:sign_wall"
200 default_sign_image = "default_sign_wall.png"
203 default_sign_metal = "default:sign_wall_steel"
204 default_sign_metal_image = "default_sign_steel.png"
208 function signs_lib.table_copy(t)
210 for k, v in pairs(t) do
211 if type(v) == "table" then
212 nt[k] = signs_lib.table_copy(v)
222 if not minetest.settings:get_bool("creative_mode") then
223 signs_lib.expect_infinite_stacks = false
225 signs_lib.expect_infinite_stacks = true
230 -- Path to the textures.
231 local TP = signs_lib.path .. "/textures"
232 -- Font file formatter
233 local CHAR_FILE = "%s_%02x.png"
235 local CHAR_PATH = TP .. "/" .. CHAR_FILE
238 local font_name = "hdf"
240 -- Lots of overkill here. KISS advocates, go away, shoo! ;) -- kaeza
242 local PNG_HDR = string.char(0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A)
244 -- check if a file does exist
245 -- to avoid reopening file after checking again
246 -- pass TRUE as second argument
247 function file_exists(name, return_handle, mode)
249 local f = io.open(name, mode)
251 if (return_handle) then
261 -- Read the image size from a PNG file.
262 -- Returns image_w, image_h.
263 -- Only the LSB is read from each field!
264 local function read_image_size(filename)
265 local f = file_exists(filename, true, "rb")
266 -- file might not exist (don't crash the game)
271 local hdr = f:read(string.len(PNG_HDR))
272 if hdr ~= PNG_HDR then
281 return ws:byte(), hs:byte()
284 -- Set by build_char_db()
287 local COLORBGW, COLORBGH
289 -- Size of the canvas, in characters.
290 -- Please note that CHARS_PER_LINE is multiplied by the average character
291 -- width to get the total width of the canvas, so for proportional fonts,
292 -- either more or fewer characters may fit on a line.
293 local CHARS_PER_LINE = 30
294 local NUMBER_OF_LINES = 6
296 -- 6 rows, max 80 chars per, plus a bit of fudge to
297 -- avoid excess trimming (e.g. due to color codes)
299 local MAX_INPUT_CHARS = 600
301 -- This holds the individual character widths.
302 -- Indexed by the actual character (e.g. charwidth["A"])
305 -- helper functions to trim sign text input/output
307 local function trim_input(text)
308 return text:sub(1, math.min(MAX_INPUT_CHARS, text:len()))
311 local function build_char_db()
315 -- To calculate average char width.
316 local total_width = 0
320 local w, h = read_image_size(CHAR_PATH:format(font_name, c))
322 local ch = string.char(c)
324 total_width = total_width + w
325 char_count = char_count + 1
329 COLORBGW, COLORBGH = read_image_size(TP.."/slc_n.png")
330 assert(COLORBGW and COLORBGH, "error reading bg dimensions")
331 LINE_HEIGHT = COLORBGH
333 -- XXX: Is there a better way to calc this?
334 SIGN_WIDTH = math.floor((total_width / char_count) * CHARS_PER_LINE)
338 local sign_groups = {choppy=2, dig_immediate=2}
340 local fences_with_sign = { }
342 -- some local helper functions
344 local function split_lines_and_words_old(text)
347 if not text then return end
348 for word in text:gmatch("%S+") do
350 table.insert(lines, line)
351 if #lines >= NUMBER_OF_LINES then break end
353 elseif word == "\\|" then
354 table.insert(line, "|")
356 table.insert(line, word)
359 table.insert(lines, line)
363 local function split_lines_and_words(text)
364 if not text then return end
365 text = string.gsub(text, "@KEYWORD", current_keyword)
367 for _, line in ipairs(text:split("\n")) do
368 table.insert(lines, line:split(" "))
373 local math_max = math.max
375 local function fill_line(x, y, w, c)
378 for xx = 0, math.max(0, w), COLORBGW do
379 table.insert(tex, (":%d,%d=slc_%s.png"):format(x + xx, y, c))
381 return table.concat(tex)
384 -- make char texture file name
385 -- if texture file does not exist use fallback texture instead
386 local function char_tex(font_name, ch)
388 local exists, tex = file_exists(CHAR_PATH:format(font_name, c))
389 if exists and c ~= 14 then
390 tex = CHAR_FILE:format(font_name, c)
392 tex = CHAR_FILE:format(font_name, 0x0)
397 local function make_line_texture(line, lineno, pos)
403 local n = minetest.registered_nodes[minetest.get_node(pos).name]
404 local default_color = n.default_color or 0
406 local cur_color = tonumber(default_color, 16)
408 -- We check which chars are available here.
409 for word_i, word in ipairs(line) do
415 local c = word:sub(i, i)
417 local cc = tonumber(word:sub(i+1, i+1), 16)
423 local w = charwidth[c]
425 width = width + w + 1
426 if width >= (SIGN_WIDTH - charwidth[" "]) then
429 maxw = math_max(width, maxw)
431 if #chars < MAX_INPUT_CHARS then
432 table.insert(chars, {
434 tex = char_tex(font_name, c),
435 col = ("%X"):format(cur_color),
438 ch_offs = ch_offs + w
443 width = width + charwidth[" "] + 1
444 maxw = math_max(width, maxw)
445 table.insert(words, { chars=chars, w=ch_offs })
448 -- Okay, we actually build the "line texture" here.
452 local start_xpos = math.floor((SIGN_WIDTH - maxw) / 2)
454 local xpos = start_xpos
455 local ypos = (LINE_HEIGHT * lineno)
459 for word_i, word in ipairs(words) do
460 local xoffs = (xpos - start_xpos)
461 if (xoffs > 0) and ((xoffs + word.w) > maxw) then
462 table.insert(texture, fill_line(xpos, ypos, maxw, "n"))
464 ypos = ypos + LINE_HEIGHT
466 if lineno >= NUMBER_OF_LINES then break end
467 table.insert(texture, fill_line(xpos, ypos, maxw, cur_color))
469 for ch_i, ch in ipairs(word.chars) do
470 if ch.col ~= cur_color then
472 table.insert(texture, fill_line(xpos + ch.off, ypos, maxw, cur_color))
474 table.insert(texture, (":%d,%d=%s"):format(xpos + ch.off, ypos, ch.tex))
478 (":%d,%d="):format(xpos + word.w, ypos) .. char_tex(font_name, " ")
480 xpos = xpos + word.w + charwidth[" "]
481 if xpos >= (SIGN_WIDTH + charwidth[" "]) then break end
484 table.insert(texture, fill_line(xpos, ypos, maxw, "n"))
485 table.insert(texture, fill_line(start_xpos, ypos + LINE_HEIGHT, maxw, "n"))
487 return table.concat(texture), lineno
490 local function make_sign_texture(lines, pos)
491 local texture = { ("[combine:%dx%d"):format(SIGN_WIDTH, LINE_HEIGHT * NUMBER_OF_LINES) }
494 if lineno >= NUMBER_OF_LINES then break end
495 local linetex, ln = make_line_texture(lines[i], lineno, pos)
496 table.insert(texture, linetex)
499 table.insert(texture, "^[makealpha:0,0,0")
500 return table.concat(texture, "")
503 local function set_obj_text(obj, text, new, pos)
504 local split = new and split_lines_and_words or split_lines_and_words_old
505 local text_ansi = Utf8ToAnsi(text)
506 local n = minetest.registered_nodes[minetest.get_node(pos).name]
507 local text_scale = (n and n.text_scale) or DEFAULT_TEXT_SCALE
509 textures={make_sign_texture(split(text_ansi), pos)},
510 visual_size = text_scale,
514 signs_lib.construct_sign = function(pos, locked)
515 local meta = minetest.get_meta(pos)
519 "textarea[0,-0.3;6.5,3;text;;${text}]"..
520 "button_exit[2,3.4;2,1;ok;"..S("Write").."]"..
521 "background[-0.5,-0.5;7,5;bg_signs_lib.jpg]")
522 meta:set_string("infotext", "")
525 signs_lib.destruct_sign = function(pos)
526 local objects = minetest.get_objects_inside_radius(pos, 0.5)
527 for _, v in ipairs(objects) do
528 local e = v:get_luaentity()
529 if e and e.name == "signs:text" then
535 local function make_infotext(text)
536 text = trim_input(text)
537 local lines = split_lines_and_words(text) or {}
539 for _, line in ipairs(lines) do
540 table.insert(lines2, (table.concat(line, " "):gsub("#[0-9a-fA-F]", ""):gsub("##", "#")))
542 return table.concat(lines2, "\n")
545 signs_lib.update_sign = function(pos, fields, owner)
547 -- First, check if the interact keyword from CWz's mod is being set,
548 -- or has been changed since the last restart...
550 local meta = minetest.get_meta(pos)
551 local stored_text = meta:get_string("text") or ""
552 current_keyword = rawget(_G, "mki_interact_keyword") or current_keyword
554 if fields then -- ...we're editing the sign.
555 if fields.text and string.find(dump(fields.text), "@KEYWORD") then
556 meta:set_string("keyword", current_keyword)
558 meta:set_string("keyword", nil)
560 elseif string.find(dump(stored_text), "@KEYWORD") then -- we need to check if the password is being set/changed
562 local stored_keyword = meta:get_string("keyword")
563 if stored_keyword and stored_keyword ~= "" and stored_keyword ~= current_keyword then
564 signs_lib.destruct_sign(pos)
565 meta:set_string("keyword", current_keyword)
567 if owner then ownstr = S("Locked sign, owned by @1\n", owner) end
568 meta:set_string("infotext", ownstr..string.gsub(make_infotext(stored_text), "@KEYWORD", current_keyword).." ")
576 fields.text = trim_input(fields.text)
579 if owner then ownstr = S("Locked sign, owned by @1\n", owner) end
581 meta:set_string("infotext", ownstr..string.gsub(make_infotext(fields.text), "@KEYWORD", current_keyword).." ")
582 meta:set_string("text", fields.text)
584 meta:set_int("__signslib_new_format", 1)
587 new = (meta:get_int("__signslib_new_format") ~= 0)
589 local text = meta:get_string("text")
590 if text == nil then return end
591 local objects = minetest.get_objects_inside_radius(pos, 0.5)
593 for _, v in ipairs(objects) do
594 local e = v:get_luaentity()
595 if e and e.name == "signs:text" then
599 set_obj_text(v, text, new, pos)
608 -- if there is no entity
610 local signnode = minetest.get_node(pos)
611 local signname = signnode.name
612 local textpos = minetest.registered_nodes[signname].textpos
614 sign_info = textpos[minetest.get_node(pos).param2 + 1]
615 elseif signnode.name == "signs:sign_yard" then
616 sign_info = signs_lib.yard_sign_model.textpos[minetest.get_node(pos).param2 + 1]
617 elseif signnode.name == "signs:sign_hanging" then
618 sign_info = signs_lib.hanging_sign_model.textpos[minetest.get_node(pos).param2 + 1]
619 elseif string.find(signnode.name, "sign_wall") then
620 if signnode.name == default_sign
621 or signnode.name == default_sign_metal
622 or signnode.name == "locked_sign:sign_wall_locked" then
623 sign_info = signs_lib.regular_wall_sign_model.textpos[minetest.get_node(pos).param2 + 1]
625 sign_info = signs_lib.metal_wall_sign_model.textpos[minetest.get_node(pos).param2 + 1]
627 else -- ...it must be a sign on a fence post.
628 sign_info = signs_lib.sign_post_model.textpos[minetest.get_node(pos).param2 + 1]
630 if sign_info == nil then
633 local text = minetest.add_entity({x = pos.x + sign_info.delta.x,
634 y = pos.y + sign_info.delta.y,
635 z = pos.z + sign_info.delta.z}, "signs:text")
636 text:setyaw(sign_info.yaw)
639 -- What kind of sign do we need to place, anyway?
641 function signs_lib.determine_sign_type(itemstack, placer, pointed_thing, locked)
643 name = minetest.get_node(pointed_thing.under).name
644 if fences_with_sign[name] then
645 if minetest.is_protected(pointed_thing.under, placer:get_player_name()) then
646 minetest.record_protection_violation(pointed_thing.under,
647 placer:get_player_name())
651 name = minetest.get_node(pointed_thing.above).name
652 local def = minetest.registered_nodes[name]
653 if not def.buildable_to then
656 if minetest.is_protected(pointed_thing.above, placer:get_player_name()) then
657 minetest.record_protection_violation(pointed_thing.above,
658 placer:get_player_name())
663 local node=minetest.get_node(pointed_thing.under)
665 if minetest.registered_nodes[node.name] and
666 minetest.registered_nodes[node.name].on_rightclick and
667 not placer:get_player_control().sneak then
668 return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack, pointed_thing)
670 local above = pointed_thing.above
671 local under = pointed_thing.under
672 local dir = {x = under.x - above.x,
673 y = under.y - above.y,
674 z = under.z - above.z}
676 local wdir = minetest.dir_to_wallmounted(dir)
678 local placer_pos = placer:getpos()
681 x = above.x - placer_pos.x,
682 y = above.y - placer_pos.y,
683 z = above.z - placer_pos.z
687 local fdir = minetest.dir_to_facedir(dir)
688 local pt_name = minetest.get_node(under).name
689 local signname = itemstack:get_name()
691 if fences_with_sign[pt_name] and signname == default_sign then
692 minetest.add_node(under, {name = fences_with_sign[pt_name], param2 = fdir})
693 elseif wdir == 0 and signname == default_sign then
694 minetest.add_node(above, {name = "signs:sign_hanging", param2 = fdir})
695 elseif wdir == 1 and signname == default_sign then
696 minetest.add_node(above, {name = "signs:sign_yard", param2 = fdir})
697 elseif signname == default_sign_metal then
698 minetest.add_node(above, {name = signname, param2 = wdir })
699 elseif signname ~= default_sign
700 and signname ~= default_sign_metal
701 and signname ~= "locked_sign:sign_wall_locked" then -- it's a signs_lib colored metal wall sign.
702 minetest.add_node(above, {name = signname, param2 = fdir})
703 else -- it must be a default or locked wooden wall sign
704 minetest.add_node(above, {name = signname, param2 = wdir }) -- note it's wallmounted here!
706 local meta = minetest.get_meta(above)
707 local owner = placer:get_player_name()
708 meta:set_string("owner", owner)
712 if not signs_lib.expect_infinite_stacks then
713 itemstack:take_item()
719 function signs_lib.receive_fields(pos, formname, fields, sender, lock)
720 if minetest.is_protected(pos, sender:get_player_name()) then
721 minetest.record_protection_violation(pos,
722 sender:get_player_name())
725 local lockstr = lock and S("locked ") or ""
726 if fields and fields.text and fields.ok then
727 minetest.log("action", S("@1 wrote \"@2\" to @3sign at @4",
728 (sender:get_player_name() or ""),
729 fields.text:gsub('\\', '\\\\'):gsub("\n", "\\n"),
731 minetest.pos_to_string(pos)
734 signs_lib.update_sign(pos, fields, sender:get_player_name())
736 signs_lib.update_sign(pos, fields)
741 minetest.register_node(":"..default_sign, {
742 description = S("Sign"),
743 inventory_image = default_sign_image,
744 wield_image = default_sign_image,
745 node_placement_prediction = "",
746 sunlight_propagates = true,
748 paramtype2 = "wallmounted",
749 drawtype = "nodebox",
750 node_box = signs_lib.regular_wall_sign_model.nodebox,
751 tiles = {"signs_wall_sign.png"},
752 groups = sign_groups,
754 on_place = function(itemstack, placer, pointed_thing)
755 return signs_lib.determine_sign_type(itemstack, placer, pointed_thing)
757 on_construct = function(pos)
758 signs_lib.construct_sign(pos)
760 on_destruct = function(pos)
761 signs_lib.destruct_sign(pos)
763 on_receive_fields = function(pos, formname, fields, sender)
764 signs_lib.receive_fields(pos, formname, fields, sender)
766 on_punch = function(pos, node, puncher)
767 signs_lib.update_sign(pos)
769 on_rotate = signs_lib.wallmounted_rotate
772 minetest.register_node(":signs:sign_yard", {
774 sunlight_propagates = true,
775 paramtype2 = "facedir",
776 drawtype = "nodebox",
777 node_box = signs_lib.yard_sign_model.nodebox,
780 fixed = {-0.4375, -0.5, -0.0625, 0.4375, 0.375, 0}
782 tiles = {"signs_top.png", "signs_bottom.png", "signs_side.png", "signs_side.png", "signs_back.png", "signs_front.png"},
783 groups = {choppy=2, dig_immediate=2},
786 on_construct = function(pos)
787 signs_lib.construct_sign(pos)
789 on_destruct = function(pos)
790 signs_lib.destruct_sign(pos)
792 on_receive_fields = function(pos, formname, fields, sender)
793 signs_lib.receive_fields(pos, formname, fields, sender)
795 on_punch = function(pos, node, puncher)
796 signs_lib.update_sign(pos)
798 on_rotate = signs_lib.facedir_rotate_simple
802 minetest.register_node(":signs:sign_hanging", {
804 sunlight_propagates = true,
805 paramtype2 = "facedir",
806 drawtype = "nodebox",
807 node_box = signs_lib.hanging_sign_model.nodebox,
810 fixed = {-0.45, -0.275, -0.049, 0.45, 0.5, 0.049}
813 "signs_hanging_top.png",
814 "signs_hanging_bottom.png",
815 "signs_hanging_side.png",
816 "signs_hanging_side.png",
817 "signs_hanging_back.png",
818 "signs_hanging_front.png"
820 groups = {choppy=2, dig_immediate=2},
823 on_construct = function(pos)
824 signs_lib.construct_sign(pos)
826 on_destruct = function(pos)
827 signs_lib.destruct_sign(pos)
829 on_receive_fields = function(pos, formname, fields, sender)
830 signs_lib.receive_fields(pos, formname, fields, sender)
832 on_punch = function(pos, node, puncher)
833 signs_lib.update_sign(pos)
835 on_rotate = signs_lib.facedir_rotate_simple
838 minetest.register_node(":signs:sign_post", {
840 sunlight_propagates = true,
841 paramtype2 = "facedir",
842 drawtype = "nodebox",
843 node_box = signs_lib.sign_post_model.nodebox,
845 "signs_post_top.png",
846 "signs_post_bottom.png",
847 "signs_post_side.png",
848 "signs_post_side.png",
849 "signs_post_back.png",
850 "signs_post_front.png",
852 groups = {choppy=2, dig_immediate=2},
856 { items = { default_sign }},
857 { items = { "default:fence_wood" }},
860 on_rotate = signs_lib.facedir_rotate_simple
865 minetest.register_privilege("sign_editor", S("Can edit all locked signs"))
867 minetest.register_node(":locked_sign:sign_wall_locked", {
868 description = S("Locked Sign"),
869 inventory_image = "signs_locked_inv.png",
870 wield_image = "signs_locked_inv.png",
871 node_placement_prediction = "",
872 sunlight_propagates = true,
874 paramtype2 = "wallmounted",
875 drawtype = "nodebox",
876 node_box = signs_lib.regular_wall_sign_model.nodebox,
877 tiles = { "signs_wall_sign_locked.png" },
878 groups = sign_groups,
879 on_place = function(itemstack, placer, pointed_thing)
880 return signs_lib.determine_sign_type(itemstack, placer, pointed_thing, true)
882 on_construct = function(pos)
883 signs_lib.construct_sign(pos, true)
885 on_destruct = function(pos)
886 signs_lib.destruct_sign(pos)
888 on_receive_fields = function(pos, formname, fields, sender)
889 local meta = minetest.get_meta(pos)
890 local owner = meta:get_string("owner")
891 local pname = sender:get_player_name() or ""
892 if pname ~= owner and pname ~= minetest.settings:get("name")
893 and not minetest.check_player_privs(pname, {sign_editor=true}) then
896 signs_lib.receive_fields(pos, formname, fields, sender, true)
898 on_punch = function(pos, node, puncher)
899 signs_lib.update_sign(pos)
901 can_dig = function(pos, player)
902 local meta = minetest.get_meta(pos)
903 local owner = meta:get_string("owner")
904 local pname = player:get_player_name()
905 return pname == owner or pname == minetest.settings:get("name")
906 or minetest.check_player_privs(pname, {sign_editor=true})
908 on_rotate = signs_lib.wallmounted_rotate
911 -- default metal sign, if defined
913 if minetest.registered_nodes["default:sign_wall_steel"] then
914 minetest.register_node(":"..default_sign_metal, {
915 description = S("Sign"),
916 inventory_image = default_sign_metal_image,
917 wield_image = default_sign_metal_image,
918 node_placement_prediction = "",
919 sunlight_propagates = true,
921 paramtype2 = "wallmounted",
922 drawtype = "nodebox",
923 node_box = signs_lib.regular_wall_sign_model.nodebox,
924 tiles = {"signs_wall_sign_metal.png"},
925 groups = sign_groups,
927 on_place = function(itemstack, placer, pointed_thing)
928 return signs_lib.determine_sign_type(itemstack, placer, pointed_thing)
930 on_construct = function(pos)
931 signs_lib.construct_sign(pos)
933 on_destruct = function(pos)
934 signs_lib.destruct_sign(pos)
936 on_receive_fields = function(pos, formname, fields, sender)
937 signs_lib.receive_fields(pos, formname, fields, sender)
939 on_punch = function(pos, node, puncher)
940 signs_lib.update_sign(pos)
942 on_rotate = signs_lib.wallmounted_rotate
946 -- metal, colored signs
947 if enable_colored_metal_signs then
948 -- array : color, translated color, default text color
949 local sign_colors = {
950 {"green", S("green"), "f"},
951 {"yellow", S("yellow"), "0"},
952 {"red", S("red"), "f"},
953 {"white_red", S("white_red"), "4"},
954 {"white_black", S("white_black"), "0"},
955 {"orange", S("orange"), "0"},
956 {"blue", S("blue"), "f"},
957 {"brown", S("brown"), "f"},
960 for i, color in ipairs(sign_colors) do
961 minetest.register_node(":signs:sign_wall_"..color[1], {
962 description = S("Sign (@1, metal)", color[2]),
963 inventory_image = "signs_"..color[1].."_inv.png",
964 wield_image = "signs_"..color[1].."_inv.png",
965 node_placement_prediction = "",
967 sunlight_propagates = true,
968 paramtype2 = "facedir",
969 drawtype = "nodebox",
970 node_box = signs_lib.metal_wall_sign_model.nodebox,
972 "signs_metal_tb.png",
973 "signs_metal_tb.png",
974 "signs_metal_sides.png",
975 "signs_metal_sides.png",
976 "signs_metal_back.png",
977 "signs_"..color[1].."_front.png"
979 default_color = color[3],
980 groups = sign_groups,
981 on_place = function(itemstack, placer, pointed_thing)
982 return signs_lib.determine_sign_type(itemstack, placer, pointed_thing)
984 on_construct = function(pos)
985 signs_lib.construct_sign(pos)
987 on_destruct = function(pos)
988 signs_lib.destruct_sign(pos)
990 on_receive_fields = function(pos, formname, fields, sender)
991 signs_lib.receive_fields(pos, formname, fields, sender)
993 on_punch = function(pos, node, puncher)
994 signs_lib.update_sign(pos)
996 on_rotate = signs_lib.facedir_rotate
1001 local signs_text_on_activate
1003 signs_text_on_activate = function(self)
1004 local pos = self.object:getpos()
1005 local meta = minetest.get_meta(pos)
1006 local text = meta:get_string("text")
1007 local new = (meta:get_int("__signslib_new_format") ~= 0)
1008 if text and minetest.registered_nodes[minetest.get_node(pos).name] then
1009 text = trim_input(text)
1010 set_obj_text(self.object, text, new, pos)
1014 minetest.register_entity(":signs:text", {
1015 collisionbox = { 0, 0, 0, 0, 0, 0 },
1016 visual = "upright_sprite",
1019 on_activate = signs_text_on_activate,
1022 -- And the good stuff here! :-)
1024 function signs_lib.register_fence_with_sign(fencename, fencewithsignname)
1025 local def = minetest.registered_nodes[fencename]
1026 local def_sign = minetest.registered_nodes[fencewithsignname]
1027 if not (def and def_sign) then
1028 minetest.log("warning", "[signs_lib] "..S("Attempt to register unknown node as fence"))
1031 def = signs_lib.table_copy(def)
1032 def_sign = signs_lib.table_copy(def_sign)
1033 fences_with_sign[fencename] = fencewithsignname
1035 def_sign.on_place = function(itemstack, placer, pointed_thing, ...)
1036 local node_above = minetest.get_node_or_nil(pointed_thing.above)
1037 local node_under = minetest.get_node_or_nil(pointed_thing.under)
1038 local def_above = node_above and minetest.registered_nodes[node_above.name]
1039 local def_under = node_under and minetest.registered_nodes[node_under.name]
1040 local fdir = minetest.dir_to_facedir(placer:get_look_dir())
1041 local playername = placer:get_player_name()
1043 if minetest.is_protected(pointed_thing.under, playername) then
1044 minetest.record_protection_violation(pointed_thing.under, playername)
1048 if minetest.is_protected(pointed_thing.above, playername) then
1049 minetest.record_protection_violation(pointed_thing.above, playername)
1053 if def_under and def_under.on_rightclick then
1054 return def_under.on_rightclick(pointed_thing.under, node_under, placer, itemstack, pointed_thing) or itemstack
1055 elseif def_under and def_under.buildable_to then
1056 minetest.add_node(pointed_thing.under, {name = fencename, param2 = fdir})
1057 if not signs_lib.expect_infinite_stacks then
1058 itemstack:take_item()
1060 placer:set_wielded_item(itemstack)
1061 elseif def_above and def_above.buildable_to then
1062 minetest.add_node(pointed_thing.above, {name = fencename, param2 = fdir})
1063 if not signs_lib.expect_infinite_stacks then
1064 itemstack:take_item()
1066 placer:set_wielded_item(itemstack)
1070 def_sign.on_construct = function(pos, ...)
1071 signs_lib.construct_sign(pos)
1073 def_sign.on_destruct = function(pos, ...)
1074 signs_lib.destruct_sign(pos)
1076 def_sign.on_receive_fields = function(pos, formname, fields, sender)
1077 signs_lib.receive_fields(pos, formname, fields, sender)
1079 def_sign.on_punch = function(pos, node, puncher, ...)
1080 signs_lib.update_sign(pos)
1082 local fencename = fencename
1083 def_sign.after_dig_node = function(pos, node, ...)
1084 node.name = fencename
1085 minetest.add_node(pos, node)
1087 def_sign.on_rotate = signs_lib.facedir_rotate_simple
1089 def_sign.drop = default_sign
1090 minetest.register_node(":"..fencename, def)
1091 minetest.register_node(":"..fencewithsignname, def_sign)
1092 table.insert(signs_lib.sign_node_list, fencewithsignname)
1093 minetest.log("verbose", S("Registered @1 and @2", fencename, fencewithsignname))
1098 minetest.register_alias("homedecor:fence_wood_with_sign", "signs:sign_post")
1099 minetest.register_alias("sign_wall_locked", "locked_sign:sign_wall_locked")
1101 signs_lib.register_fence_with_sign("default:fence_wood", "signs:sign_post")
1103 -- restore signs' text after /clearobjects and the like, the next time
1104 -- a block is reloaded by the server.
1106 minetest.register_lbm({
1107 nodenames = signs_lib.sign_node_list,
1108 name = "signs_lib:restore_sign_text",
1109 label = "Restore sign text",
1110 run_at_every_load = true,
1111 action = function(pos, node)
1112 signs_lib.update_sign(pos)
1118 minetest.register_craft({
1119 output = "locked_sign:sign_wall_locked",
1122 {"default:steel_ingot"},
1126 -- craft recipes for the metal signs
1127 if enable_colored_metal_signs then
1129 minetest.register_craft( {
1130 output = "signs:sign_wall_green",
1132 { "dye:dark_green", "dye:white", "dye:dark_green" },
1133 { "", default_sign_metal, "" }
1137 minetest.register_craft( {
1138 output = "signs:sign_wall_green 2",
1140 { "dye:dark_green", "dye:white", "dye:dark_green" },
1141 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1145 minetest.register_craft( {
1146 output = "signs:sign_wall_yellow",
1148 { "dye:yellow", "dye:black", "dye:yellow" },
1149 { "", default_sign_metal, "" }
1153 minetest.register_craft( {
1154 output = "signs:sign_wall_yellow 2",
1156 { "dye:yellow", "dye:black", "dye:yellow" },
1157 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1161 minetest.register_craft( {
1162 output = "signs:sign_wall_red",
1164 { "dye:red", "dye:white", "dye:red" },
1165 { "", default_sign_metal, "" }
1169 minetest.register_craft( {
1170 output = "signs:sign_wall_red 2",
1172 { "dye:red", "dye:white", "dye:red" },
1173 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1177 minetest.register_craft( {
1178 output = "signs:sign_wall_white_red",
1180 { "dye:white", "dye:red", "dye:white" },
1181 { "", default_sign_metal, "" }
1185 minetest.register_craft( {
1186 output = "signs:sign_wall_white_red 2",
1188 { "dye:white", "dye:red", "dye:white" },
1189 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1193 minetest.register_craft( {
1194 output = "signs:sign_wall_white_black",
1196 { "dye:white", "dye:black", "dye:white" },
1197 { "", default_sign_metal, "" }
1201 minetest.register_craft( {
1202 output = "signs:sign_wall_white_black 2",
1204 { "dye:white", "dye:black", "dye:white" },
1205 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1209 minetest.register_craft( {
1210 output = "signs:sign_wall_orange",
1212 { "dye:orange", "dye:black", "dye:orange" },
1213 { "", default_sign_metal, "" }
1217 minetest.register_craft( {
1218 output = "signs:sign_wall_orange 2",
1220 { "dye:orange", "dye:black", "dye:orange" },
1221 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1225 minetest.register_craft( {
1226 output = "signs:sign_wall_blue",
1228 { "dye:blue", "dye:white", "dye:blue" },
1229 { "", default_sign_metal, "" }
1233 minetest.register_craft( {
1234 output = "signs:sign_wall_blue 2",
1236 { "dye:blue", "dye:white", "dye:blue" },
1237 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1241 minetest.register_craft( {
1242 output = "signs:sign_wall_brown",
1244 { "dye:brown", "dye:white", "dye:brown" },
1245 { "", default_sign_metal, "" }
1249 minetest.register_craft( {
1250 output = "signs:sign_wall_brown 2",
1252 { "dye:brown", "dye:white", "dye:brown" },
1253 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1258 if minetest.settings:get("log_mods") then
1259 minetest.log("action", S("[MOD] signs loaded"))