]> git.lizzy.rs Git - signs_lib.git/blob - init.lua
b8b4db593afca598852d4530068ee81493c41089
[signs_lib.git] / init.lua
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
4 -- and Diego Martinez
5
6 -- textpos = {
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 }
11 -- }
12 -- Made colored metal signs optionals
13 local enable_colored_metal_signs = true
14
15 -- CWz's keyword interact mod uses this setting.
16 local current_keyword = minetest.settings:get("interact_keyword") or "iaccept"
17
18 signs_lib = {}
19 signs_lib.path = minetest.get_modpath(minetest.get_current_modname())
20 screwdriver = screwdriver or {}
21
22 -- Load support for intllib.
23 local S, NS = dofile(signs_lib.path .. "/intllib.lua")
24 signs_lib.gettext = S
25
26 -- text encoding
27 dofile(signs_lib.path .. "/encoding.lua");
28
29
30 local wall_dir_change = {
31         [0] = 4,
32         0,
33         5,
34         1,
35         2,
36         3,
37         0
38 }
39
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)
44         return true
45 end
46
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
51                 newparam2 = 6
52         elseif newparam2 > 6 then
53                 newparam2 = 0
54         end
55         minetest.swap_node(pos, { name = node.name, param2 = newparam2 })
56         signs_lib.update_sign(pos,nil,nil,node)
57         return true
58 end
59
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)
66         return true
67 end
68
69
70 signs_lib.modpath = minetest.get_modpath("signs_lib")
71
72 local DEFAULT_TEXT_SCALE = {x=0.8, y=0.5}
73
74 signs_lib.regular_wall_sign_model = {
75         nodebox = {
76                 type = "wallmounted",
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 }
80         },
81         textpos = {
82                 nil,
83                 nil,
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},
88         }
89 }
90
91 signs_lib.metal_wall_sign_model = {
92         nodebox = {
93                 type = "fixed",
94                 fixed = {-0.4375, -0.25, 0.4375, 0.4375, 0.375, 0.5}
95         },
96         textpos = {
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},
101         }
102 }
103
104 signs_lib.yard_sign_model = {
105         nodebox = {
106                 type = "fixed",
107                 fixed = {
108                                 {-0.4375, -0.25, -0.0625, 0.4375, 0.375, 0},
109                                 {-0.0625, -0.5, -0.0625, 0.0625, -0.1875, 0},
110                 }
111         },
112         textpos = {
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},
117         }
118 }
119
120 signs_lib.hanging_sign_model = {
121         nodebox = {
122                 type = "fixed",
123                 fixed = {
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},
126                 }
127         },
128         textpos = {
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},
133         }
134 }
135
136 signs_lib.sign_post_model = {
137         nodebox = {
138                 type = "fixed",
139                 fixed = {
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},
142                 }
143         },
144         textpos = {
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},
149         }
150 }
151
152 -- the list of standard sign nodes
153
154 signs_lib.sign_node_list = {
155         "default:sign_wall_wood",
156         "default:sign_wall_steel",
157         "signs:sign_yard",
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"
168 }
169
170 local default_sign, default_sign_image
171
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"
176 else
177         default_sign = "default:sign_wall"
178         default_sign_image = "default_sign_wall.png"
179 end
180
181 default_sign_metal = "default:sign_wall_steel"
182 default_sign_metal_image = "default_sign_steel.png"
183
184 --table copy
185
186 function signs_lib.table_copy(t)
187         local nt = { }
188         for k, v in pairs(t) do
189                 if type(v) == "table" then
190                         nt[k] = signs_lib.table_copy(v)
191                 else
192                         nt[k] = v
193                 end
194         end
195         return nt
196 end
197
198 -- infinite stacks
199
200 if not minetest.settings:get_bool("creative_mode") then
201         signs_lib.expect_infinite_stacks = false
202 else
203         signs_lib.expect_infinite_stacks = true
204 end
205
206 -- CONSTANTS
207
208 -- Path to the textures.
209 local TP = signs_lib.path .. "/textures"
210 -- Font file formatter
211 local CHAR_FILE = "%s_%02x.png"
212 -- Fonts path
213 local CHAR_PATH = TP .. "/" .. CHAR_FILE
214
215 -- Font name.
216 local font_name = "hdf"
217
218 -- Lots of overkill here. KISS advocates, go away, shoo! ;) -- kaeza
219
220 local PNG_HDR = string.char(0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A)
221
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)
226         mode = mode or "r";
227         local f = io.open(name, mode)
228         if f ~= nil then
229                 if (return_handle) then
230                         return f
231                 end
232                 io.close(f) 
233                 return true 
234         else 
235                 return false 
236         end
237 end
238
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)
245         if (not f) then
246                 return 0, 0
247         end
248         f:seek("set", 0x0)
249         local hdr = f:read(string.len(PNG_HDR))
250         if hdr ~= PNG_HDR then
251                 f:close()
252                 return
253         end
254         f:seek("set", 0x13)
255         local ws = f:read(1)
256         f:seek("set", 0x17)
257         local hs = f:read(1)
258         f:close()
259         return ws:byte(), hs:byte()
260 end
261
262 -- Set by build_char_db()
263 local LINE_HEIGHT
264 local SIGN_WIDTH
265 local COLORBGW, COLORBGH
266
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
273
274 -- 6 rows, max 80 chars per, plus a bit of fudge to
275 -- avoid excess trimming (e.g. due to color codes)
276
277 local MAX_INPUT_CHARS = 600
278
279 -- This holds the individual character widths.
280 -- Indexed by the actual character (e.g. charwidth["A"])
281 local charwidth
282
283 -- helper functions to trim sign text input/output
284
285 local function trim_input(text)
286         return text:sub(1, math.min(MAX_INPUT_CHARS, text:len()))
287 end
288
289 local function build_char_db()
290
291         charwidth = { }
292
293         -- To calculate average char width.
294         local total_width = 0
295         local char_count = 0
296
297         for c = 32, 255 do
298                 local w, h = read_image_size(CHAR_PATH:format(font_name, c))
299                 if w and h then
300                         local ch = string.char(c)
301                         charwidth[ch] = w
302                         total_width = total_width + w
303                         char_count = char_count + 1
304                 end
305         end
306
307         COLORBGW, COLORBGH = read_image_size(TP.."/slc_n.png")
308         assert(COLORBGW and COLORBGH, "error reading bg dimensions")
309         LINE_HEIGHT = COLORBGH
310
311         -- XXX: Is there a better way to calc this?
312         SIGN_WIDTH = math.floor((total_width / char_count) * CHARS_PER_LINE)
313
314 end
315
316 local sign_groups = {choppy=2, dig_immediate=2}
317
318 local fences_with_sign = { }
319
320 -- some local helper functions
321
322 local function split_lines_and_words_old(text)
323         local lines = { }
324         local line = { }
325         if not text then return end
326         for word in text:gmatch("%S+") do
327                 if word == "|" then
328                         table.insert(lines, line)
329                         if #lines >= NUMBER_OF_LINES then break end
330                         line = { }
331                 elseif word == "\\|" then
332                         table.insert(line, "|")
333                 else
334                         table.insert(line, word)
335                 end
336         end
337         table.insert(lines, line)
338         return lines
339 end
340
341 local function split_lines_and_words(text)
342         if not text then return end
343         text = string.gsub(text, "@KEYWORD", current_keyword)
344         local lines = { }
345         for _, line in ipairs(text:split("\n")) do
346                 table.insert(lines, line:split(" "))
347         end
348         return lines
349 end
350
351 local math_max = math.max
352
353 local function fill_line(x, y, w, c)
354         c = c or "0"
355         local tex = { }
356         for xx = 0, math.max(0, w), COLORBGW do
357                 table.insert(tex, (":%d,%d=slc_%s.png"):format(x + xx, y, c))
358         end
359         return table.concat(tex)
360 end
361
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)
365         local c = ch:byte()
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)
369         else
370                 tex = CHAR_FILE:format(font_name, 0x0)
371         end
372         return tex, exists
373 end
374
375 local function make_line_texture(line, lineno, pos)
376
377         local width = 0
378         local maxw = 0
379
380         local words = { }
381         local n = minetest.registered_nodes[minetest.get_node(pos).name]
382         local default_color = n.default_color or 0
383
384         local cur_color = tonumber(default_color, 16)
385
386         -- We check which chars are available here.
387         for word_i, word in ipairs(line) do
388                 local chars = { }
389                 local ch_offs = 0
390                 local word_l = #word
391                 local i = 1
392                 while i <= word_l  do
393                         local c = word:sub(i, i)
394                         if c == "#" then
395                                 local cc = tonumber(word:sub(i+1, i+1), 16)
396                                 if cc then
397                                         i = i + 1
398                                         cur_color = cc
399                                 end
400                         else
401                                 local w = charwidth[c]
402                                 if w then
403                                         width = width + w + 1
404                                         if width >= (SIGN_WIDTH - charwidth[" "]) then
405                                                 width = 0
406                                         else
407                                                 maxw = math_max(width, maxw)
408                                         end
409                                         if #chars < MAX_INPUT_CHARS then
410                                                 table.insert(chars, {
411                                                         off = ch_offs,
412                                                         tex = char_tex(font_name, c),
413                                                         col = ("%X"):format(cur_color),
414                                                 })
415                                         end
416                                         ch_offs = ch_offs + w
417                                 end
418                         end
419                         i = i + 1
420                 end
421                 width = width + charwidth[" "] + 1
422                 maxw = math_max(width, maxw)
423                 table.insert(words, { chars=chars, w=ch_offs })
424         end
425
426         -- Okay, we actually build the "line texture" here.
427
428         local texture = { }
429
430         local start_xpos = math.floor((SIGN_WIDTH - maxw) / 2)
431
432         local xpos = start_xpos
433         local ypos = (LINE_HEIGHT * lineno)
434
435         cur_color = nil
436
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"))
441                         xpos = start_xpos
442                         ypos = ypos + LINE_HEIGHT
443                         lineno = lineno + 1
444                         if lineno >= NUMBER_OF_LINES then break end
445                         table.insert(texture, fill_line(xpos, ypos, maxw, cur_color))
446                 end
447                 for ch_i, ch in ipairs(word.chars) do
448                         if ch.col ~= cur_color then
449                                 cur_color = ch.col
450                                 table.insert(texture, fill_line(xpos + ch.off, ypos, maxw, cur_color))
451                         end
452                         table.insert(texture, (":%d,%d=%s"):format(xpos + ch.off, ypos, ch.tex))
453                 end
454                 table.insert(
455                         texture, 
456                         (":%d,%d="):format(xpos + word.w, ypos) .. char_tex(font_name, " ")
457                 )
458                 xpos = xpos + word.w + charwidth[" "]
459                 if xpos >= (SIGN_WIDTH + charwidth[" "]) then break end
460         end
461
462         table.insert(texture, fill_line(xpos, ypos, maxw, "n"))
463         table.insert(texture, fill_line(start_xpos, ypos + LINE_HEIGHT, maxw, "n"))
464
465         return table.concat(texture), lineno
466 end
467
468 local function make_sign_texture(lines, pos)
469         local texture = { ("[combine:%dx%d"):format(SIGN_WIDTH, LINE_HEIGHT * NUMBER_OF_LINES) }
470         local lineno = 0
471         for i = 1, #lines do
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)
475                 lineno = ln + 1
476         end
477         table.insert(texture, "^[makealpha:0,0,0")
478         return table.concat(texture, "")
479 end
480
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
486         obj:set_properties({
487                 textures={make_sign_texture(split(text_ansi), pos)},
488                 visual_size = text_scale,
489         })
490 end
491
492 signs_lib.construct_sign = function(pos, locked)
493         local meta = minetest.get_meta(pos)
494         meta:set_string(
495                 "formspec",
496                 "size[6,4]"..
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", "")
501 end
502
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
508                         v:remove()
509                 end
510         end
511 end
512
513 local function make_infotext(text)
514         text = trim_input(text)
515         local lines = split_lines_and_words(text) or {}
516         local lines2 = { }
517         for _, line in ipairs(lines) do
518                 table.insert(lines2, (table.concat(line, " "):gsub("#[0-9a-fA-F]", ""):gsub("##", "#")))
519         end
520         return table.concat(lines2, "\n")
521 end
522
523 signs_lib.update_sign = function(pos, fields, owner, node)
524
525         -- First, check if the interact keyword from CWz's mod is being set,
526         -- or has been changed since the last restart...
527
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
531
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)
535                 else
536                         meta:set_string("keyword", nil)
537                 end
538         elseif string.find(dump(stored_text), "@KEYWORD") then -- we need to check if the password is being set/changed
539
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)
544                         local ownstr = ""
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).." ")
547                 end
548         end
549
550         local new
551
552         if fields then
553
554                 fields.text = trim_input(fields.text)
555
556                 local ownstr = ""
557                 if owner then ownstr = S("Locked sign, owned by @1\n", owner) end
558
559                 meta:set_string("infotext", ownstr..string.gsub(make_infotext(fields.text), "@KEYWORD", current_keyword).." ")
560                 meta:set_string("text", fields.text)
561                 
562                 meta:set_int("__signslib_new_format", 1)
563                 new = true
564         else
565                 new = (meta:get_int("__signslib_new_format") ~= 0)
566         end
567         signs_lib.destruct_sign(pos)
568         local text = meta:get_string("text")
569         if text == nil or text == "" then return end
570
571 if node then print ("---", node.name, node.param2) end
572
573         local sign_info
574         local signnode = node or minetest.get_node(pos)
575         local signname = signnode.name
576         local textpos = minetest.registered_nodes[signname].textpos
577         if textpos then
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]
588                 else
589                         sign_info = signs_lib.metal_wall_sign_model.textpos[minetest.get_node(pos).param2 + 1]
590                 end
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]
593         end
594         if sign_info == nil then
595                 return
596         end
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)
601 end
602
603 -- What kind of sign do we need to place, anyway?
604
605 function signs_lib.determine_sign_type(itemstack, placer, pointed_thing, locked)
606         local name
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())
612                         return itemstack
613                 end
614         else
615                 name = minetest.get_node(pointed_thing.above).name
616                 local def = minetest.registered_nodes[name]
617                 if not def.buildable_to then
618                         return itemstack
619                 end
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())
623                         return itemstack
624                 end
625         end
626
627         local node=minetest.get_node(pointed_thing.under)
628
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)
633         else
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}
639
640                 local wdir = minetest.dir_to_wallmounted(dir)
641
642                 local placer_pos = placer:getpos()
643                 if placer_pos then
644                         dir = {
645                                 x = above.x - placer_pos.x,
646                                 y = above.y - placer_pos.y,
647                                 z = above.z - placer_pos.z
648                         }
649                 end
650
651                 local fdir = minetest.dir_to_facedir(dir)
652                 local pt_name = minetest.get_node(under).name
653                 local signname = itemstack:get_name()
654
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!
669                         if locked then
670                                 local meta = minetest.get_meta(above)
671                                 local owner = placer:get_player_name()
672                                 meta:set_string("owner", owner)
673                         end
674                 end
675
676                 if not signs_lib.expect_infinite_stacks then
677                         itemstack:take_item()
678                 end
679                 return itemstack
680         end
681 end
682
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())
687                 return
688         end
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"),
694                         lockstr,
695                         minetest.pos_to_string(pos)
696                 ))
697                 if lock then
698                         signs_lib.update_sign(pos, fields, sender:get_player_name())
699                 else
700                         signs_lib.update_sign(pos, fields)
701                 end
702         end
703 end
704
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,
711         paramtype = "light",
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,
717
718         on_place = function(itemstack, placer, pointed_thing)
719                 return signs_lib.determine_sign_type(itemstack, placer, pointed_thing)
720         end,
721         on_construct = function(pos)
722                 signs_lib.construct_sign(pos)
723         end,
724         on_destruct = function(pos)
725                 signs_lib.destruct_sign(pos)
726         end,
727         on_receive_fields = function(pos, formname, fields, sender)
728                 signs_lib.receive_fields(pos, formname, fields, sender)
729         end,
730         on_punch = function(pos, node, puncher)
731                 signs_lib.update_sign(pos,nil,nil,node)
732         end,
733         on_rotate = signs_lib.wallmounted_rotate
734 })
735
736 minetest.register_node(":signs:sign_yard", {
737         paramtype = "light",
738         sunlight_propagates = true,
739         paramtype2 = "facedir",
740         drawtype = "nodebox",
741         node_box = signs_lib.yard_sign_model.nodebox,
742         selection_box = {
743                 type = "fixed",
744                 fixed = {-0.4375, -0.5, -0.0625, 0.4375, 0.375, 0}
745         },
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},
748         drop = default_sign,
749
750         on_construct = function(pos)
751                 signs_lib.construct_sign(pos)
752         end,
753         on_destruct = function(pos)
754                 signs_lib.destruct_sign(pos)
755         end,
756         on_receive_fields = function(pos, formname, fields, sender)
757                 signs_lib.receive_fields(pos, formname, fields, sender)
758         end,
759         on_punch = function(pos, node, puncher)
760                 signs_lib.update_sign(pos,nil,nil,node)
761         end,
762         on_rotate = signs_lib.facedir_rotate_simple
763
764 })
765
766 minetest.register_node(":signs:sign_hanging", {
767         paramtype = "light",
768         sunlight_propagates = true,
769         paramtype2 = "facedir",
770         drawtype = "nodebox",
771         node_box = signs_lib.hanging_sign_model.nodebox,
772         selection_box = {
773                 type = "fixed",
774                 fixed = {-0.45, -0.275, -0.049, 0.45, 0.5, 0.049}
775         },
776         tiles = {
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"
783         },
784         groups = {choppy=2, dig_immediate=2},
785         drop = default_sign,
786
787         on_construct = function(pos)
788                 signs_lib.construct_sign(pos)
789         end,
790         on_destruct = function(pos)
791                 signs_lib.destruct_sign(pos)
792         end,
793         on_receive_fields = function(pos, formname, fields, sender)
794                 signs_lib.receive_fields(pos, formname, fields, sender)
795         end,
796         on_punch = function(pos, node, puncher)
797                 signs_lib.update_sign(pos,nil,nil,node)
798         end,
799         on_rotate = signs_lib.facedir_rotate_simple
800 })
801
802 minetest.register_node(":signs:sign_post", {
803         paramtype = "light",
804         sunlight_propagates = true,
805         paramtype2 = "facedir",
806         drawtype = "nodebox",
807         node_box = signs_lib.sign_post_model.nodebox,
808         tiles = {
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",
815         },
816         groups = {choppy=2, dig_immediate=2},
817         drop = {
818                 max_items = 2,
819                 items = {
820                         { items = { default_sign }},
821                         { items = { "default:fence_wood" }},
822                 },
823         },
824         on_rotate = signs_lib.facedir_rotate_simple
825 })
826
827 -- Locked wall sign
828
829 minetest.register_privilege("sign_editor", S("Can edit all locked signs"))
830
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,
837         paramtype = "light",
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)
845         end,
846         on_construct = function(pos)
847                 signs_lib.construct_sign(pos, true)
848         end,
849         on_destruct = function(pos)
850                 signs_lib.destruct_sign(pos)
851         end,
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
858                         return
859                 end
860                 signs_lib.receive_fields(pos, formname, fields, sender, true)
861         end,
862         on_punch = function(pos, node, puncher)
863                 signs_lib.update_sign(pos,nil,nil,node)
864         end,
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})
871         end,
872         on_rotate = signs_lib.wallmounted_rotate
873 })
874
875 -- default metal sign, if defined
876
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,
884                 paramtype = "light",
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,
890
891                 on_place = function(itemstack, placer, pointed_thing)
892                         return signs_lib.determine_sign_type(itemstack, placer, pointed_thing)
893                 end,
894                 on_construct = function(pos)
895                         signs_lib.construct_sign(pos)
896                 end,
897                 on_destruct = function(pos)
898                         signs_lib.destruct_sign(pos)
899                 end,
900                 on_receive_fields = function(pos, formname, fields, sender)
901                         signs_lib.receive_fields(pos, formname, fields, sender)
902                 end,
903                 on_punch = function(pos, node, puncher)
904                         signs_lib.update_sign(pos,nil,nil,node)
905                 end,
906                 on_rotate = signs_lib.wallmounted_rotate
907         })
908 end
909
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"},
922         }
923
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 = "",
930                         paramtype = "light",
931                         sunlight_propagates = true,
932                         paramtype2 = "facedir",
933                         drawtype = "nodebox",
934                         node_box = signs_lib.metal_wall_sign_model.nodebox,
935                         tiles = {
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"
942                         },
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)
947                         end,
948                         on_construct = function(pos)
949                                 signs_lib.construct_sign(pos)
950                         end,
951                         on_destruct = function(pos)
952                                 signs_lib.destruct_sign(pos)
953                         end,
954                         on_receive_fields = function(pos, formname, fields, sender)
955                                 signs_lib.receive_fields(pos, formname, fields, sender)
956                         end,
957                         on_punch = function(pos, node, puncher)
958                                 signs_lib.update_sign(pos,nil,nil,node)
959                         end,
960                         on_rotate = signs_lib.facedir_rotate
961                 })
962         end
963 end
964
965 local signs_text_on_activate
966
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)
975         end
976 end
977
978 minetest.register_entity(":signs:text", {
979         collisionbox = { 0, 0, 0, 0, 0, 0 },
980         visual = "upright_sprite",
981         textures = {},
982
983         on_activate = signs_text_on_activate,
984 })
985
986 -- And the good stuff here! :-)
987
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"))
993                 return
994         end
995         def = signs_lib.table_copy(def)
996         def_sign = signs_lib.table_copy(def_sign)
997         fences_with_sign[fencename] = fencewithsignname
998
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()
1006
1007                 if minetest.is_protected(pointed_thing.under, playername) then
1008                         minetest.record_protection_violation(pointed_thing.under, playername)
1009                         return itemstack
1010                 end
1011
1012                 if minetest.is_protected(pointed_thing.above, playername) then
1013                         minetest.record_protection_violation(pointed_thing.above, playername)
1014                         return itemstack
1015                 end
1016
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()
1023                         end
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()
1029                         end
1030                         placer:set_wielded_item(itemstack)
1031                 end
1032                 return itemstack
1033         end
1034         def_sign.on_construct = function(pos, ...)
1035                 signs_lib.construct_sign(pos)
1036         end
1037         def_sign.on_destruct = function(pos, ...)
1038                 signs_lib.destruct_sign(pos)
1039         end
1040         def_sign.on_receive_fields = function(pos, formname, fields, sender)
1041                 signs_lib.receive_fields(pos, formname, fields, sender)
1042         end
1043         def_sign.on_punch = function(pos, node, puncher, ...)
1044                 signs_lib.update_sign(pos,nil,nil,node)
1045         end
1046         local fencename = fencename
1047         def_sign.after_dig_node = function(pos, node, ...)
1048                 node.name = fencename
1049                 minetest.add_node(pos, node)
1050         end
1051         def_sign.on_rotate = signs_lib.facedir_rotate_simple
1052
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))
1058 end
1059
1060 build_char_db()
1061
1062 minetest.register_alias("homedecor:fence_wood_with_sign", "signs:sign_post")
1063 minetest.register_alias("sign_wall_locked", "locked_sign:sign_wall_locked")
1064
1065 signs_lib.register_fence_with_sign("default:fence_wood", "signs:sign_post")
1066
1067 -- restore signs' text after /clearobjects and the like, the next time
1068 -- a block is reloaded by the server.
1069
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)
1077         end
1078 })
1079
1080 -- locked sign
1081
1082 minetest.register_craft({
1083                 output = "locked_sign:sign_wall_locked",
1084                 recipe = {
1085                         {default_sign},
1086                         {"default:steel_ingot"},
1087         },
1088 })
1089
1090 -- craft recipes for the metal signs
1091 if enable_colored_metal_signs then
1092
1093         minetest.register_craft( {
1094                 output = "signs:sign_wall_green",
1095                 recipe = {
1096                                 { "dye:dark_green", "dye:white", "dye:dark_green" },
1097                                 { "", default_sign_metal, "" }
1098                 },
1099         })
1100
1101         minetest.register_craft( {
1102                 output = "signs:sign_wall_green 2",
1103                 recipe = {
1104                                 { "dye:dark_green", "dye:white", "dye:dark_green" },
1105                                 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1106                 },
1107         })
1108
1109         minetest.register_craft( {
1110                 output = "signs:sign_wall_yellow",
1111                 recipe = {
1112                                 { "dye:yellow", "dye:black", "dye:yellow" },
1113                                 { "", default_sign_metal, "" }
1114                 },
1115         })
1116
1117         minetest.register_craft( {
1118                 output = "signs:sign_wall_yellow 2",
1119                 recipe = {
1120                                 { "dye:yellow", "dye:black", "dye:yellow" },
1121                                 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1122                 },
1123         })
1124
1125         minetest.register_craft( {
1126                 output = "signs:sign_wall_red",
1127                 recipe = {
1128                                 { "dye:red", "dye:white", "dye:red" },
1129                                 { "", default_sign_metal, "" }
1130                 },
1131         })
1132
1133         minetest.register_craft( {
1134                 output = "signs:sign_wall_red 2",
1135                 recipe = {
1136                                 { "dye:red", "dye:white", "dye:red" },
1137                                 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1138                 },
1139         })
1140
1141         minetest.register_craft( {
1142                 output = "signs:sign_wall_white_red",
1143                 recipe = {
1144                                 { "dye:white", "dye:red", "dye:white" },
1145                                 { "", default_sign_metal, "" }
1146                 },
1147         })
1148
1149         minetest.register_craft( {
1150                 output = "signs:sign_wall_white_red 2",
1151                 recipe = {
1152                                 { "dye:white", "dye:red", "dye:white" },
1153                                 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1154                 },
1155         })
1156
1157         minetest.register_craft( {
1158                 output = "signs:sign_wall_white_black",
1159                 recipe = {
1160                                 { "dye:white", "dye:black", "dye:white" },
1161                                 { "", default_sign_metal, "" }
1162                 },
1163         })
1164
1165         minetest.register_craft( {
1166                 output = "signs:sign_wall_white_black 2",
1167                 recipe = {
1168                                 { "dye:white", "dye:black", "dye:white" },
1169                                 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1170                 },
1171         })
1172
1173         minetest.register_craft( {
1174                 output = "signs:sign_wall_orange",
1175                 recipe = {
1176                                 { "dye:orange", "dye:black", "dye:orange" },
1177                                 { "", default_sign_metal, "" }
1178                 },
1179         })
1180
1181         minetest.register_craft( {
1182                 output = "signs:sign_wall_orange 2",
1183                 recipe = {
1184                                 { "dye:orange", "dye:black", "dye:orange" },
1185                                 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1186                 },
1187         })
1188
1189         minetest.register_craft( {
1190                 output = "signs:sign_wall_blue",
1191                 recipe = {
1192                                 { "dye:blue", "dye:white", "dye:blue" },
1193                                 { "", default_sign_metal, "" }
1194                 },
1195         })
1196
1197         minetest.register_craft( {
1198                 output = "signs:sign_wall_blue 2",
1199                 recipe = {
1200                                 { "dye:blue", "dye:white", "dye:blue" },
1201                                 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1202                 },
1203         })
1204
1205         minetest.register_craft( {
1206                 output = "signs:sign_wall_brown",
1207                 recipe = {
1208                                 { "dye:brown", "dye:white", "dye:brown" },
1209                                 { "", default_sign_metal, "" }
1210                 },
1211         })
1212
1213         minetest.register_craft( {
1214                 output = "signs:sign_wall_brown 2",
1215                 recipe = {
1216                                 { "dye:brown", "dye:white", "dye:brown" },
1217                                 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1218                 },
1219         })
1220 end
1221
1222 if minetest.settings:get("log_mods") then
1223         minetest.log("action", S("[MOD] signs loaded"))
1224 end