]> git.lizzy.rs Git - signs_lib.git/blob - init.lua
5d44b2cf29c7a88f69346c9ba3609f3335ea6392
[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         local sign_info
571         local signnode = node or minetest.get_node(pos)
572         local signname = signnode.name
573         local textpos = minetest.registered_nodes[signname].textpos
574         if textpos then
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]
585                 else
586                         sign_info = signs_lib.metal_wall_sign_model.textpos[minetest.get_node(pos).param2 + 1]
587                 end
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]
590         end
591         if sign_info == nil then
592                 return
593         end
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)
598 end
599
600 -- What kind of sign do we need to place, anyway?
601
602 function signs_lib.determine_sign_type(itemstack, placer, pointed_thing, locked)
603         local name
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())
609                         return itemstack
610                 end
611         else
612                 name = minetest.get_node(pointed_thing.above).name
613                 local def = minetest.registered_nodes[name]
614                 if not def.buildable_to then
615                         return itemstack
616                 end
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())
620                         return itemstack
621                 end
622         end
623
624         local node=minetest.get_node(pointed_thing.under)
625
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)
630         else
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}
636
637                 local wdir = minetest.dir_to_wallmounted(dir)
638
639                 local placer_pos = placer:getpos()
640                 if placer_pos then
641                         dir = {
642                                 x = above.x - placer_pos.x,
643                                 y = above.y - placer_pos.y,
644                                 z = above.z - placer_pos.z
645                         }
646                 end
647
648                 local fdir = minetest.dir_to_facedir(dir)
649                 local pt_name = minetest.get_node(under).name
650                 local signname = itemstack:get_name()
651
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!
666                         if locked then
667                                 local meta = minetest.get_meta(above)
668                                 local owner = placer:get_player_name()
669                                 meta:set_string("owner", owner)
670                         end
671                 end
672
673                 if not signs_lib.expect_infinite_stacks then
674                         itemstack:take_item()
675                 end
676                 return itemstack
677         end
678 end
679
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())
684                 return
685         end
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"),
691                         lockstr,
692                         minetest.pos_to_string(pos)
693                 ))
694                 if lock then
695                         signs_lib.update_sign(pos, fields, sender:get_player_name())
696                 else
697                         signs_lib.update_sign(pos, fields)
698                 end
699         end
700 end
701
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,
708         paramtype = "light",
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,
714
715         on_place = function(itemstack, placer, pointed_thing)
716                 return signs_lib.determine_sign_type(itemstack, placer, pointed_thing)
717         end,
718         on_construct = function(pos)
719                 signs_lib.construct_sign(pos)
720         end,
721         on_destruct = function(pos)
722                 signs_lib.destruct_sign(pos)
723         end,
724         on_receive_fields = function(pos, formname, fields, sender)
725                 signs_lib.receive_fields(pos, formname, fields, sender)
726         end,
727         on_punch = function(pos, node, puncher)
728                 signs_lib.update_sign(pos,nil,nil,node)
729         end,
730         on_rotate = signs_lib.wallmounted_rotate
731 })
732
733 minetest.register_node(":signs:sign_yard", {
734         paramtype = "light",
735         sunlight_propagates = true,
736         paramtype2 = "facedir",
737         drawtype = "nodebox",
738         node_box = signs_lib.yard_sign_model.nodebox,
739         selection_box = {
740                 type = "fixed",
741                 fixed = {-0.4375, -0.5, -0.0625, 0.4375, 0.375, 0}
742         },
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},
745         drop = default_sign,
746
747         on_construct = function(pos)
748                 signs_lib.construct_sign(pos)
749         end,
750         on_destruct = function(pos)
751                 signs_lib.destruct_sign(pos)
752         end,
753         on_receive_fields = function(pos, formname, fields, sender)
754                 signs_lib.receive_fields(pos, formname, fields, sender)
755         end,
756         on_punch = function(pos, node, puncher)
757                 signs_lib.update_sign(pos,nil,nil,node)
758         end,
759         on_rotate = signs_lib.facedir_rotate_simple
760
761 })
762
763 minetest.register_node(":signs:sign_hanging", {
764         paramtype = "light",
765         sunlight_propagates = true,
766         paramtype2 = "facedir",
767         drawtype = "nodebox",
768         node_box = signs_lib.hanging_sign_model.nodebox,
769         selection_box = {
770                 type = "fixed",
771                 fixed = {-0.45, -0.275, -0.049, 0.45, 0.5, 0.049}
772         },
773         tiles = {
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"
780         },
781         groups = {choppy=2, dig_immediate=2},
782         drop = default_sign,
783
784         on_construct = function(pos)
785                 signs_lib.construct_sign(pos)
786         end,
787         on_destruct = function(pos)
788                 signs_lib.destruct_sign(pos)
789         end,
790         on_receive_fields = function(pos, formname, fields, sender)
791                 signs_lib.receive_fields(pos, formname, fields, sender)
792         end,
793         on_punch = function(pos, node, puncher)
794                 signs_lib.update_sign(pos,nil,nil,node)
795         end,
796         on_rotate = signs_lib.facedir_rotate_simple
797 })
798
799 minetest.register_node(":signs:sign_post", {
800         paramtype = "light",
801         sunlight_propagates = true,
802         paramtype2 = "facedir",
803         drawtype = "nodebox",
804         node_box = signs_lib.sign_post_model.nodebox,
805         tiles = {
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",
812         },
813         groups = {choppy=2, dig_immediate=2},
814         drop = {
815                 max_items = 2,
816                 items = {
817                         { items = { default_sign }},
818                         { items = { "default:fence_wood" }},
819                 },
820         },
821         on_rotate = signs_lib.facedir_rotate_simple
822 })
823
824 -- Locked wall sign
825
826 minetest.register_privilege("sign_editor", S("Can edit all locked signs"))
827
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,
834         paramtype = "light",
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)
842         end,
843         on_construct = function(pos)
844                 signs_lib.construct_sign(pos, true)
845         end,
846         on_destruct = function(pos)
847                 signs_lib.destruct_sign(pos)
848         end,
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
855                         return
856                 end
857                 signs_lib.receive_fields(pos, formname, fields, sender, true)
858         end,
859         on_punch = function(pos, node, puncher)
860                 signs_lib.update_sign(pos,nil,nil,node)
861         end,
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})
868         end,
869         on_rotate = signs_lib.wallmounted_rotate
870 })
871
872 -- default metal sign, if defined
873
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,
881                 paramtype = "light",
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,
887
888                 on_place = function(itemstack, placer, pointed_thing)
889                         return signs_lib.determine_sign_type(itemstack, placer, pointed_thing)
890                 end,
891                 on_construct = function(pos)
892                         signs_lib.construct_sign(pos)
893                 end,
894                 on_destruct = function(pos)
895                         signs_lib.destruct_sign(pos)
896                 end,
897                 on_receive_fields = function(pos, formname, fields, sender)
898                         signs_lib.receive_fields(pos, formname, fields, sender)
899                 end,
900                 on_punch = function(pos, node, puncher)
901                         signs_lib.update_sign(pos,nil,nil,node)
902                 end,
903                 on_rotate = signs_lib.wallmounted_rotate
904         })
905 end
906
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"},
919         }
920
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 = "",
927                         paramtype = "light",
928                         sunlight_propagates = true,
929                         paramtype2 = "facedir",
930                         drawtype = "nodebox",
931                         node_box = signs_lib.metal_wall_sign_model.nodebox,
932                         tiles = {
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"
939                         },
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)
944                         end,
945                         on_construct = function(pos)
946                                 signs_lib.construct_sign(pos)
947                         end,
948                         on_destruct = function(pos)
949                                 signs_lib.destruct_sign(pos)
950                         end,
951                         on_receive_fields = function(pos, formname, fields, sender)
952                                 signs_lib.receive_fields(pos, formname, fields, sender)
953                         end,
954                         on_punch = function(pos, node, puncher)
955                                 signs_lib.update_sign(pos,nil,nil,node)
956                         end,
957                         on_rotate = signs_lib.facedir_rotate
958                 })
959         end
960 end
961
962 local signs_text_on_activate
963
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)
972         end
973 end
974
975 minetest.register_entity(":signs:text", {
976         collisionbox = { 0, 0, 0, 0, 0, 0 },
977         visual = "upright_sprite",
978         textures = {},
979
980         on_activate = signs_text_on_activate,
981 })
982
983 -- And the good stuff here! :-)
984
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"))
990                 return
991         end
992         def = signs_lib.table_copy(def)
993         def_sign = signs_lib.table_copy(def_sign)
994         fences_with_sign[fencename] = fencewithsignname
995
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()
1003
1004                 if minetest.is_protected(pointed_thing.under, playername) then
1005                         minetest.record_protection_violation(pointed_thing.under, playername)
1006                         return itemstack
1007                 end
1008
1009                 if minetest.is_protected(pointed_thing.above, playername) then
1010                         minetest.record_protection_violation(pointed_thing.above, playername)
1011                         return itemstack
1012                 end
1013
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()
1020                         end
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()
1026                         end
1027                         placer:set_wielded_item(itemstack)
1028                 end
1029                 return itemstack
1030         end
1031         def_sign.on_construct = function(pos, ...)
1032                 signs_lib.construct_sign(pos)
1033         end
1034         def_sign.on_destruct = function(pos, ...)
1035                 signs_lib.destruct_sign(pos)
1036         end
1037         def_sign.on_receive_fields = function(pos, formname, fields, sender)
1038                 signs_lib.receive_fields(pos, formname, fields, sender)
1039         end
1040         def_sign.on_punch = function(pos, node, puncher, ...)
1041                 signs_lib.update_sign(pos,nil,nil,node)
1042         end
1043         local fencename = fencename
1044         def_sign.after_dig_node = function(pos, node, ...)
1045                 node.name = fencename
1046                 minetest.add_node(pos, node)
1047         end
1048         def_sign.on_rotate = signs_lib.facedir_rotate_simple
1049
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))
1055 end
1056
1057 build_char_db()
1058
1059 minetest.register_alias("homedecor:fence_wood_with_sign", "signs:sign_post")
1060 minetest.register_alias("sign_wall_locked", "locked_sign:sign_wall_locked")
1061
1062 signs_lib.register_fence_with_sign("default:fence_wood", "signs:sign_post")
1063
1064 -- restore signs' text after /clearobjects and the like, the next time
1065 -- a block is reloaded by the server.
1066
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)
1074         end
1075 })
1076
1077 -- locked sign
1078
1079 minetest.register_craft({
1080                 output = "locked_sign:sign_wall_locked",
1081                 recipe = {
1082                         {default_sign},
1083                         {"default:steel_ingot"},
1084         },
1085 })
1086
1087 -- craft recipes for the metal signs
1088 if enable_colored_metal_signs then
1089
1090         minetest.register_craft( {
1091                 output = "signs:sign_wall_green",
1092                 recipe = {
1093                                 { "dye:dark_green", "dye:white", "dye:dark_green" },
1094                                 { "", default_sign_metal, "" }
1095                 },
1096         })
1097
1098         minetest.register_craft( {
1099                 output = "signs:sign_wall_green 2",
1100                 recipe = {
1101                                 { "dye:dark_green", "dye:white", "dye:dark_green" },
1102                                 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1103                 },
1104         })
1105
1106         minetest.register_craft( {
1107                 output = "signs:sign_wall_yellow",
1108                 recipe = {
1109                                 { "dye:yellow", "dye:black", "dye:yellow" },
1110                                 { "", default_sign_metal, "" }
1111                 },
1112         })
1113
1114         minetest.register_craft( {
1115                 output = "signs:sign_wall_yellow 2",
1116                 recipe = {
1117                                 { "dye:yellow", "dye:black", "dye:yellow" },
1118                                 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1119                 },
1120         })
1121
1122         minetest.register_craft( {
1123                 output = "signs:sign_wall_red",
1124                 recipe = {
1125                                 { "dye:red", "dye:white", "dye:red" },
1126                                 { "", default_sign_metal, "" }
1127                 },
1128         })
1129
1130         minetest.register_craft( {
1131                 output = "signs:sign_wall_red 2",
1132                 recipe = {
1133                                 { "dye:red", "dye:white", "dye:red" },
1134                                 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1135                 },
1136         })
1137
1138         minetest.register_craft( {
1139                 output = "signs:sign_wall_white_red",
1140                 recipe = {
1141                                 { "dye:white", "dye:red", "dye:white" },
1142                                 { "", default_sign_metal, "" }
1143                 },
1144         })
1145
1146         minetest.register_craft( {
1147                 output = "signs:sign_wall_white_red 2",
1148                 recipe = {
1149                                 { "dye:white", "dye:red", "dye:white" },
1150                                 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1151                 },
1152         })
1153
1154         minetest.register_craft( {
1155                 output = "signs:sign_wall_white_black",
1156                 recipe = {
1157                                 { "dye:white", "dye:black", "dye:white" },
1158                                 { "", default_sign_metal, "" }
1159                 },
1160         })
1161
1162         minetest.register_craft( {
1163                 output = "signs:sign_wall_white_black 2",
1164                 recipe = {
1165                                 { "dye:white", "dye:black", "dye:white" },
1166                                 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1167                 },
1168         })
1169
1170         minetest.register_craft( {
1171                 output = "signs:sign_wall_orange",
1172                 recipe = {
1173                                 { "dye:orange", "dye:black", "dye:orange" },
1174                                 { "", default_sign_metal, "" }
1175                 },
1176         })
1177
1178         minetest.register_craft( {
1179                 output = "signs:sign_wall_orange 2",
1180                 recipe = {
1181                                 { "dye:orange", "dye:black", "dye:orange" },
1182                                 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1183                 },
1184         })
1185
1186         minetest.register_craft( {
1187                 output = "signs:sign_wall_blue",
1188                 recipe = {
1189                                 { "dye:blue", "dye:white", "dye:blue" },
1190                                 { "", default_sign_metal, "" }
1191                 },
1192         })
1193
1194         minetest.register_craft( {
1195                 output = "signs:sign_wall_blue 2",
1196                 recipe = {
1197                                 { "dye:blue", "dye:white", "dye:blue" },
1198                                 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1199                 },
1200         })
1201
1202         minetest.register_craft( {
1203                 output = "signs:sign_wall_brown",
1204                 recipe = {
1205                                 { "dye:brown", "dye:white", "dye:brown" },
1206                                 { "", default_sign_metal, "" }
1207                 },
1208         })
1209
1210         minetest.register_craft( {
1211                 output = "signs:sign_wall_brown 2",
1212                 recipe = {
1213                                 { "dye:brown", "dye:white", "dye:brown" },
1214                                 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1215                 },
1216         })
1217 end
1218
1219 if minetest.settings:get("log_mods") then
1220         minetest.log("action", S("[MOD] signs loaded"))
1221 end