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 signs_lib.update_sign(pos,nil,nil,node)
47 signs_lib.facedir_rotate = function(pos, node, user, mode)
48 if mode ~= screwdriver.ROTATE_FACE then return false end
49 local newparam2 = (node.param2 %8) + 1
50 if newparam2 == 5 then
52 elseif newparam2 > 6 then
55 minetest.swap_node(pos, { name = node.name, param2 = newparam2 })
56 signs_lib.update_sign(pos,nil,nil,node)
60 signs_lib.facedir_rotate_simple = function(pos, node, user, mode)
61 if mode ~= screwdriver.ROTATE_FACE then return false end
62 local newparam2 = (node.param2 %8) + 1
63 if newparam2 > 3 then newparam2 = 0 end
64 minetest.swap_node(pos, { name = node.name, param2 = newparam2 })
65 signs_lib.update_sign(pos,nil,nil,node)
70 signs_lib.modpath = minetest.get_modpath("signs_lib")
72 local DEFAULT_TEXT_SCALE = {x=0.8, y=0.5}
74 signs_lib.regular_wall_sign_model = {
77 wall_side = { -0.5, -0.25, -0.4375, -0.4375, 0.375, 0.4375 },
78 wall_bottom = { -0.4375, -0.5, -0.25, 0.4375, -0.4375, 0.375 },
79 wall_top = { -0.4375, 0.4375, -0.375, 0.4375, 0.5, 0.25 }
84 {delta = { x = 0.41, y = 0.07, z = 0 }, yaw = math.pi / -2},
85 {delta = { x = -0.41, y = 0.07, z = 0 }, yaw = math.pi / 2},
86 {delta = { x = 0, y = 0.07, z = 0.41 }, yaw = 0},
87 {delta = { x = 0, y = 0.07, z = -0.41 }, yaw = math.pi},
91 signs_lib.metal_wall_sign_model = {
94 fixed = {-0.4375, -0.25, 0.4375, 0.4375, 0.375, 0.5}
97 {delta = { x = 0, y = 0.07, z = 0.41 }, yaw = 0},
98 {delta = { x = 0.41, y = 0.07, z = 0 }, yaw = math.pi / -2},
99 {delta = { x = 0, y = 0.07, z = -0.41 }, yaw = math.pi},
100 {delta = { x = -0.41, y = 0.07, z = 0 }, yaw = math.pi / 2},
104 signs_lib.yard_sign_model = {
108 {-0.4375, -0.25, -0.0625, 0.4375, 0.375, 0},
109 {-0.0625, -0.5, -0.0625, 0.0625, -0.1875, 0},
113 {delta = { x = 0, y = 0.07, z = -0.08 }, yaw = 0},
114 {delta = { x = -0.08, y = 0.07, z = 0 }, yaw = math.pi / -2},
115 {delta = { x = 0, y = 0.07, z = 0.08 }, yaw = math.pi},
116 {delta = { x = 0.08, y = 0.07, z = 0 }, yaw = math.pi / 2},
120 signs_lib.hanging_sign_model = {
124 {-0.4375, -0.3125, -0.0625, 0.4375, 0.3125, 0},
125 {-0.4375, 0.25, -0.03125, 0.4375, 0.5, -0.03125},
129 {delta = { x = 0, y = -0.02, z = -0.08 }, yaw = 0},
130 {delta = { x = -0.08, y = -0.02, z = 0 }, yaw = math.pi / -2},
131 {delta = { x = 0, y = -0.02, z = 0.08 }, yaw = math.pi},
132 {delta = { x = 0.08, y = -0.02, z = 0 }, yaw = math.pi / 2},
136 signs_lib.sign_post_model = {
140 {-0.4375, -0.25, -0.1875, 0.4375, 0.375, -0.125},
141 {-0.125, -0.5, -0.125, 0.125, 0.5, 0.125},
145 {delta = { x = 0, y = 0.07, z = -0.2 }, yaw = 0},
146 {delta = { x = -0.2, y = 0.07, z = 0 }, yaw = math.pi / -2},
147 {delta = { x = 0, y = 0.07, z = 0.2 }, yaw = math.pi},
148 {delta = { x = 0.2, y = 0.07, z = 0 }, yaw = math.pi / 2},
152 -- the list of standard sign nodes
154 signs_lib.sign_node_list = {
155 "default:sign_wall_wood",
156 "default:sign_wall_steel",
158 "signs:sign_hanging",
159 "signs:sign_wall_green",
160 "signs:sign_wall_yellow",
161 "signs:sign_wall_red",
162 "signs:sign_wall_white_red",
163 "signs:sign_wall_white_black",
164 "signs:sign_wall_orange",
165 "signs:sign_wall_blue",
166 "signs:sign_wall_brown",
167 "locked_sign:sign_wall_locked"
170 local default_sign, default_sign_image
172 -- Default sign was renamed in 0.4.14. Support both & old versions.
173 if minetest.registered_nodes["default:sign_wall_wood"] then
174 default_sign = "default:sign_wall_wood"
175 default_sign_image = "default_sign_wood.png"
177 default_sign = "default:sign_wall"
178 default_sign_image = "default_sign_wall.png"
181 default_sign_metal = "default:sign_wall_steel"
182 default_sign_metal_image = "default_sign_steel.png"
186 function signs_lib.table_copy(t)
188 for k, v in pairs(t) do
189 if type(v) == "table" then
190 nt[k] = signs_lib.table_copy(v)
200 if not minetest.settings:get_bool("creative_mode") then
201 signs_lib.expect_infinite_stacks = false
203 signs_lib.expect_infinite_stacks = true
208 -- Path to the textures.
209 local TP = signs_lib.path .. "/textures"
210 -- Font file formatter
211 local CHAR_FILE = "%s_%02x.png"
213 local CHAR_PATH = TP .. "/" .. CHAR_FILE
216 local font_name = "hdf"
218 -- Lots of overkill here. KISS advocates, go away, shoo! ;) -- kaeza
220 local PNG_HDR = string.char(0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A)
222 -- check if a file does exist
223 -- to avoid reopening file after checking again
224 -- pass TRUE as second argument
225 function file_exists(name, return_handle, mode)
227 local f = io.open(name, mode)
229 if (return_handle) then
239 -- Read the image size from a PNG file.
240 -- Returns image_w, image_h.
241 -- Only the LSB is read from each field!
242 local function read_image_size(filename)
243 local f = file_exists(filename, true, "rb")
244 -- file might not exist (don't crash the game)
249 local hdr = f:read(string.len(PNG_HDR))
250 if hdr ~= PNG_HDR then
259 return ws:byte(), hs:byte()
262 -- Set by build_char_db()
265 local COLORBGW, COLORBGH
267 -- Size of the canvas, in characters.
268 -- Please note that CHARS_PER_LINE is multiplied by the average character
269 -- width to get the total width of the canvas, so for proportional fonts,
270 -- either more or fewer characters may fit on a line.
271 local CHARS_PER_LINE = 30
272 local NUMBER_OF_LINES = 6
274 -- 6 rows, max 80 chars per, plus a bit of fudge to
275 -- avoid excess trimming (e.g. due to color codes)
277 local MAX_INPUT_CHARS = 600
279 -- This holds the individual character widths.
280 -- Indexed by the actual character (e.g. charwidth["A"])
283 -- helper functions to trim sign text input/output
285 local function trim_input(text)
286 return text:sub(1, math.min(MAX_INPUT_CHARS, text:len()))
289 local function build_char_db()
293 -- To calculate average char width.
294 local total_width = 0
298 local w, h = read_image_size(CHAR_PATH:format(font_name, c))
300 local ch = string.char(c)
302 total_width = total_width + w
303 char_count = char_count + 1
307 COLORBGW, COLORBGH = read_image_size(TP.."/slc_n.png")
308 assert(COLORBGW and COLORBGH, "error reading bg dimensions")
309 LINE_HEIGHT = COLORBGH
311 -- XXX: Is there a better way to calc this?
312 SIGN_WIDTH = math.floor((total_width / char_count) * CHARS_PER_LINE)
316 local sign_groups = {choppy=2, dig_immediate=2}
318 local fences_with_sign = { }
320 -- some local helper functions
322 local function split_lines_and_words_old(text)
325 if not text then return end
326 for word in text:gmatch("%S+") do
328 table.insert(lines, line)
329 if #lines >= NUMBER_OF_LINES then break end
331 elseif word == "\\|" then
332 table.insert(line, "|")
334 table.insert(line, word)
337 table.insert(lines, line)
341 local function split_lines_and_words(text)
342 if not text then return end
343 text = string.gsub(text, "@KEYWORD", current_keyword)
345 for _, line in ipairs(text:split("\n")) do
346 table.insert(lines, line:split(" "))
351 local math_max = math.max
353 local function fill_line(x, y, w, c)
356 for xx = 0, math.max(0, w), COLORBGW do
357 table.insert(tex, (":%d,%d=slc_%s.png"):format(x + xx, y, c))
359 return table.concat(tex)
362 -- make char texture file name
363 -- if texture file does not exist use fallback texture instead
364 local function char_tex(font_name, ch)
366 local exists, tex = file_exists(CHAR_PATH:format(font_name, c))
367 if exists and c ~= 14 then
368 tex = CHAR_FILE:format(font_name, c)
370 tex = CHAR_FILE:format(font_name, 0x0)
375 local function make_line_texture(line, lineno, pos)
381 local n = minetest.registered_nodes[minetest.get_node(pos).name]
382 local default_color = n.default_color or 0
384 local cur_color = tonumber(default_color, 16)
386 -- We check which chars are available here.
387 for word_i, word in ipairs(line) do
393 local c = word:sub(i, i)
395 local cc = tonumber(word:sub(i+1, i+1), 16)
401 local w = charwidth[c]
403 width = width + w + 1
404 if width >= (SIGN_WIDTH - charwidth[" "]) then
407 maxw = math_max(width, maxw)
409 if #chars < MAX_INPUT_CHARS then
410 table.insert(chars, {
412 tex = char_tex(font_name, c),
413 col = ("%X"):format(cur_color),
416 ch_offs = ch_offs + w
421 width = width + charwidth[" "] + 1
422 maxw = math_max(width, maxw)
423 table.insert(words, { chars=chars, w=ch_offs })
426 -- Okay, we actually build the "line texture" here.
430 local start_xpos = math.floor((SIGN_WIDTH - maxw) / 2)
432 local xpos = start_xpos
433 local ypos = (LINE_HEIGHT * lineno)
437 for word_i, word in ipairs(words) do
438 local xoffs = (xpos - start_xpos)
439 if (xoffs > 0) and ((xoffs + word.w) > maxw) then
440 table.insert(texture, fill_line(xpos, ypos, maxw, "n"))
442 ypos = ypos + LINE_HEIGHT
444 if lineno >= NUMBER_OF_LINES then break end
445 table.insert(texture, fill_line(xpos, ypos, maxw, cur_color))
447 for ch_i, ch in ipairs(word.chars) do
448 if ch.col ~= cur_color then
450 table.insert(texture, fill_line(xpos + ch.off, ypos, maxw, cur_color))
452 table.insert(texture, (":%d,%d=%s"):format(xpos + ch.off, ypos, ch.tex))
456 (":%d,%d="):format(xpos + word.w, ypos) .. char_tex(font_name, " ")
458 xpos = xpos + word.w + charwidth[" "]
459 if xpos >= (SIGN_WIDTH + charwidth[" "]) then break end
462 table.insert(texture, fill_line(xpos, ypos, maxw, "n"))
463 table.insert(texture, fill_line(start_xpos, ypos + LINE_HEIGHT, maxw, "n"))
465 return table.concat(texture), lineno
468 local function make_sign_texture(lines, pos)
469 local texture = { ("[combine:%dx%d"):format(SIGN_WIDTH, LINE_HEIGHT * NUMBER_OF_LINES) }
472 if lineno >= NUMBER_OF_LINES then break end
473 local linetex, ln = make_line_texture(lines[i], lineno, pos)
474 table.insert(texture, linetex)
477 table.insert(texture, "^[makealpha:0,0,0")
478 return table.concat(texture, "")
481 local function set_obj_text(obj, text, new, pos)
482 local split = new and split_lines_and_words or split_lines_and_words_old
483 local text_ansi = Utf8ToAnsi(text)
484 local n = minetest.registered_nodes[minetest.get_node(pos).name]
485 local text_scale = (n and n.text_scale) or DEFAULT_TEXT_SCALE
487 textures={make_sign_texture(split(text_ansi), pos)},
488 visual_size = text_scale,
492 signs_lib.construct_sign = function(pos, locked)
493 local meta = minetest.get_meta(pos)
497 "textarea[0,-0.3;6.5,3;text;;${text}]"..
498 "button_exit[2,3.4;2,1;ok;"..S("Write").."]"..
499 "background[-0.5,-0.5;7,5;bg_signs_lib.jpg]")
500 meta:set_string("infotext", "")
503 signs_lib.destruct_sign = function(pos)
504 local objects = minetest.get_objects_inside_radius(pos, 0.5)
505 for _, v in ipairs(objects) do
506 local e = v:get_luaentity()
507 if e and e.name == "signs:text" then
513 local function make_infotext(text)
514 text = trim_input(text)
515 local lines = split_lines_and_words(text) or {}
517 for _, line in ipairs(lines) do
518 table.insert(lines2, (table.concat(line, " "):gsub("#[0-9a-fA-F]", ""):gsub("##", "#")))
520 return table.concat(lines2, "\n")
523 signs_lib.update_sign = function(pos, fields, owner, node)
525 -- First, check if the interact keyword from CWz's mod is being set,
526 -- or has been changed since the last restart...
528 local meta = minetest.get_meta(pos)
529 local stored_text = meta:get_string("text") or ""
530 current_keyword = rawget(_G, "mki_interact_keyword") or current_keyword
532 if fields then -- ...we're editing the sign.
533 if fields.text and string.find(dump(fields.text), "@KEYWORD") then
534 meta:set_string("keyword", current_keyword)
536 meta:set_string("keyword", nil)
538 elseif string.find(dump(stored_text), "@KEYWORD") then -- we need to check if the password is being set/changed
540 local stored_keyword = meta:get_string("keyword")
541 if stored_keyword and stored_keyword ~= "" and stored_keyword ~= current_keyword then
542 signs_lib.destruct_sign(pos)
543 meta:set_string("keyword", current_keyword)
545 if owner then ownstr = S("Locked sign, owned by @1\n", owner) end
546 meta:set_string("infotext", ownstr..string.gsub(make_infotext(stored_text), "@KEYWORD", current_keyword).." ")
554 fields.text = trim_input(fields.text)
557 if owner then ownstr = S("Locked sign, owned by @1\n", owner) end
559 meta:set_string("infotext", ownstr..string.gsub(make_infotext(fields.text), "@KEYWORD", current_keyword).." ")
560 meta:set_string("text", fields.text)
562 meta:set_int("__signslib_new_format", 1)
565 new = (meta:get_int("__signslib_new_format") ~= 0)
567 signs_lib.destruct_sign(pos)
568 local text = meta:get_string("text")
569 if text == nil or text == "" then return end
571 if node then print ("---", node.name, node.param2) end
574 local signnode = node or minetest.get_node(pos)
575 local signname = signnode.name
576 local textpos = minetest.registered_nodes[signname].textpos
578 sign_info = textpos[minetest.get_node(pos).param2 + 1]
579 elseif signnode.name == "signs:sign_yard" then
580 sign_info = signs_lib.yard_sign_model.textpos[minetest.get_node(pos).param2 + 1]
581 elseif signnode.name == "signs:sign_hanging" then
582 sign_info = signs_lib.hanging_sign_model.textpos[minetest.get_node(pos).param2 + 1]
583 elseif string.find(signnode.name, "sign_wall") then
584 if signnode.name == default_sign
585 or signnode.name == default_sign_metal
586 or signnode.name == "locked_sign:sign_wall_locked" then
587 sign_info = signs_lib.regular_wall_sign_model.textpos[minetest.get_node(pos).param2 + 1]
589 sign_info = signs_lib.metal_wall_sign_model.textpos[minetest.get_node(pos).param2 + 1]
591 else -- ...it must be a sign on a fence post.
592 sign_info = signs_lib.sign_post_model.textpos[minetest.get_node(pos).param2 + 1]
594 if sign_info == nil then
597 local text = minetest.add_entity({x = pos.x + sign_info.delta.x,
598 y = pos.y + sign_info.delta.y,
599 z = pos.z + sign_info.delta.z}, "signs:text")
600 text:setyaw(sign_info.yaw)
603 -- What kind of sign do we need to place, anyway?
605 function signs_lib.determine_sign_type(itemstack, placer, pointed_thing, locked)
607 name = minetest.get_node(pointed_thing.under).name
608 if fences_with_sign[name] then
609 if minetest.is_protected(pointed_thing.under, placer:get_player_name()) then
610 minetest.record_protection_violation(pointed_thing.under,
611 placer:get_player_name())
615 name = minetest.get_node(pointed_thing.above).name
616 local def = minetest.registered_nodes[name]
617 if not def.buildable_to then
620 if minetest.is_protected(pointed_thing.above, placer:get_player_name()) then
621 minetest.record_protection_violation(pointed_thing.above,
622 placer:get_player_name())
627 local node=minetest.get_node(pointed_thing.under)
629 if minetest.registered_nodes[node.name] and
630 minetest.registered_nodes[node.name].on_rightclick and
631 not placer:get_player_control().sneak then
632 return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack, pointed_thing)
634 local above = pointed_thing.above
635 local under = pointed_thing.under
636 local dir = {x = under.x - above.x,
637 y = under.y - above.y,
638 z = under.z - above.z}
640 local wdir = minetest.dir_to_wallmounted(dir)
642 local placer_pos = placer:getpos()
645 x = above.x - placer_pos.x,
646 y = above.y - placer_pos.y,
647 z = above.z - placer_pos.z
651 local fdir = minetest.dir_to_facedir(dir)
652 local pt_name = minetest.get_node(under).name
653 local signname = itemstack:get_name()
655 if fences_with_sign[pt_name] and signname == default_sign then
656 minetest.add_node(under, {name = fences_with_sign[pt_name], param2 = fdir})
657 elseif wdir == 0 and signname == default_sign then
658 minetest.add_node(above, {name = "signs:sign_hanging", param2 = fdir})
659 elseif wdir == 1 and signname == default_sign then
660 minetest.add_node(above, {name = "signs:sign_yard", param2 = fdir})
661 elseif signname == default_sign_metal then
662 minetest.add_node(above, {name = signname, param2 = wdir })
663 elseif signname ~= default_sign
664 and signname ~= default_sign_metal
665 and signname ~= "locked_sign:sign_wall_locked" then -- it's a signs_lib colored metal wall sign.
666 minetest.add_node(above, {name = signname, param2 = fdir})
667 else -- it must be a default or locked wooden wall sign
668 minetest.add_node(above, {name = signname, param2 = wdir }) -- note it's wallmounted here!
670 local meta = minetest.get_meta(above)
671 local owner = placer:get_player_name()
672 meta:set_string("owner", owner)
676 if not signs_lib.expect_infinite_stacks then
677 itemstack:take_item()
683 function signs_lib.receive_fields(pos, formname, fields, sender, lock)
684 if minetest.is_protected(pos, sender:get_player_name()) then
685 minetest.record_protection_violation(pos,
686 sender:get_player_name())
689 local lockstr = lock and S("locked ") or ""
690 if fields and fields.text and fields.ok then
691 minetest.log("action", S("@1 wrote \"@2\" to @3sign at @4",
692 (sender:get_player_name() or ""),
693 fields.text:gsub('\\', '\\\\'):gsub("\n", "\\n"),
695 minetest.pos_to_string(pos)
698 signs_lib.update_sign(pos, fields, sender:get_player_name())
700 signs_lib.update_sign(pos, fields)
705 minetest.register_node(":"..default_sign, {
706 description = S("Sign"),
707 inventory_image = default_sign_image,
708 wield_image = default_sign_image,
709 node_placement_prediction = "",
710 sunlight_propagates = true,
712 paramtype2 = "wallmounted",
713 drawtype = "nodebox",
714 node_box = signs_lib.regular_wall_sign_model.nodebox,
715 tiles = {"signs_wall_sign.png"},
716 groups = sign_groups,
718 on_place = function(itemstack, placer, pointed_thing)
719 return signs_lib.determine_sign_type(itemstack, placer, pointed_thing)
721 on_construct = function(pos)
722 signs_lib.construct_sign(pos)
724 on_destruct = function(pos)
725 signs_lib.destruct_sign(pos)
727 on_receive_fields = function(pos, formname, fields, sender)
728 signs_lib.receive_fields(pos, formname, fields, sender)
730 on_punch = function(pos, node, puncher)
731 signs_lib.update_sign(pos,nil,nil,node)
733 on_rotate = signs_lib.wallmounted_rotate
736 minetest.register_node(":signs:sign_yard", {
738 sunlight_propagates = true,
739 paramtype2 = "facedir",
740 drawtype = "nodebox",
741 node_box = signs_lib.yard_sign_model.nodebox,
744 fixed = {-0.4375, -0.5, -0.0625, 0.4375, 0.375, 0}
746 tiles = {"signs_top.png", "signs_bottom.png", "signs_side.png", "signs_side.png", "signs_back.png", "signs_front.png"},
747 groups = {choppy=2, dig_immediate=2},
750 on_construct = function(pos)
751 signs_lib.construct_sign(pos)
753 on_destruct = function(pos)
754 signs_lib.destruct_sign(pos)
756 on_receive_fields = function(pos, formname, fields, sender)
757 signs_lib.receive_fields(pos, formname, fields, sender)
759 on_punch = function(pos, node, puncher)
760 signs_lib.update_sign(pos,nil,nil,node)
762 on_rotate = signs_lib.facedir_rotate_simple
766 minetest.register_node(":signs:sign_hanging", {
768 sunlight_propagates = true,
769 paramtype2 = "facedir",
770 drawtype = "nodebox",
771 node_box = signs_lib.hanging_sign_model.nodebox,
774 fixed = {-0.45, -0.275, -0.049, 0.45, 0.5, 0.049}
777 "signs_hanging_top.png",
778 "signs_hanging_bottom.png",
779 "signs_hanging_side.png",
780 "signs_hanging_side.png",
781 "signs_hanging_back.png",
782 "signs_hanging_front.png"
784 groups = {choppy=2, dig_immediate=2},
787 on_construct = function(pos)
788 signs_lib.construct_sign(pos)
790 on_destruct = function(pos)
791 signs_lib.destruct_sign(pos)
793 on_receive_fields = function(pos, formname, fields, sender)
794 signs_lib.receive_fields(pos, formname, fields, sender)
796 on_punch = function(pos, node, puncher)
797 signs_lib.update_sign(pos,nil,nil,node)
799 on_rotate = signs_lib.facedir_rotate_simple
802 minetest.register_node(":signs:sign_post", {
804 sunlight_propagates = true,
805 paramtype2 = "facedir",
806 drawtype = "nodebox",
807 node_box = signs_lib.sign_post_model.nodebox,
809 "signs_post_top.png",
810 "signs_post_bottom.png",
811 "signs_post_side.png",
812 "signs_post_side.png",
813 "signs_post_back.png",
814 "signs_post_front.png",
816 groups = {choppy=2, dig_immediate=2},
820 { items = { default_sign }},
821 { items = { "default:fence_wood" }},
824 on_rotate = signs_lib.facedir_rotate_simple
829 minetest.register_privilege("sign_editor", S("Can edit all locked signs"))
831 minetest.register_node(":locked_sign:sign_wall_locked", {
832 description = S("Locked Sign"),
833 inventory_image = "signs_locked_inv.png",
834 wield_image = "signs_locked_inv.png",
835 node_placement_prediction = "",
836 sunlight_propagates = true,
838 paramtype2 = "wallmounted",
839 drawtype = "nodebox",
840 node_box = signs_lib.regular_wall_sign_model.nodebox,
841 tiles = { "signs_wall_sign_locked.png" },
842 groups = sign_groups,
843 on_place = function(itemstack, placer, pointed_thing)
844 return signs_lib.determine_sign_type(itemstack, placer, pointed_thing, true)
846 on_construct = function(pos)
847 signs_lib.construct_sign(pos, true)
849 on_destruct = function(pos)
850 signs_lib.destruct_sign(pos)
852 on_receive_fields = function(pos, formname, fields, sender)
853 local meta = minetest.get_meta(pos)
854 local owner = meta:get_string("owner")
855 local pname = sender:get_player_name() or ""
856 if pname ~= owner and pname ~= minetest.settings:get("name")
857 and not minetest.check_player_privs(pname, {sign_editor=true}) then
860 signs_lib.receive_fields(pos, formname, fields, sender, true)
862 on_punch = function(pos, node, puncher)
863 signs_lib.update_sign(pos,nil,nil,node)
865 can_dig = function(pos, player)
866 local meta = minetest.get_meta(pos)
867 local owner = meta:get_string("owner")
868 local pname = player:get_player_name()
869 return pname == owner or pname == minetest.settings:get("name")
870 or minetest.check_player_privs(pname, {sign_editor=true})
872 on_rotate = signs_lib.wallmounted_rotate
875 -- default metal sign, if defined
877 if minetest.registered_nodes["default:sign_wall_steel"] then
878 minetest.register_node(":"..default_sign_metal, {
879 description = S("Sign"),
880 inventory_image = default_sign_metal_image,
881 wield_image = default_sign_metal_image,
882 node_placement_prediction = "",
883 sunlight_propagates = true,
885 paramtype2 = "wallmounted",
886 drawtype = "nodebox",
887 node_box = signs_lib.regular_wall_sign_model.nodebox,
888 tiles = {"signs_wall_sign_metal.png"},
889 groups = sign_groups,
891 on_place = function(itemstack, placer, pointed_thing)
892 return signs_lib.determine_sign_type(itemstack, placer, pointed_thing)
894 on_construct = function(pos)
895 signs_lib.construct_sign(pos)
897 on_destruct = function(pos)
898 signs_lib.destruct_sign(pos)
900 on_receive_fields = function(pos, formname, fields, sender)
901 signs_lib.receive_fields(pos, formname, fields, sender)
903 on_punch = function(pos, node, puncher)
904 signs_lib.update_sign(pos,nil,nil,node)
906 on_rotate = signs_lib.wallmounted_rotate
910 -- metal, colored signs
911 if enable_colored_metal_signs then
912 -- array : color, translated color, default text color
913 local sign_colors = {
914 {"green", S("green"), "f"},
915 {"yellow", S("yellow"), "0"},
916 {"red", S("red"), "f"},
917 {"white_red", S("white_red"), "4"},
918 {"white_black", S("white_black"), "0"},
919 {"orange", S("orange"), "0"},
920 {"blue", S("blue"), "f"},
921 {"brown", S("brown"), "f"},
924 for i, color in ipairs(sign_colors) do
925 minetest.register_node(":signs:sign_wall_"..color[1], {
926 description = S("Sign (@1, metal)", color[2]),
927 inventory_image = "signs_"..color[1].."_inv.png",
928 wield_image = "signs_"..color[1].."_inv.png",
929 node_placement_prediction = "",
931 sunlight_propagates = true,
932 paramtype2 = "facedir",
933 drawtype = "nodebox",
934 node_box = signs_lib.metal_wall_sign_model.nodebox,
936 "signs_metal_tb.png",
937 "signs_metal_tb.png",
938 "signs_metal_sides.png",
939 "signs_metal_sides.png",
940 "signs_metal_back.png",
941 "signs_"..color[1].."_front.png"
943 default_color = color[3],
944 groups = sign_groups,
945 on_place = function(itemstack, placer, pointed_thing)
946 return signs_lib.determine_sign_type(itemstack, placer, pointed_thing)
948 on_construct = function(pos)
949 signs_lib.construct_sign(pos)
951 on_destruct = function(pos)
952 signs_lib.destruct_sign(pos)
954 on_receive_fields = function(pos, formname, fields, sender)
955 signs_lib.receive_fields(pos, formname, fields, sender)
957 on_punch = function(pos, node, puncher)
958 signs_lib.update_sign(pos,nil,nil,node)
960 on_rotate = signs_lib.facedir_rotate
965 local signs_text_on_activate
967 signs_text_on_activate = function(self)
968 local pos = self.object:getpos()
969 local meta = minetest.get_meta(pos)
970 local text = meta:get_string("text")
971 local new = (meta:get_int("__signslib_new_format") ~= 0)
972 if text and minetest.registered_nodes[minetest.get_node(pos).name] then
973 text = trim_input(text)
974 set_obj_text(self.object, text, new, pos)
978 minetest.register_entity(":signs:text", {
979 collisionbox = { 0, 0, 0, 0, 0, 0 },
980 visual = "upright_sprite",
983 on_activate = signs_text_on_activate,
986 -- And the good stuff here! :-)
988 function signs_lib.register_fence_with_sign(fencename, fencewithsignname)
989 local def = minetest.registered_nodes[fencename]
990 local def_sign = minetest.registered_nodes[fencewithsignname]
991 if not (def and def_sign) then
992 minetest.log("warning", "[signs_lib] "..S("Attempt to register unknown node as fence"))
995 def = signs_lib.table_copy(def)
996 def_sign = signs_lib.table_copy(def_sign)
997 fences_with_sign[fencename] = fencewithsignname
999 def_sign.on_place = function(itemstack, placer, pointed_thing, ...)
1000 local node_above = minetest.get_node_or_nil(pointed_thing.above)
1001 local node_under = minetest.get_node_or_nil(pointed_thing.under)
1002 local def_above = node_above and minetest.registered_nodes[node_above.name]
1003 local def_under = node_under and minetest.registered_nodes[node_under.name]
1004 local fdir = minetest.dir_to_facedir(placer:get_look_dir())
1005 local playername = placer:get_player_name()
1007 if minetest.is_protected(pointed_thing.under, playername) then
1008 minetest.record_protection_violation(pointed_thing.under, playername)
1012 if minetest.is_protected(pointed_thing.above, playername) then
1013 minetest.record_protection_violation(pointed_thing.above, playername)
1017 if def_under and def_under.on_rightclick then
1018 return def_under.on_rightclick(pointed_thing.under, node_under, placer, itemstack, pointed_thing) or itemstack
1019 elseif def_under and def_under.buildable_to then
1020 minetest.add_node(pointed_thing.under, {name = fencename, param2 = fdir})
1021 if not signs_lib.expect_infinite_stacks then
1022 itemstack:take_item()
1024 placer:set_wielded_item(itemstack)
1025 elseif def_above and def_above.buildable_to then
1026 minetest.add_node(pointed_thing.above, {name = fencename, param2 = fdir})
1027 if not signs_lib.expect_infinite_stacks then
1028 itemstack:take_item()
1030 placer:set_wielded_item(itemstack)
1034 def_sign.on_construct = function(pos, ...)
1035 signs_lib.construct_sign(pos)
1037 def_sign.on_destruct = function(pos, ...)
1038 signs_lib.destruct_sign(pos)
1040 def_sign.on_receive_fields = function(pos, formname, fields, sender)
1041 signs_lib.receive_fields(pos, formname, fields, sender)
1043 def_sign.on_punch = function(pos, node, puncher, ...)
1044 signs_lib.update_sign(pos,nil,nil,node)
1046 local fencename = fencename
1047 def_sign.after_dig_node = function(pos, node, ...)
1048 node.name = fencename
1049 minetest.add_node(pos, node)
1051 def_sign.on_rotate = signs_lib.facedir_rotate_simple
1053 def_sign.drop = default_sign
1054 minetest.register_node(":"..fencename, def)
1055 minetest.register_node(":"..fencewithsignname, def_sign)
1056 table.insert(signs_lib.sign_node_list, fencewithsignname)
1057 minetest.log("verbose", S("Registered @1 and @2", fencename, fencewithsignname))
1062 minetest.register_alias("homedecor:fence_wood_with_sign", "signs:sign_post")
1063 minetest.register_alias("sign_wall_locked", "locked_sign:sign_wall_locked")
1065 signs_lib.register_fence_with_sign("default:fence_wood", "signs:sign_post")
1067 -- restore signs' text after /clearobjects and the like, the next time
1068 -- a block is reloaded by the server.
1070 minetest.register_lbm({
1071 nodenames = signs_lib.sign_node_list,
1072 name = "signs_lib:restore_sign_text",
1073 label = "Restore sign text",
1074 run_at_every_load = true,
1075 action = function(pos, node)
1076 signs_lib.update_sign(pos,nil,nil,node)
1082 minetest.register_craft({
1083 output = "locked_sign:sign_wall_locked",
1086 {"default:steel_ingot"},
1090 -- craft recipes for the metal signs
1091 if enable_colored_metal_signs then
1093 minetest.register_craft( {
1094 output = "signs:sign_wall_green",
1096 { "dye:dark_green", "dye:white", "dye:dark_green" },
1097 { "", default_sign_metal, "" }
1101 minetest.register_craft( {
1102 output = "signs:sign_wall_green 2",
1104 { "dye:dark_green", "dye:white", "dye:dark_green" },
1105 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1109 minetest.register_craft( {
1110 output = "signs:sign_wall_yellow",
1112 { "dye:yellow", "dye:black", "dye:yellow" },
1113 { "", default_sign_metal, "" }
1117 minetest.register_craft( {
1118 output = "signs:sign_wall_yellow 2",
1120 { "dye:yellow", "dye:black", "dye:yellow" },
1121 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1125 minetest.register_craft( {
1126 output = "signs:sign_wall_red",
1128 { "dye:red", "dye:white", "dye:red" },
1129 { "", default_sign_metal, "" }
1133 minetest.register_craft( {
1134 output = "signs:sign_wall_red 2",
1136 { "dye:red", "dye:white", "dye:red" },
1137 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1141 minetest.register_craft( {
1142 output = "signs:sign_wall_white_red",
1144 { "dye:white", "dye:red", "dye:white" },
1145 { "", default_sign_metal, "" }
1149 minetest.register_craft( {
1150 output = "signs:sign_wall_white_red 2",
1152 { "dye:white", "dye:red", "dye:white" },
1153 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1157 minetest.register_craft( {
1158 output = "signs:sign_wall_white_black",
1160 { "dye:white", "dye:black", "dye:white" },
1161 { "", default_sign_metal, "" }
1165 minetest.register_craft( {
1166 output = "signs:sign_wall_white_black 2",
1168 { "dye:white", "dye:black", "dye:white" },
1169 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1173 minetest.register_craft( {
1174 output = "signs:sign_wall_orange",
1176 { "dye:orange", "dye:black", "dye:orange" },
1177 { "", default_sign_metal, "" }
1181 minetest.register_craft( {
1182 output = "signs:sign_wall_orange 2",
1184 { "dye:orange", "dye:black", "dye:orange" },
1185 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1189 minetest.register_craft( {
1190 output = "signs:sign_wall_blue",
1192 { "dye:blue", "dye:white", "dye:blue" },
1193 { "", default_sign_metal, "" }
1197 minetest.register_craft( {
1198 output = "signs:sign_wall_blue 2",
1200 { "dye:blue", "dye:white", "dye:blue" },
1201 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1205 minetest.register_craft( {
1206 output = "signs:sign_wall_brown",
1208 { "dye:brown", "dye:white", "dye:brown" },
1209 { "", default_sign_metal, "" }
1213 minetest.register_craft( {
1214 output = "signs:sign_wall_brown 2",
1216 { "dye:brown", "dye:white", "dye:brown" },
1217 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1222 if minetest.settings:get("log_mods") then
1223 minetest.log("action", S("[MOD] signs loaded"))