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 local signnode = node or minetest.get_node(pos)
572 local signname = signnode.name
573 local textpos = minetest.registered_nodes[signname].textpos
575 sign_info = textpos[minetest.get_node(pos).param2 + 1]
576 elseif signnode.name == "signs:sign_yard" then
577 sign_info = signs_lib.yard_sign_model.textpos[minetest.get_node(pos).param2 + 1]
578 elseif signnode.name == "signs:sign_hanging" then
579 sign_info = signs_lib.hanging_sign_model.textpos[minetest.get_node(pos).param2 + 1]
580 elseif string.find(signnode.name, "sign_wall") then
581 if signnode.name == default_sign
582 or signnode.name == default_sign_metal
583 or signnode.name == "locked_sign:sign_wall_locked" then
584 sign_info = signs_lib.regular_wall_sign_model.textpos[minetest.get_node(pos).param2 + 1]
586 sign_info = signs_lib.metal_wall_sign_model.textpos[minetest.get_node(pos).param2 + 1]
588 else -- ...it must be a sign on a fence post.
589 sign_info = signs_lib.sign_post_model.textpos[minetest.get_node(pos).param2 + 1]
591 if sign_info == nil then
594 local text = minetest.add_entity({x = pos.x + sign_info.delta.x,
595 y = pos.y + sign_info.delta.y,
596 z = pos.z + sign_info.delta.z}, "signs:text")
597 text:setyaw(sign_info.yaw)
600 -- What kind of sign do we need to place, anyway?
602 function signs_lib.determine_sign_type(itemstack, placer, pointed_thing, locked)
604 name = minetest.get_node(pointed_thing.under).name
605 if fences_with_sign[name] then
606 if minetest.is_protected(pointed_thing.under, placer:get_player_name()) then
607 minetest.record_protection_violation(pointed_thing.under,
608 placer:get_player_name())
612 name = minetest.get_node(pointed_thing.above).name
613 local def = minetest.registered_nodes[name]
614 if not def.buildable_to then
617 if minetest.is_protected(pointed_thing.above, placer:get_player_name()) then
618 minetest.record_protection_violation(pointed_thing.above,
619 placer:get_player_name())
624 local node=minetest.get_node(pointed_thing.under)
626 if minetest.registered_nodes[node.name] and
627 minetest.registered_nodes[node.name].on_rightclick and
628 not placer:get_player_control().sneak then
629 return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack, pointed_thing)
631 local above = pointed_thing.above
632 local under = pointed_thing.under
633 local dir = {x = under.x - above.x,
634 y = under.y - above.y,
635 z = under.z - above.z}
637 local wdir = minetest.dir_to_wallmounted(dir)
639 local placer_pos = placer:getpos()
642 x = above.x - placer_pos.x,
643 y = above.y - placer_pos.y,
644 z = above.z - placer_pos.z
648 local fdir = minetest.dir_to_facedir(dir)
649 local pt_name = minetest.get_node(under).name
650 local signname = itemstack:get_name()
652 if fences_with_sign[pt_name] and signname == default_sign then
653 minetest.add_node(under, {name = fences_with_sign[pt_name], param2 = fdir})
654 elseif wdir == 0 and signname == default_sign then
655 minetest.add_node(above, {name = "signs:sign_hanging", param2 = fdir})
656 elseif wdir == 1 and signname == default_sign then
657 minetest.add_node(above, {name = "signs:sign_yard", param2 = fdir})
658 elseif signname == default_sign_metal then
659 minetest.add_node(above, {name = signname, param2 = wdir })
660 elseif signname ~= default_sign
661 and signname ~= default_sign_metal
662 and signname ~= "locked_sign:sign_wall_locked" then -- it's a signs_lib colored metal wall sign.
663 minetest.add_node(above, {name = signname, param2 = fdir})
664 else -- it must be a default or locked wooden wall sign
665 minetest.add_node(above, {name = signname, param2 = wdir }) -- note it's wallmounted here!
667 local meta = minetest.get_meta(above)
668 local owner = placer:get_player_name()
669 meta:set_string("owner", owner)
673 if not signs_lib.expect_infinite_stacks then
674 itemstack:take_item()
680 function signs_lib.receive_fields(pos, formname, fields, sender, lock)
681 if minetest.is_protected(pos, sender:get_player_name()) then
682 minetest.record_protection_violation(pos,
683 sender:get_player_name())
686 local lockstr = lock and S("locked ") or ""
687 if fields and fields.text and fields.ok then
688 minetest.log("action", S("@1 wrote \"@2\" to @3sign at @4",
689 (sender:get_player_name() or ""),
690 fields.text:gsub('\\', '\\\\'):gsub("\n", "\\n"),
692 minetest.pos_to_string(pos)
695 signs_lib.update_sign(pos, fields, sender:get_player_name())
697 signs_lib.update_sign(pos, fields)
702 minetest.register_node(":"..default_sign, {
703 description = S("Sign"),
704 inventory_image = default_sign_image,
705 wield_image = default_sign_image,
706 node_placement_prediction = "",
707 sunlight_propagates = true,
709 paramtype2 = "wallmounted",
710 drawtype = "nodebox",
711 node_box = signs_lib.regular_wall_sign_model.nodebox,
712 tiles = {"signs_wall_sign.png"},
713 groups = sign_groups,
715 on_place = function(itemstack, placer, pointed_thing)
716 return signs_lib.determine_sign_type(itemstack, placer, pointed_thing)
718 on_construct = function(pos)
719 signs_lib.construct_sign(pos)
721 on_destruct = function(pos)
722 signs_lib.destruct_sign(pos)
724 on_receive_fields = function(pos, formname, fields, sender)
725 signs_lib.receive_fields(pos, formname, fields, sender)
727 on_punch = function(pos, node, puncher)
728 signs_lib.update_sign(pos,nil,nil,node)
730 on_rotate = signs_lib.wallmounted_rotate
733 minetest.register_node(":signs:sign_yard", {
735 sunlight_propagates = true,
736 paramtype2 = "facedir",
737 drawtype = "nodebox",
738 node_box = signs_lib.yard_sign_model.nodebox,
741 fixed = {-0.4375, -0.5, -0.0625, 0.4375, 0.375, 0}
743 tiles = {"signs_top.png", "signs_bottom.png", "signs_side.png", "signs_side.png", "signs_back.png", "signs_front.png"},
744 groups = {choppy=2, dig_immediate=2},
747 on_construct = function(pos)
748 signs_lib.construct_sign(pos)
750 on_destruct = function(pos)
751 signs_lib.destruct_sign(pos)
753 on_receive_fields = function(pos, formname, fields, sender)
754 signs_lib.receive_fields(pos, formname, fields, sender)
756 on_punch = function(pos, node, puncher)
757 signs_lib.update_sign(pos,nil,nil,node)
759 on_rotate = signs_lib.facedir_rotate_simple
763 minetest.register_node(":signs:sign_hanging", {
765 sunlight_propagates = true,
766 paramtype2 = "facedir",
767 drawtype = "nodebox",
768 node_box = signs_lib.hanging_sign_model.nodebox,
771 fixed = {-0.45, -0.275, -0.049, 0.45, 0.5, 0.049}
774 "signs_hanging_top.png",
775 "signs_hanging_bottom.png",
776 "signs_hanging_side.png",
777 "signs_hanging_side.png",
778 "signs_hanging_back.png",
779 "signs_hanging_front.png"
781 groups = {choppy=2, dig_immediate=2},
784 on_construct = function(pos)
785 signs_lib.construct_sign(pos)
787 on_destruct = function(pos)
788 signs_lib.destruct_sign(pos)
790 on_receive_fields = function(pos, formname, fields, sender)
791 signs_lib.receive_fields(pos, formname, fields, sender)
793 on_punch = function(pos, node, puncher)
794 signs_lib.update_sign(pos,nil,nil,node)
796 on_rotate = signs_lib.facedir_rotate_simple
799 minetest.register_node(":signs:sign_post", {
801 sunlight_propagates = true,
802 paramtype2 = "facedir",
803 drawtype = "nodebox",
804 node_box = signs_lib.sign_post_model.nodebox,
806 "signs_post_top.png",
807 "signs_post_bottom.png",
808 "signs_post_side.png",
809 "signs_post_side.png",
810 "signs_post_back.png",
811 "signs_post_front.png",
813 groups = {choppy=2, dig_immediate=2},
817 { items = { default_sign }},
818 { items = { "default:fence_wood" }},
821 on_rotate = signs_lib.facedir_rotate_simple
826 minetest.register_privilege("sign_editor", S("Can edit all locked signs"))
828 minetest.register_node(":locked_sign:sign_wall_locked", {
829 description = S("Locked Sign"),
830 inventory_image = "signs_locked_inv.png",
831 wield_image = "signs_locked_inv.png",
832 node_placement_prediction = "",
833 sunlight_propagates = true,
835 paramtype2 = "wallmounted",
836 drawtype = "nodebox",
837 node_box = signs_lib.regular_wall_sign_model.nodebox,
838 tiles = { "signs_wall_sign_locked.png" },
839 groups = sign_groups,
840 on_place = function(itemstack, placer, pointed_thing)
841 return signs_lib.determine_sign_type(itemstack, placer, pointed_thing, true)
843 on_construct = function(pos)
844 signs_lib.construct_sign(pos, true)
846 on_destruct = function(pos)
847 signs_lib.destruct_sign(pos)
849 on_receive_fields = function(pos, formname, fields, sender)
850 local meta = minetest.get_meta(pos)
851 local owner = meta:get_string("owner")
852 local pname = sender:get_player_name() or ""
853 if pname ~= owner and pname ~= minetest.settings:get("name")
854 and not minetest.check_player_privs(pname, {sign_editor=true}) then
857 signs_lib.receive_fields(pos, formname, fields, sender, true)
859 on_punch = function(pos, node, puncher)
860 signs_lib.update_sign(pos,nil,nil,node)
862 can_dig = function(pos, player)
863 local meta = minetest.get_meta(pos)
864 local owner = meta:get_string("owner")
865 local pname = player:get_player_name()
866 return pname == owner or pname == minetest.settings:get("name")
867 or minetest.check_player_privs(pname, {sign_editor=true})
869 on_rotate = signs_lib.wallmounted_rotate
872 -- default metal sign, if defined
874 if minetest.registered_nodes["default:sign_wall_steel"] then
875 minetest.register_node(":"..default_sign_metal, {
876 description = S("Sign"),
877 inventory_image = default_sign_metal_image,
878 wield_image = default_sign_metal_image,
879 node_placement_prediction = "",
880 sunlight_propagates = true,
882 paramtype2 = "wallmounted",
883 drawtype = "nodebox",
884 node_box = signs_lib.regular_wall_sign_model.nodebox,
885 tiles = {"signs_wall_sign_metal.png"},
886 groups = sign_groups,
888 on_place = function(itemstack, placer, pointed_thing)
889 return signs_lib.determine_sign_type(itemstack, placer, pointed_thing)
891 on_construct = function(pos)
892 signs_lib.construct_sign(pos)
894 on_destruct = function(pos)
895 signs_lib.destruct_sign(pos)
897 on_receive_fields = function(pos, formname, fields, sender)
898 signs_lib.receive_fields(pos, formname, fields, sender)
900 on_punch = function(pos, node, puncher)
901 signs_lib.update_sign(pos,nil,nil,node)
903 on_rotate = signs_lib.wallmounted_rotate
907 -- metal, colored signs
908 if enable_colored_metal_signs then
909 -- array : color, translated color, default text color
910 local sign_colors = {
911 {"green", S("green"), "f"},
912 {"yellow", S("yellow"), "0"},
913 {"red", S("red"), "f"},
914 {"white_red", S("white_red"), "4"},
915 {"white_black", S("white_black"), "0"},
916 {"orange", S("orange"), "0"},
917 {"blue", S("blue"), "f"},
918 {"brown", S("brown"), "f"},
921 for i, color in ipairs(sign_colors) do
922 minetest.register_node(":signs:sign_wall_"..color[1], {
923 description = S("Sign (@1, metal)", color[2]),
924 inventory_image = "signs_"..color[1].."_inv.png",
925 wield_image = "signs_"..color[1].."_inv.png",
926 node_placement_prediction = "",
928 sunlight_propagates = true,
929 paramtype2 = "facedir",
930 drawtype = "nodebox",
931 node_box = signs_lib.metal_wall_sign_model.nodebox,
933 "signs_metal_tb.png",
934 "signs_metal_tb.png",
935 "signs_metal_sides.png",
936 "signs_metal_sides.png",
937 "signs_metal_back.png",
938 "signs_"..color[1].."_front.png"
940 default_color = color[3],
941 groups = sign_groups,
942 on_place = function(itemstack, placer, pointed_thing)
943 return signs_lib.determine_sign_type(itemstack, placer, pointed_thing)
945 on_construct = function(pos)
946 signs_lib.construct_sign(pos)
948 on_destruct = function(pos)
949 signs_lib.destruct_sign(pos)
951 on_receive_fields = function(pos, formname, fields, sender)
952 signs_lib.receive_fields(pos, formname, fields, sender)
954 on_punch = function(pos, node, puncher)
955 signs_lib.update_sign(pos,nil,nil,node)
957 on_rotate = signs_lib.facedir_rotate
962 local signs_text_on_activate
964 signs_text_on_activate = function(self)
965 local pos = self.object:getpos()
966 local meta = minetest.get_meta(pos)
967 local text = meta:get_string("text")
968 local new = (meta:get_int("__signslib_new_format") ~= 0)
969 if text and minetest.registered_nodes[minetest.get_node(pos).name] then
970 text = trim_input(text)
971 set_obj_text(self.object, text, new, pos)
975 minetest.register_entity(":signs:text", {
976 collisionbox = { 0, 0, 0, 0, 0, 0 },
977 visual = "upright_sprite",
980 on_activate = signs_text_on_activate,
983 -- And the good stuff here! :-)
985 function signs_lib.register_fence_with_sign(fencename, fencewithsignname)
986 local def = minetest.registered_nodes[fencename]
987 local def_sign = minetest.registered_nodes[fencewithsignname]
988 if not (def and def_sign) then
989 minetest.log("warning", "[signs_lib] "..S("Attempt to register unknown node as fence"))
992 def = signs_lib.table_copy(def)
993 def_sign = signs_lib.table_copy(def_sign)
994 fences_with_sign[fencename] = fencewithsignname
996 def_sign.on_place = function(itemstack, placer, pointed_thing, ...)
997 local node_above = minetest.get_node_or_nil(pointed_thing.above)
998 local node_under = minetest.get_node_or_nil(pointed_thing.under)
999 local def_above = node_above and minetest.registered_nodes[node_above.name]
1000 local def_under = node_under and minetest.registered_nodes[node_under.name]
1001 local fdir = minetest.dir_to_facedir(placer:get_look_dir())
1002 local playername = placer:get_player_name()
1004 if minetest.is_protected(pointed_thing.under, playername) then
1005 minetest.record_protection_violation(pointed_thing.under, playername)
1009 if minetest.is_protected(pointed_thing.above, playername) then
1010 minetest.record_protection_violation(pointed_thing.above, playername)
1014 if def_under and def_under.on_rightclick then
1015 return def_under.on_rightclick(pointed_thing.under, node_under, placer, itemstack, pointed_thing) or itemstack
1016 elseif def_under and def_under.buildable_to then
1017 minetest.add_node(pointed_thing.under, {name = fencename, param2 = fdir})
1018 if not signs_lib.expect_infinite_stacks then
1019 itemstack:take_item()
1021 placer:set_wielded_item(itemstack)
1022 elseif def_above and def_above.buildable_to then
1023 minetest.add_node(pointed_thing.above, {name = fencename, param2 = fdir})
1024 if not signs_lib.expect_infinite_stacks then
1025 itemstack:take_item()
1027 placer:set_wielded_item(itemstack)
1031 def_sign.on_construct = function(pos, ...)
1032 signs_lib.construct_sign(pos)
1034 def_sign.on_destruct = function(pos, ...)
1035 signs_lib.destruct_sign(pos)
1037 def_sign.on_receive_fields = function(pos, formname, fields, sender)
1038 signs_lib.receive_fields(pos, formname, fields, sender)
1040 def_sign.on_punch = function(pos, node, puncher, ...)
1041 signs_lib.update_sign(pos,nil,nil,node)
1043 local fencename = fencename
1044 def_sign.after_dig_node = function(pos, node, ...)
1045 node.name = fencename
1046 minetest.add_node(pos, node)
1048 def_sign.on_rotate = signs_lib.facedir_rotate_simple
1050 def_sign.drop = default_sign
1051 minetest.register_node(":"..fencename, def)
1052 minetest.register_node(":"..fencewithsignname, def_sign)
1053 table.insert(signs_lib.sign_node_list, fencewithsignname)
1054 minetest.log("verbose", S("Registered @1 and @2", fencename, fencewithsignname))
1059 minetest.register_alias("homedecor:fence_wood_with_sign", "signs:sign_post")
1060 minetest.register_alias("sign_wall_locked", "locked_sign:sign_wall_locked")
1062 signs_lib.register_fence_with_sign("default:fence_wood", "signs:sign_post")
1064 -- restore signs' text after /clearobjects and the like, the next time
1065 -- a block is reloaded by the server.
1067 minetest.register_lbm({
1068 nodenames = signs_lib.sign_node_list,
1069 name = "signs_lib:restore_sign_text",
1070 label = "Restore sign text",
1071 run_at_every_load = true,
1072 action = function(pos, node)
1073 signs_lib.update_sign(pos,nil,nil,node)
1079 minetest.register_craft({
1080 output = "locked_sign:sign_wall_locked",
1083 {"default:steel_ingot"},
1087 -- craft recipes for the metal signs
1088 if enable_colored_metal_signs then
1090 minetest.register_craft( {
1091 output = "signs:sign_wall_green",
1093 { "dye:dark_green", "dye:white", "dye:dark_green" },
1094 { "", default_sign_metal, "" }
1098 minetest.register_craft( {
1099 output = "signs:sign_wall_green 2",
1101 { "dye:dark_green", "dye:white", "dye:dark_green" },
1102 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1106 minetest.register_craft( {
1107 output = "signs:sign_wall_yellow",
1109 { "dye:yellow", "dye:black", "dye:yellow" },
1110 { "", default_sign_metal, "" }
1114 minetest.register_craft( {
1115 output = "signs:sign_wall_yellow 2",
1117 { "dye:yellow", "dye:black", "dye:yellow" },
1118 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1122 minetest.register_craft( {
1123 output = "signs:sign_wall_red",
1125 { "dye:red", "dye:white", "dye:red" },
1126 { "", default_sign_metal, "" }
1130 minetest.register_craft( {
1131 output = "signs:sign_wall_red 2",
1133 { "dye:red", "dye:white", "dye:red" },
1134 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1138 minetest.register_craft( {
1139 output = "signs:sign_wall_white_red",
1141 { "dye:white", "dye:red", "dye:white" },
1142 { "", default_sign_metal, "" }
1146 minetest.register_craft( {
1147 output = "signs:sign_wall_white_red 2",
1149 { "dye:white", "dye:red", "dye:white" },
1150 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1154 minetest.register_craft( {
1155 output = "signs:sign_wall_white_black",
1157 { "dye:white", "dye:black", "dye:white" },
1158 { "", default_sign_metal, "" }
1162 minetest.register_craft( {
1163 output = "signs:sign_wall_white_black 2",
1165 { "dye:white", "dye:black", "dye:white" },
1166 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1170 minetest.register_craft( {
1171 output = "signs:sign_wall_orange",
1173 { "dye:orange", "dye:black", "dye:orange" },
1174 { "", default_sign_metal, "" }
1178 minetest.register_craft( {
1179 output = "signs:sign_wall_orange 2",
1181 { "dye:orange", "dye:black", "dye:orange" },
1182 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1186 minetest.register_craft( {
1187 output = "signs:sign_wall_blue",
1189 { "dye:blue", "dye:white", "dye:blue" },
1190 { "", default_sign_metal, "" }
1194 minetest.register_craft( {
1195 output = "signs:sign_wall_blue 2",
1197 { "dye:blue", "dye:white", "dye:blue" },
1198 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1202 minetest.register_craft( {
1203 output = "signs:sign_wall_brown",
1205 { "dye:brown", "dye:white", "dye:brown" },
1206 { "", default_sign_metal, "" }
1210 minetest.register_craft( {
1211 output = "signs:sign_wall_brown 2",
1213 { "dye:brown", "dye:white", "dye:brown" },
1214 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1219 if minetest.settings:get("log_mods") then
1220 minetest.log("action", S("[MOD] signs loaded"))