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
390 word = string.gsub(word, "%^[12345678abcdefgh]", {
391 ["^1"] = string.char(0x81),
392 ["^2"] = string.char(0x82),
393 ["^3"] = string.char(0x83),
394 ["^4"] = string.char(0x84),
395 ["^5"] = string.char(0x85),
396 ["^6"] = string.char(0x86),
397 ["^7"] = string.char(0x87),
398 ["^8"] = string.char(0x88),
399 ["^a"] = string.char(0x8a),
400 ["^b"] = string.char(0x8b),
401 ["^c"] = string.char(0x8c),
402 ["^d"] = string.char(0x8d),
403 ["^e"] = string.char(0x8e),
404 ["^f"] = string.char(0x8f),
405 ["^g"] = string.char(0x90),
406 ["^h"] = string.char(0x91)
411 local c = word:sub(i, i)
413 local cc = tonumber(word:sub(i+1, i+1), 16)
419 local w = charwidth[c]
421 width = width + w + 1
422 if width >= (SIGN_WIDTH - charwidth[" "]) then
425 maxw = math_max(width, maxw)
427 if #chars < MAX_INPUT_CHARS then
428 table.insert(chars, {
430 tex = char_tex(font_name, c),
431 col = ("%X"):format(cur_color),
434 ch_offs = ch_offs + w
439 width = width + charwidth[" "] + 1
440 maxw = math_max(width, maxw)
441 table.insert(words, { chars=chars, w=ch_offs })
444 -- Okay, we actually build the "line texture" here.
448 local start_xpos = math.floor((SIGN_WIDTH - maxw) / 2)
450 local xpos = start_xpos
451 local ypos = (LINE_HEIGHT * lineno)
455 for word_i, word in ipairs(words) do
456 local xoffs = (xpos - start_xpos)
457 if (xoffs > 0) and ((xoffs + word.w) > maxw) then
458 table.insert(texture, fill_line(xpos, ypos, maxw, "n"))
460 ypos = ypos + LINE_HEIGHT
462 if lineno >= NUMBER_OF_LINES then break end
463 table.insert(texture, fill_line(xpos, ypos, maxw, cur_color))
465 for ch_i, ch in ipairs(word.chars) do
466 if ch.col ~= cur_color then
468 table.insert(texture, fill_line(xpos + ch.off, ypos, maxw, cur_color))
470 table.insert(texture, (":%d,%d=%s"):format(xpos + ch.off, ypos, ch.tex))
474 (":%d,%d="):format(xpos + word.w, ypos) .. char_tex(font_name, " ")
476 xpos = xpos + word.w + charwidth[" "]
477 if xpos >= (SIGN_WIDTH + charwidth[" "]) then break end
480 table.insert(texture, fill_line(xpos, ypos, maxw, "n"))
481 table.insert(texture, fill_line(start_xpos, ypos + LINE_HEIGHT, maxw, "n"))
483 return table.concat(texture), lineno
486 local function make_sign_texture(lines, pos)
487 local texture = { ("[combine:%dx%d"):format(SIGN_WIDTH, LINE_HEIGHT * NUMBER_OF_LINES) }
490 if lineno >= NUMBER_OF_LINES then break end
491 local linetex, ln = make_line_texture(lines[i], lineno, pos)
492 table.insert(texture, linetex)
495 table.insert(texture, "^[makealpha:0,0,0")
496 return table.concat(texture, "")
499 local function set_obj_text(obj, text, new, pos)
500 local split = new and split_lines_and_words or split_lines_and_words_old
501 local text_ansi = Utf8ToAnsi(text)
502 local n = minetest.registered_nodes[minetest.get_node(pos).name]
503 local text_scale = (n and n.text_scale) or DEFAULT_TEXT_SCALE
505 textures={make_sign_texture(split(text_ansi), pos)},
506 visual_size = text_scale,
510 signs_lib.construct_sign = function(pos, locked)
511 local meta = minetest.get_meta(pos)
515 "textarea[0,-0.3;6.5,3;text;;${text}]"..
516 "button_exit[2,3.4;2,1;ok;"..S("Write").."]"..
517 "background[-0.5,-0.5;7,5;bg_signs_lib.jpg]")
518 meta:set_string("infotext", "")
521 signs_lib.destruct_sign = function(pos)
522 local objects = minetest.get_objects_inside_radius(pos, 0.5)
523 for _, v in ipairs(objects) do
524 local e = v:get_luaentity()
525 if e and e.name == "signs:text" then
531 local function make_infotext(text)
532 text = trim_input(text)
533 local lines = split_lines_and_words(text) or {}
535 for _, line in ipairs(lines) do
536 table.insert(lines2, (table.concat(line, " "):gsub("#[0-9a-fA-F]", ""):gsub("##", "#")))
538 return table.concat(lines2, "\n")
541 signs_lib.update_sign = function(pos, fields, owner, node)
543 -- First, check if the interact keyword from CWz's mod is being set,
544 -- or has been changed since the last restart...
546 local meta = minetest.get_meta(pos)
547 local stored_text = meta:get_string("text") or ""
548 current_keyword = rawget(_G, "mki_interact_keyword") or current_keyword
550 if fields then -- ...we're editing the sign.
551 if fields.text and string.find(dump(fields.text), "@KEYWORD") then
552 meta:set_string("keyword", current_keyword)
554 meta:set_string("keyword", nil)
556 elseif string.find(dump(stored_text), "@KEYWORD") then -- we need to check if the password is being set/changed
558 local stored_keyword = meta:get_string("keyword")
559 if stored_keyword and stored_keyword ~= "" and stored_keyword ~= current_keyword then
560 signs_lib.destruct_sign(pos)
561 meta:set_string("keyword", current_keyword)
563 if owner then ownstr = S("Locked sign, owned by @1\n", owner) end
564 meta:set_string("infotext", ownstr..string.gsub(make_infotext(stored_text), "@KEYWORD", current_keyword).." ")
572 fields.text = trim_input(fields.text)
575 if owner then ownstr = S("Locked sign, owned by @1\n", owner) end
577 meta:set_string("infotext", ownstr..string.gsub(make_infotext(fields.text), "@KEYWORD", current_keyword).." ")
578 meta:set_string("text", fields.text)
580 meta:set_int("__signslib_new_format", 1)
583 new = (meta:get_int("__signslib_new_format") ~= 0)
585 signs_lib.destruct_sign(pos)
586 local text = meta:get_string("text")
587 if text == nil or text == "" then return end
589 local signnode = node or minetest.get_node(pos)
590 local signname = signnode.name
591 local textpos = minetest.registered_nodes[signname].textpos
593 sign_info = textpos[minetest.get_node(pos).param2 + 1]
594 elseif signnode.name == "signs:sign_yard" then
595 sign_info = signs_lib.yard_sign_model.textpos[minetest.get_node(pos).param2 + 1]
596 elseif signnode.name == "signs:sign_hanging" then
597 sign_info = signs_lib.hanging_sign_model.textpos[minetest.get_node(pos).param2 + 1]
598 elseif string.find(signnode.name, "sign_wall") then
599 if signnode.name == default_sign
600 or signnode.name == default_sign_metal
601 or signnode.name == "locked_sign:sign_wall_locked" then
602 sign_info = signs_lib.regular_wall_sign_model.textpos[minetest.get_node(pos).param2 + 1]
604 sign_info = signs_lib.metal_wall_sign_model.textpos[minetest.get_node(pos).param2 + 1]
606 else -- ...it must be a sign on a fence post.
607 sign_info = signs_lib.sign_post_model.textpos[minetest.get_node(pos).param2 + 1]
609 if sign_info == nil then
612 local text = minetest.add_entity({x = pos.x + sign_info.delta.x,
613 y = pos.y + sign_info.delta.y,
614 z = pos.z + sign_info.delta.z}, "signs:text")
615 text:setyaw(sign_info.yaw)
618 -- What kind of sign do we need to place, anyway?
620 function signs_lib.determine_sign_type(itemstack, placer, pointed_thing, locked)
622 name = minetest.get_node(pointed_thing.under).name
623 if fences_with_sign[name] then
624 if minetest.is_protected(pointed_thing.under, placer:get_player_name()) then
625 minetest.record_protection_violation(pointed_thing.under,
626 placer:get_player_name())
630 name = minetest.get_node(pointed_thing.above).name
631 local def = minetest.registered_nodes[name]
632 if not def.buildable_to then
635 if minetest.is_protected(pointed_thing.above, placer:get_player_name()) then
636 minetest.record_protection_violation(pointed_thing.above,
637 placer:get_player_name())
642 local node=minetest.get_node(pointed_thing.under)
644 if minetest.registered_nodes[node.name] and
645 minetest.registered_nodes[node.name].on_rightclick and
646 not placer:get_player_control().sneak then
647 return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack, pointed_thing)
649 local above = pointed_thing.above
650 local under = pointed_thing.under
651 local dir = {x = under.x - above.x,
652 y = under.y - above.y,
653 z = under.z - above.z}
655 local wdir = minetest.dir_to_wallmounted(dir)
657 local placer_pos = placer:getpos()
660 x = above.x - placer_pos.x,
661 y = above.y - placer_pos.y,
662 z = above.z - placer_pos.z
666 local fdir = minetest.dir_to_facedir(dir)
667 local pt_name = minetest.get_node(under).name
668 local signname = itemstack:get_name()
670 if fences_with_sign[pt_name] and signname == default_sign then
671 minetest.add_node(under, {name = fences_with_sign[pt_name], param2 = fdir})
672 elseif wdir == 0 and signname == default_sign then
673 minetest.add_node(above, {name = "signs:sign_hanging", param2 = fdir})
674 elseif wdir == 1 and signname == default_sign then
675 minetest.add_node(above, {name = "signs:sign_yard", param2 = fdir})
676 elseif signname == default_sign_metal then
677 minetest.add_node(above, {name = signname, param2 = wdir })
678 elseif signname ~= default_sign
679 and signname ~= default_sign_metal
680 and signname ~= "locked_sign:sign_wall_locked" then -- it's a signs_lib colored metal wall sign.
681 minetest.add_node(above, {name = signname, param2 = fdir})
682 else -- it must be a default or locked wooden wall sign
683 minetest.add_node(above, {name = signname, param2 = wdir }) -- note it's wallmounted here!
685 local meta = minetest.get_meta(above)
686 local owner = placer:get_player_name()
687 meta:set_string("owner", owner)
691 if not signs_lib.expect_infinite_stacks then
692 itemstack:take_item()
698 function signs_lib.receive_fields(pos, formname, fields, sender, lock)
699 if minetest.is_protected(pos, sender:get_player_name()) then
700 minetest.record_protection_violation(pos,
701 sender:get_player_name())
704 local lockstr = lock and S("locked ") or ""
705 if fields and fields.text and fields.ok then
706 minetest.log("action", S("@1 wrote \"@2\" to @3sign at @4",
707 (sender:get_player_name() or ""),
708 fields.text:gsub('\\', '\\\\'):gsub("\n", "\\n"),
710 minetest.pos_to_string(pos)
713 signs_lib.update_sign(pos, fields, sender:get_player_name())
715 signs_lib.update_sign(pos, fields)
720 minetest.register_node(":"..default_sign, {
721 description = S("Sign"),
722 inventory_image = default_sign_image,
723 wield_image = default_sign_image,
724 node_placement_prediction = "",
725 sunlight_propagates = true,
727 paramtype2 = "wallmounted",
728 drawtype = "nodebox",
729 node_box = signs_lib.regular_wall_sign_model.nodebox,
730 tiles = {"signs_wall_sign.png"},
731 groups = sign_groups,
733 on_place = function(itemstack, placer, pointed_thing)
734 return signs_lib.determine_sign_type(itemstack, placer, pointed_thing)
736 on_construct = function(pos)
737 signs_lib.construct_sign(pos)
739 on_destruct = function(pos)
740 signs_lib.destruct_sign(pos)
742 on_receive_fields = function(pos, formname, fields, sender)
743 signs_lib.receive_fields(pos, formname, fields, sender)
745 on_punch = function(pos, node, puncher)
746 signs_lib.update_sign(pos,nil,nil,node)
748 on_rotate = signs_lib.wallmounted_rotate
751 minetest.register_node(":signs:sign_yard", {
753 sunlight_propagates = true,
754 paramtype2 = "facedir",
755 drawtype = "nodebox",
756 node_box = signs_lib.yard_sign_model.nodebox,
759 fixed = {-0.4375, -0.5, -0.0625, 0.4375, 0.375, 0}
761 tiles = {"signs_top.png", "signs_bottom.png", "signs_side.png", "signs_side.png", "signs_back.png", "signs_front.png"},
762 groups = {choppy=2, dig_immediate=2},
765 on_construct = function(pos)
766 signs_lib.construct_sign(pos)
768 on_destruct = function(pos)
769 signs_lib.destruct_sign(pos)
771 on_receive_fields = function(pos, formname, fields, sender)
772 signs_lib.receive_fields(pos, formname, fields, sender)
774 on_punch = function(pos, node, puncher)
775 signs_lib.update_sign(pos,nil,nil,node)
777 on_rotate = signs_lib.facedir_rotate_simple
781 minetest.register_node(":signs:sign_hanging", {
783 sunlight_propagates = true,
784 paramtype2 = "facedir",
785 drawtype = "nodebox",
786 node_box = signs_lib.hanging_sign_model.nodebox,
789 fixed = {-0.45, -0.275, -0.049, 0.45, 0.5, 0.049}
792 "signs_hanging_top.png",
793 "signs_hanging_bottom.png",
794 "signs_hanging_side.png",
795 "signs_hanging_side.png",
796 "signs_hanging_back.png",
797 "signs_hanging_front.png"
799 groups = {choppy=2, dig_immediate=2},
802 on_construct = function(pos)
803 signs_lib.construct_sign(pos)
805 on_destruct = function(pos)
806 signs_lib.destruct_sign(pos)
808 on_receive_fields = function(pos, formname, fields, sender)
809 signs_lib.receive_fields(pos, formname, fields, sender)
811 on_punch = function(pos, node, puncher)
812 signs_lib.update_sign(pos,nil,nil,node)
814 on_rotate = signs_lib.facedir_rotate_simple
817 minetest.register_node(":signs:sign_post", {
819 sunlight_propagates = true,
820 paramtype2 = "facedir",
821 drawtype = "nodebox",
822 node_box = signs_lib.sign_post_model.nodebox,
824 "signs_post_top.png",
825 "signs_post_bottom.png",
826 "signs_post_side.png",
827 "signs_post_side.png",
828 "signs_post_back.png",
829 "signs_post_front.png",
831 groups = {choppy=2, dig_immediate=2},
835 { items = { default_sign }},
836 { items = { "default:fence_wood" }},
839 on_rotate = signs_lib.facedir_rotate_simple
844 minetest.register_privilege("sign_editor", S("Can edit all locked signs"))
846 minetest.register_node(":locked_sign:sign_wall_locked", {
847 description = S("Locked Sign"),
848 inventory_image = "signs_locked_inv.png",
849 wield_image = "signs_locked_inv.png",
850 node_placement_prediction = "",
851 sunlight_propagates = true,
853 paramtype2 = "wallmounted",
854 drawtype = "nodebox",
855 node_box = signs_lib.regular_wall_sign_model.nodebox,
856 tiles = { "signs_wall_sign_locked.png" },
857 groups = sign_groups,
858 on_place = function(itemstack, placer, pointed_thing)
859 return signs_lib.determine_sign_type(itemstack, placer, pointed_thing, true)
861 on_construct = function(pos)
862 signs_lib.construct_sign(pos, true)
864 on_destruct = function(pos)
865 signs_lib.destruct_sign(pos)
867 on_receive_fields = function(pos, formname, fields, sender)
868 local meta = minetest.get_meta(pos)
869 local owner = meta:get_string("owner")
870 local pname = sender:get_player_name() or ""
871 if pname ~= owner and pname ~= minetest.settings:get("name")
872 and not minetest.check_player_privs(pname, {sign_editor=true}) then
875 signs_lib.receive_fields(pos, formname, fields, sender, true)
877 on_punch = function(pos, node, puncher)
878 signs_lib.update_sign(pos,nil,nil,node)
880 can_dig = function(pos, player)
881 local meta = minetest.get_meta(pos)
882 local owner = meta:get_string("owner")
883 local pname = player:get_player_name()
884 return pname == owner or pname == minetest.settings:get("name")
885 or minetest.check_player_privs(pname, {sign_editor=true})
887 on_rotate = signs_lib.wallmounted_rotate
890 -- default metal sign, if defined
892 if minetest.registered_nodes["default:sign_wall_steel"] then
893 minetest.register_node(":"..default_sign_metal, {
894 description = S("Sign"),
895 inventory_image = default_sign_metal_image,
896 wield_image = default_sign_metal_image,
897 node_placement_prediction = "",
898 sunlight_propagates = true,
900 paramtype2 = "wallmounted",
901 drawtype = "nodebox",
902 node_box = signs_lib.regular_wall_sign_model.nodebox,
903 tiles = {"signs_wall_sign_metal.png"},
904 groups = sign_groups,
906 on_place = function(itemstack, placer, pointed_thing)
907 return signs_lib.determine_sign_type(itemstack, placer, pointed_thing)
909 on_construct = function(pos)
910 signs_lib.construct_sign(pos)
912 on_destruct = function(pos)
913 signs_lib.destruct_sign(pos)
915 on_receive_fields = function(pos, formname, fields, sender)
916 signs_lib.receive_fields(pos, formname, fields, sender)
918 on_punch = function(pos, node, puncher)
919 signs_lib.update_sign(pos,nil,nil,node)
921 on_rotate = signs_lib.wallmounted_rotate
925 -- metal, colored signs
926 if enable_colored_metal_signs then
927 -- array : color, translated color, default text color
928 local sign_colors = {
929 {"green", S("green"), "f"},
930 {"yellow", S("yellow"), "0"},
931 {"red", S("red"), "f"},
932 {"white_red", S("white_red"), "4"},
933 {"white_black", S("white_black"), "0"},
934 {"orange", S("orange"), "0"},
935 {"blue", S("blue"), "f"},
936 {"brown", S("brown"), "f"},
939 for i, color in ipairs(sign_colors) do
940 minetest.register_node(":signs:sign_wall_"..color[1], {
941 description = S("Sign (@1, metal)", color[2]),
942 inventory_image = "signs_"..color[1].."_inv.png",
943 wield_image = "signs_"..color[1].."_inv.png",
944 node_placement_prediction = "",
946 sunlight_propagates = true,
947 paramtype2 = "facedir",
948 drawtype = "nodebox",
949 node_box = signs_lib.metal_wall_sign_model.nodebox,
951 "signs_metal_tb.png",
952 "signs_metal_tb.png",
953 "signs_metal_sides.png",
954 "signs_metal_sides.png",
955 "signs_metal_back.png",
956 "signs_"..color[1].."_front.png"
958 default_color = color[3],
959 groups = sign_groups,
960 on_place = function(itemstack, placer, pointed_thing)
961 return signs_lib.determine_sign_type(itemstack, placer, pointed_thing)
963 on_construct = function(pos)
964 signs_lib.construct_sign(pos)
966 on_destruct = function(pos)
967 signs_lib.destruct_sign(pos)
969 on_receive_fields = function(pos, formname, fields, sender)
970 signs_lib.receive_fields(pos, formname, fields, sender)
972 on_punch = function(pos, node, puncher)
973 signs_lib.update_sign(pos,nil,nil,node)
975 on_rotate = signs_lib.facedir_rotate
980 local signs_text_on_activate
982 signs_text_on_activate = function(self)
983 local pos = self.object:getpos()
984 local meta = minetest.get_meta(pos)
985 local text = meta:get_string("text")
986 local new = (meta:get_int("__signslib_new_format") ~= 0)
987 if text and minetest.registered_nodes[minetest.get_node(pos).name] then
988 text = trim_input(text)
989 set_obj_text(self.object, text, new, pos)
993 minetest.register_entity(":signs:text", {
994 collisionbox = { 0, 0, 0, 0, 0, 0 },
995 visual = "upright_sprite",
998 on_activate = signs_text_on_activate,
1001 -- And the good stuff here! :-)
1003 function signs_lib.register_fence_with_sign(fencename, fencewithsignname)
1004 local def = minetest.registered_nodes[fencename]
1005 local def_sign = minetest.registered_nodes[fencewithsignname]
1006 if not (def and def_sign) then
1007 minetest.log("warning", "[signs_lib] "..S("Attempt to register unknown node as fence"))
1010 def = signs_lib.table_copy(def)
1011 def_sign = signs_lib.table_copy(def_sign)
1012 fences_with_sign[fencename] = fencewithsignname
1014 def_sign.on_place = function(itemstack, placer, pointed_thing, ...)
1015 local node_above = minetest.get_node_or_nil(pointed_thing.above)
1016 local node_under = minetest.get_node_or_nil(pointed_thing.under)
1017 local def_above = node_above and minetest.registered_nodes[node_above.name]
1018 local def_under = node_under and minetest.registered_nodes[node_under.name]
1019 local fdir = minetest.dir_to_facedir(placer:get_look_dir())
1020 local playername = placer:get_player_name()
1022 if minetest.is_protected(pointed_thing.under, playername) then
1023 minetest.record_protection_violation(pointed_thing.under, playername)
1027 if minetest.is_protected(pointed_thing.above, playername) then
1028 minetest.record_protection_violation(pointed_thing.above, playername)
1032 if def_under and def_under.on_rightclick then
1033 return def_under.on_rightclick(pointed_thing.under, node_under, placer, itemstack, pointed_thing) or itemstack
1034 elseif def_under and def_under.buildable_to then
1035 minetest.add_node(pointed_thing.under, {name = fencename, param2 = fdir})
1036 if not signs_lib.expect_infinite_stacks then
1037 itemstack:take_item()
1039 placer:set_wielded_item(itemstack)
1040 elseif def_above and def_above.buildable_to then
1041 minetest.add_node(pointed_thing.above, {name = fencename, param2 = fdir})
1042 if not signs_lib.expect_infinite_stacks then
1043 itemstack:take_item()
1045 placer:set_wielded_item(itemstack)
1049 def_sign.on_construct = function(pos, ...)
1050 signs_lib.construct_sign(pos)
1052 def_sign.on_destruct = function(pos, ...)
1053 signs_lib.destruct_sign(pos)
1055 def_sign.on_receive_fields = function(pos, formname, fields, sender)
1056 signs_lib.receive_fields(pos, formname, fields, sender)
1058 def_sign.on_punch = function(pos, node, puncher, ...)
1059 signs_lib.update_sign(pos,nil,nil,node)
1061 local fencename = fencename
1062 def_sign.after_dig_node = function(pos, node, ...)
1063 node.name = fencename
1064 minetest.add_node(pos, node)
1066 def_sign.on_rotate = signs_lib.facedir_rotate_simple
1068 def_sign.drop = default_sign
1069 minetest.register_node(":"..fencename, def)
1070 minetest.register_node(":"..fencewithsignname, def_sign)
1071 table.insert(signs_lib.sign_node_list, fencewithsignname)
1072 minetest.log("verbose", S("Registered @1 and @2", fencename, fencewithsignname))
1077 minetest.register_alias("homedecor:fence_wood_with_sign", "signs:sign_post")
1078 minetest.register_alias("sign_wall_locked", "locked_sign:sign_wall_locked")
1080 signs_lib.register_fence_with_sign("default:fence_wood", "signs:sign_post")
1082 -- restore signs' text after /clearobjects and the like, the next time
1083 -- a block is reloaded by the server.
1085 minetest.register_lbm({
1086 nodenames = signs_lib.sign_node_list,
1087 name = "signs_lib:restore_sign_text",
1088 label = "Restore sign text",
1089 run_at_every_load = true,
1090 action = function(pos, node)
1091 signs_lib.update_sign(pos,nil,nil,node)
1097 minetest.register_craft({
1098 output = "locked_sign:sign_wall_locked",
1101 {"default:steel_ingot"},
1105 -- craft recipes for the metal signs
1106 if enable_colored_metal_signs then
1108 minetest.register_craft( {
1109 output = "signs:sign_wall_green",
1111 { "dye:dark_green", "dye:white", "dye:dark_green" },
1112 { "", default_sign_metal, "" }
1116 minetest.register_craft( {
1117 output = "signs:sign_wall_green 2",
1119 { "dye:dark_green", "dye:white", "dye:dark_green" },
1120 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1124 minetest.register_craft( {
1125 output = "signs:sign_wall_yellow",
1127 { "dye:yellow", "dye:black", "dye:yellow" },
1128 { "", default_sign_metal, "" }
1132 minetest.register_craft( {
1133 output = "signs:sign_wall_yellow 2",
1135 { "dye:yellow", "dye:black", "dye:yellow" },
1136 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1140 minetest.register_craft( {
1141 output = "signs:sign_wall_red",
1143 { "dye:red", "dye:white", "dye:red" },
1144 { "", default_sign_metal, "" }
1148 minetest.register_craft( {
1149 output = "signs:sign_wall_red 2",
1151 { "dye:red", "dye:white", "dye:red" },
1152 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1156 minetest.register_craft( {
1157 output = "signs:sign_wall_white_red",
1159 { "dye:white", "dye:red", "dye:white" },
1160 { "", default_sign_metal, "" }
1164 minetest.register_craft( {
1165 output = "signs:sign_wall_white_red 2",
1167 { "dye:white", "dye:red", "dye:white" },
1168 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1172 minetest.register_craft( {
1173 output = "signs:sign_wall_white_black",
1175 { "dye:white", "dye:black", "dye:white" },
1176 { "", default_sign_metal, "" }
1180 minetest.register_craft( {
1181 output = "signs:sign_wall_white_black 2",
1183 { "dye:white", "dye:black", "dye:white" },
1184 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1188 minetest.register_craft( {
1189 output = "signs:sign_wall_orange",
1191 { "dye:orange", "dye:black", "dye:orange" },
1192 { "", default_sign_metal, "" }
1196 minetest.register_craft( {
1197 output = "signs:sign_wall_orange 2",
1199 { "dye:orange", "dye:black", "dye:orange" },
1200 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1204 minetest.register_craft( {
1205 output = "signs:sign_wall_blue",
1207 { "dye:blue", "dye:white", "dye:blue" },
1208 { "", default_sign_metal, "" }
1212 minetest.register_craft( {
1213 output = "signs:sign_wall_blue 2",
1215 { "dye:blue", "dye:white", "dye:blue" },
1216 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1220 minetest.register_craft( {
1221 output = "signs:sign_wall_brown",
1223 { "dye:brown", "dye:white", "dye:brown" },
1224 { "", default_sign_metal, "" }
1228 minetest.register_craft( {
1229 output = "signs:sign_wall_brown 2",
1231 { "dye:brown", "dye:white", "dye:brown" },
1232 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1237 if minetest.settings:get("log_mods") then
1238 minetest.log("action", S("[MOD] signs loaded"))