]> git.lizzy.rs Git - signs_lib.git/blob - init.lua
add arrow symbols to fonts
[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                 word = string.gsub(word, "%^[12345678abcdefgh]", {
391                         ["^1"] = string.char(0x81),
392                         ["^2"] = string.char(0x82),
393                         ["^3"] = string.char(0x83),
394                         ["^4"] = string.char(0x84),
395                         ["^5"] = string.char(0x85),
396                         ["^6"] = string.char(0x86),
397                         ["^7"] = string.char(0x87),
398                         ["^8"] = string.char(0x88),
399                         ["^a"] = string.char(0x8a),
400                         ["^b"] = string.char(0x8b),
401                         ["^c"] = string.char(0x8c),
402                         ["^d"] = string.char(0x8d),
403                         ["^e"] = string.char(0x8e),
404                         ["^f"] = string.char(0x8f),
405                         ["^g"] = string.char(0x90),
406                         ["^h"] = string.char(0x91)
407                 })
408                 local word_l = #word
409                 local i = 1
410                 while i <= word_l  do
411                         local c = word:sub(i, i)
412                         if c == "#" then
413                                 local cc = tonumber(word:sub(i+1, i+1), 16)
414                                 if cc then
415                                         i = i + 1
416                                         cur_color = cc
417                                 end
418                         else
419                                 local w = charwidth[c]
420                                 if w then
421                                         width = width + w + 1
422                                         if width >= (SIGN_WIDTH - charwidth[" "]) then
423                                                 width = 0
424                                         else
425                                                 maxw = math_max(width, maxw)
426                                         end
427                                         if #chars < MAX_INPUT_CHARS then
428                                                 table.insert(chars, {
429                                                         off = ch_offs,
430                                                         tex = char_tex(font_name, c),
431                                                         col = ("%X"):format(cur_color),
432                                                 })
433                                         end
434                                         ch_offs = ch_offs + w
435                                 end
436                         end
437                         i = i + 1
438                 end
439                 width = width + charwidth[" "] + 1
440                 maxw = math_max(width, maxw)
441                 table.insert(words, { chars=chars, w=ch_offs })
442         end
443
444         -- Okay, we actually build the "line texture" here.
445
446         local texture = { }
447
448         local start_xpos = math.floor((SIGN_WIDTH - maxw) / 2)
449
450         local xpos = start_xpos
451         local ypos = (LINE_HEIGHT * lineno)
452
453         cur_color = nil
454
455         for word_i, word in ipairs(words) do
456                 local xoffs = (xpos - start_xpos)
457                 if (xoffs > 0) and ((xoffs + word.w) > maxw) then
458                         table.insert(texture, fill_line(xpos, ypos, maxw, "n"))
459                         xpos = start_xpos
460                         ypos = ypos + LINE_HEIGHT
461                         lineno = lineno + 1
462                         if lineno >= NUMBER_OF_LINES then break end
463                         table.insert(texture, fill_line(xpos, ypos, maxw, cur_color))
464                 end
465                 for ch_i, ch in ipairs(word.chars) do
466                         if ch.col ~= cur_color then
467                                 cur_color = ch.col
468                                 table.insert(texture, fill_line(xpos + ch.off, ypos, maxw, cur_color))
469                         end
470                         table.insert(texture, (":%d,%d=%s"):format(xpos + ch.off, ypos, ch.tex))
471                 end
472                 table.insert(
473                         texture, 
474                         (":%d,%d="):format(xpos + word.w, ypos) .. char_tex(font_name, " ")
475                 )
476                 xpos = xpos + word.w + charwidth[" "]
477                 if xpos >= (SIGN_WIDTH + charwidth[" "]) then break end
478         end
479
480         table.insert(texture, fill_line(xpos, ypos, maxw, "n"))
481         table.insert(texture, fill_line(start_xpos, ypos + LINE_HEIGHT, maxw, "n"))
482
483         return table.concat(texture), lineno
484 end
485
486 local function make_sign_texture(lines, pos)
487         local texture = { ("[combine:%dx%d"):format(SIGN_WIDTH, LINE_HEIGHT * NUMBER_OF_LINES) }
488         local lineno = 0
489         for i = 1, #lines do
490                 if lineno >= NUMBER_OF_LINES then break end
491                 local linetex, ln = make_line_texture(lines[i], lineno, pos)
492                 table.insert(texture, linetex)
493                 lineno = ln + 1
494         end
495         table.insert(texture, "^[makealpha:0,0,0")
496         return table.concat(texture, "")
497 end
498
499 local function set_obj_text(obj, text, new, pos)
500         local split = new and split_lines_and_words or split_lines_and_words_old
501         local text_ansi = Utf8ToAnsi(text)
502         local n = minetest.registered_nodes[minetest.get_node(pos).name]
503         local text_scale = (n and n.text_scale) or DEFAULT_TEXT_SCALE
504         obj:set_properties({
505                 textures={make_sign_texture(split(text_ansi), pos)},
506                 visual_size = text_scale,
507         })
508 end
509
510 signs_lib.construct_sign = function(pos, locked)
511         local meta = minetest.get_meta(pos)
512         meta:set_string(
513                 "formspec",
514                 "size[6,4]"..
515                 "textarea[0,-0.3;6.5,3;text;;${text}]"..
516                 "button_exit[2,3.4;2,1;ok;"..S("Write").."]"..
517                 "background[-0.5,-0.5;7,5;bg_signs_lib.jpg]")
518         meta:set_string("infotext", "")
519 end
520
521 signs_lib.destruct_sign = function(pos)
522         local objects = minetest.get_objects_inside_radius(pos, 0.5)
523         for _, v in ipairs(objects) do
524                 local e = v:get_luaentity()
525                 if e and e.name == "signs:text" then
526                         v:remove()
527                 end
528         end
529 end
530
531 local function make_infotext(text)
532         text = trim_input(text)
533         local lines = split_lines_and_words(text) or {}
534         local lines2 = { }
535         for _, line in ipairs(lines) do
536                 table.insert(lines2, (table.concat(line, " "):gsub("#[0-9a-fA-F]", ""):gsub("##", "#")))
537         end
538         return table.concat(lines2, "\n")
539 end
540
541 signs_lib.update_sign = function(pos, fields, owner, node)
542
543         -- First, check if the interact keyword from CWz's mod is being set,
544         -- or has been changed since the last restart...
545
546         local meta = minetest.get_meta(pos)
547         local stored_text = meta:get_string("text") or ""
548         current_keyword = rawget(_G, "mki_interact_keyword") or current_keyword
549
550         if fields then -- ...we're editing the sign.
551                 if fields.text and string.find(dump(fields.text), "@KEYWORD") then
552                         meta:set_string("keyword", current_keyword)
553                 else
554                         meta:set_string("keyword", nil)
555                 end
556         elseif string.find(dump(stored_text), "@KEYWORD") then -- we need to check if the password is being set/changed
557
558                 local stored_keyword = meta:get_string("keyword")
559                 if stored_keyword and stored_keyword ~= "" and stored_keyword ~= current_keyword then
560                         signs_lib.destruct_sign(pos)
561                         meta:set_string("keyword", current_keyword)
562                         local ownstr = ""
563                         if owner then ownstr = S("Locked sign, owned by @1\n", owner) end
564                         meta:set_string("infotext", ownstr..string.gsub(make_infotext(stored_text), "@KEYWORD", current_keyword).." ")
565                 end
566         end
567
568         local new
569
570         if fields then
571
572                 fields.text = trim_input(fields.text)
573
574                 local ownstr = ""
575                 if owner then ownstr = S("Locked sign, owned by @1\n", owner) end
576
577                 meta:set_string("infotext", ownstr..string.gsub(make_infotext(fields.text), "@KEYWORD", current_keyword).." ")
578                 meta:set_string("text", fields.text)
579                 
580                 meta:set_int("__signslib_new_format", 1)
581                 new = true
582         else
583                 new = (meta:get_int("__signslib_new_format") ~= 0)
584         end
585         signs_lib.destruct_sign(pos)
586         local text = meta:get_string("text")
587         if text == nil or text == "" then return end
588         local sign_info
589         local signnode = node or minetest.get_node(pos)
590         local signname = signnode.name
591         local textpos = minetest.registered_nodes[signname].textpos
592         if textpos then
593                 sign_info = textpos[minetest.get_node(pos).param2 + 1]
594         elseif signnode.name == "signs:sign_yard" then
595                 sign_info = signs_lib.yard_sign_model.textpos[minetest.get_node(pos).param2 + 1]
596         elseif signnode.name == "signs:sign_hanging" then
597                 sign_info = signs_lib.hanging_sign_model.textpos[minetest.get_node(pos).param2 + 1]
598         elseif string.find(signnode.name, "sign_wall") then
599                 if signnode.name == default_sign
600                   or signnode.name == default_sign_metal
601                   or signnode.name == "locked_sign:sign_wall_locked" then
602                         sign_info = signs_lib.regular_wall_sign_model.textpos[minetest.get_node(pos).param2 + 1]
603                 else
604                         sign_info = signs_lib.metal_wall_sign_model.textpos[minetest.get_node(pos).param2 + 1]
605                 end
606         else -- ...it must be a sign on a fence post.
607                 sign_info = signs_lib.sign_post_model.textpos[minetest.get_node(pos).param2 + 1]
608         end
609         if sign_info == nil then
610                 return
611         end
612         local text = minetest.add_entity({x = pos.x + sign_info.delta.x,
613                                                                                 y = pos.y + sign_info.delta.y,
614                                                                                 z = pos.z + sign_info.delta.z}, "signs:text")
615         text:setyaw(sign_info.yaw)
616 end
617
618 -- What kind of sign do we need to place, anyway?
619
620 function signs_lib.determine_sign_type(itemstack, placer, pointed_thing, locked)
621         local name
622         name = minetest.get_node(pointed_thing.under).name
623         if fences_with_sign[name] then
624                 if minetest.is_protected(pointed_thing.under, placer:get_player_name()) then
625                         minetest.record_protection_violation(pointed_thing.under,
626                                 placer:get_player_name())
627                         return itemstack
628                 end
629         else
630                 name = minetest.get_node(pointed_thing.above).name
631                 local def = minetest.registered_nodes[name]
632                 if not def.buildable_to then
633                         return itemstack
634                 end
635                 if minetest.is_protected(pointed_thing.above, placer:get_player_name()) then
636                         minetest.record_protection_violation(pointed_thing.above,
637                                 placer:get_player_name())
638                         return itemstack
639                 end
640         end
641
642         local node=minetest.get_node(pointed_thing.under)
643
644         if minetest.registered_nodes[node.name] and
645            minetest.registered_nodes[node.name].on_rightclick and
646            not placer:get_player_control().sneak then
647                 return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack, pointed_thing)
648         else
649                 local above = pointed_thing.above
650                 local under = pointed_thing.under
651                 local dir = {x = under.x - above.x,
652                                          y = under.y - above.y,
653                                          z = under.z - above.z}
654
655                 local wdir = minetest.dir_to_wallmounted(dir)
656
657                 local placer_pos = placer:getpos()
658                 if placer_pos then
659                         dir = {
660                                 x = above.x - placer_pos.x,
661                                 y = above.y - placer_pos.y,
662                                 z = above.z - placer_pos.z
663                         }
664                 end
665
666                 local fdir = minetest.dir_to_facedir(dir)
667                 local pt_name = minetest.get_node(under).name
668                 local signname = itemstack:get_name()
669
670                 if fences_with_sign[pt_name] and signname == default_sign then
671                         minetest.add_node(under, {name = fences_with_sign[pt_name], param2 = fdir})
672                 elseif wdir == 0 and signname == default_sign then
673                         minetest.add_node(above, {name = "signs:sign_hanging", param2 = fdir})
674                 elseif wdir == 1 and signname == default_sign then
675                         minetest.add_node(above, {name = "signs:sign_yard", param2 = fdir})
676                 elseif signname == default_sign_metal then
677                         minetest.add_node(above, {name = signname, param2 = wdir })
678                 elseif signname ~= default_sign
679                   and signname ~= default_sign_metal
680                   and signname ~= "locked_sign:sign_wall_locked" then -- it's a signs_lib colored metal wall sign.
681                         minetest.add_node(above, {name = signname, param2 = fdir})
682                 else -- it must be a default or locked wooden wall sign
683                         minetest.add_node(above, {name = signname, param2 = wdir }) -- note it's wallmounted here!
684                         if locked then
685                                 local meta = minetest.get_meta(above)
686                                 local owner = placer:get_player_name()
687                                 meta:set_string("owner", owner)
688                         end
689                 end
690
691                 if not signs_lib.expect_infinite_stacks then
692                         itemstack:take_item()
693                 end
694                 return itemstack
695         end
696 end
697
698 function signs_lib.receive_fields(pos, formname, fields, sender, lock)
699         if minetest.is_protected(pos, sender:get_player_name()) then
700                 minetest.record_protection_violation(pos,
701                         sender:get_player_name())
702                 return
703         end
704         local lockstr = lock and S("locked ") or ""
705         if fields and fields.text and fields.ok then
706                 minetest.log("action", S("@1 wrote \"@2\" to @3sign at @4",
707                         (sender:get_player_name() or ""),
708                         fields.text:gsub('\\', '\\\\'):gsub("\n", "\\n"),
709                         lockstr,
710                         minetest.pos_to_string(pos)
711                 ))
712                 if lock then
713                         signs_lib.update_sign(pos, fields, sender:get_player_name())
714                 else
715                         signs_lib.update_sign(pos, fields)
716                 end
717         end
718 end
719
720 minetest.register_node(":"..default_sign, {
721         description = S("Sign"),
722         inventory_image = default_sign_image,
723         wield_image = default_sign_image,
724         node_placement_prediction = "",
725         sunlight_propagates = true,
726         paramtype = "light",
727         paramtype2 = "wallmounted",
728         drawtype = "nodebox",
729         node_box = signs_lib.regular_wall_sign_model.nodebox,
730         tiles = {"signs_wall_sign.png"},
731         groups = sign_groups,
732
733         on_place = function(itemstack, placer, pointed_thing)
734                 return signs_lib.determine_sign_type(itemstack, placer, pointed_thing)
735         end,
736         on_construct = function(pos)
737                 signs_lib.construct_sign(pos)
738         end,
739         on_destruct = function(pos)
740                 signs_lib.destruct_sign(pos)
741         end,
742         on_receive_fields = function(pos, formname, fields, sender)
743                 signs_lib.receive_fields(pos, formname, fields, sender)
744         end,
745         on_punch = function(pos, node, puncher)
746                 signs_lib.update_sign(pos,nil,nil,node)
747         end,
748         on_rotate = signs_lib.wallmounted_rotate
749 })
750
751 minetest.register_node(":signs:sign_yard", {
752         paramtype = "light",
753         sunlight_propagates = true,
754         paramtype2 = "facedir",
755         drawtype = "nodebox",
756         node_box = signs_lib.yard_sign_model.nodebox,
757         selection_box = {
758                 type = "fixed",
759                 fixed = {-0.4375, -0.5, -0.0625, 0.4375, 0.375, 0}
760         },
761         tiles = {"signs_top.png", "signs_bottom.png", "signs_side.png", "signs_side.png", "signs_back.png", "signs_front.png"},
762         groups = {choppy=2, dig_immediate=2},
763         drop = default_sign,
764
765         on_construct = function(pos)
766                 signs_lib.construct_sign(pos)
767         end,
768         on_destruct = function(pos)
769                 signs_lib.destruct_sign(pos)
770         end,
771         on_receive_fields = function(pos, formname, fields, sender)
772                 signs_lib.receive_fields(pos, formname, fields, sender)
773         end,
774         on_punch = function(pos, node, puncher)
775                 signs_lib.update_sign(pos,nil,nil,node)
776         end,
777         on_rotate = signs_lib.facedir_rotate_simple
778
779 })
780
781 minetest.register_node(":signs:sign_hanging", {
782         paramtype = "light",
783         sunlight_propagates = true,
784         paramtype2 = "facedir",
785         drawtype = "nodebox",
786         node_box = signs_lib.hanging_sign_model.nodebox,
787         selection_box = {
788                 type = "fixed",
789                 fixed = {-0.45, -0.275, -0.049, 0.45, 0.5, 0.049}
790         },
791         tiles = {
792                 "signs_hanging_top.png",
793                 "signs_hanging_bottom.png",
794                 "signs_hanging_side.png",
795                 "signs_hanging_side.png",
796                 "signs_hanging_back.png",
797                 "signs_hanging_front.png"
798         },
799         groups = {choppy=2, dig_immediate=2},
800         drop = default_sign,
801
802         on_construct = function(pos)
803                 signs_lib.construct_sign(pos)
804         end,
805         on_destruct = function(pos)
806                 signs_lib.destruct_sign(pos)
807         end,
808         on_receive_fields = function(pos, formname, fields, sender)
809                 signs_lib.receive_fields(pos, formname, fields, sender)
810         end,
811         on_punch = function(pos, node, puncher)
812                 signs_lib.update_sign(pos,nil,nil,node)
813         end,
814         on_rotate = signs_lib.facedir_rotate_simple
815 })
816
817 minetest.register_node(":signs:sign_post", {
818         paramtype = "light",
819         sunlight_propagates = true,
820         paramtype2 = "facedir",
821         drawtype = "nodebox",
822         node_box = signs_lib.sign_post_model.nodebox,
823         tiles = {
824                 "signs_post_top.png",
825                 "signs_post_bottom.png",
826                 "signs_post_side.png",
827                 "signs_post_side.png",
828                 "signs_post_back.png",
829                 "signs_post_front.png",
830         },
831         groups = {choppy=2, dig_immediate=2},
832         drop = {
833                 max_items = 2,
834                 items = {
835                         { items = { default_sign }},
836                         { items = { "default:fence_wood" }},
837                 },
838         },
839         on_rotate = signs_lib.facedir_rotate_simple
840 })
841
842 -- Locked wall sign
843
844 minetest.register_privilege("sign_editor", S("Can edit all locked signs"))
845
846 minetest.register_node(":locked_sign:sign_wall_locked", {
847         description = S("Locked Sign"),
848         inventory_image = "signs_locked_inv.png",
849         wield_image = "signs_locked_inv.png",
850         node_placement_prediction = "",
851         sunlight_propagates = true,
852         paramtype = "light",
853         paramtype2 = "wallmounted",
854         drawtype = "nodebox",
855         node_box = signs_lib.regular_wall_sign_model.nodebox,
856         tiles = { "signs_wall_sign_locked.png" },
857         groups = sign_groups,
858         on_place = function(itemstack, placer, pointed_thing)
859                 return signs_lib.determine_sign_type(itemstack, placer, pointed_thing, true)
860         end,
861         on_construct = function(pos)
862                 signs_lib.construct_sign(pos, true)
863         end,
864         on_destruct = function(pos)
865                 signs_lib.destruct_sign(pos)
866         end,
867         on_receive_fields = function(pos, formname, fields, sender)
868                 local meta = minetest.get_meta(pos)
869                 local owner = meta:get_string("owner")
870                 local pname = sender:get_player_name() or ""
871                 if pname ~= owner and pname ~= minetest.settings:get("name")
872                   and not minetest.check_player_privs(pname, {sign_editor=true}) then
873                         return
874                 end
875                 signs_lib.receive_fields(pos, formname, fields, sender, true)
876         end,
877         on_punch = function(pos, node, puncher)
878                 signs_lib.update_sign(pos,nil,nil,node)
879         end,
880         can_dig = function(pos, player)
881                 local meta = minetest.get_meta(pos)
882                 local owner = meta:get_string("owner")
883                 local pname = player:get_player_name()
884                 return pname == owner or pname == minetest.settings:get("name")
885                         or minetest.check_player_privs(pname, {sign_editor=true})
886         end,
887         on_rotate = signs_lib.wallmounted_rotate
888 })
889
890 -- default metal sign, if defined
891
892 if minetest.registered_nodes["default:sign_wall_steel"] then
893         minetest.register_node(":"..default_sign_metal, {
894                 description = S("Sign"),
895                 inventory_image = default_sign_metal_image,
896                 wield_image = default_sign_metal_image,
897                 node_placement_prediction = "",
898                 sunlight_propagates = true,
899                 paramtype = "light",
900                 paramtype2 = "wallmounted",
901                 drawtype = "nodebox",
902                 node_box = signs_lib.regular_wall_sign_model.nodebox,
903                 tiles = {"signs_wall_sign_metal.png"},
904                 groups = sign_groups,
905
906                 on_place = function(itemstack, placer, pointed_thing)
907                         return signs_lib.determine_sign_type(itemstack, placer, pointed_thing)
908                 end,
909                 on_construct = function(pos)
910                         signs_lib.construct_sign(pos)
911                 end,
912                 on_destruct = function(pos)
913                         signs_lib.destruct_sign(pos)
914                 end,
915                 on_receive_fields = function(pos, formname, fields, sender)
916                         signs_lib.receive_fields(pos, formname, fields, sender)
917                 end,
918                 on_punch = function(pos, node, puncher)
919                         signs_lib.update_sign(pos,nil,nil,node)
920                 end,
921                 on_rotate = signs_lib.wallmounted_rotate
922         })
923 end
924
925 -- metal, colored signs
926 if enable_colored_metal_signs then
927         -- array : color, translated color, default text color
928         local sign_colors = {
929                 {"green",        S("green"),       "f"},
930                 {"yellow",       S("yellow"),      "0"},
931                 {"red",          S("red"),         "f"},
932                 {"white_red",    S("white_red"),   "4"},
933                 {"white_black",  S("white_black"), "0"},
934                 {"orange",       S("orange"),      "0"},
935                 {"blue",         S("blue"),        "f"},
936                 {"brown",        S("brown"),       "f"},
937         }
938
939         for i, color in ipairs(sign_colors) do
940                 minetest.register_node(":signs:sign_wall_"..color[1], {
941                         description = S("Sign (@1, metal)", color[2]),
942                         inventory_image = "signs_"..color[1].."_inv.png",
943                         wield_image = "signs_"..color[1].."_inv.png",
944                         node_placement_prediction = "",
945                         paramtype = "light",
946                         sunlight_propagates = true,
947                         paramtype2 = "facedir",
948                         drawtype = "nodebox",
949                         node_box = signs_lib.metal_wall_sign_model.nodebox,
950                         tiles = {
951                                 "signs_metal_tb.png",
952                                 "signs_metal_tb.png",
953                                 "signs_metal_sides.png",
954                                 "signs_metal_sides.png",
955                                 "signs_metal_back.png",
956                                 "signs_"..color[1].."_front.png"
957                         },
958                         default_color = color[3],
959                         groups = sign_groups,
960                         on_place = function(itemstack, placer, pointed_thing)
961                                 return signs_lib.determine_sign_type(itemstack, placer, pointed_thing)
962                         end,
963                         on_construct = function(pos)
964                                 signs_lib.construct_sign(pos)
965                         end,
966                         on_destruct = function(pos)
967                                 signs_lib.destruct_sign(pos)
968                         end,
969                         on_receive_fields = function(pos, formname, fields, sender)
970                                 signs_lib.receive_fields(pos, formname, fields, sender)
971                         end,
972                         on_punch = function(pos, node, puncher)
973                                 signs_lib.update_sign(pos,nil,nil,node)
974                         end,
975                         on_rotate = signs_lib.facedir_rotate
976                 })
977         end
978 end
979
980 local signs_text_on_activate
981
982 signs_text_on_activate = function(self)
983         local pos = self.object:getpos()
984         local meta = minetest.get_meta(pos)
985         local text = meta:get_string("text")
986         local new = (meta:get_int("__signslib_new_format") ~= 0)
987         if text and minetest.registered_nodes[minetest.get_node(pos).name] then
988                 text = trim_input(text)
989                 set_obj_text(self.object, text, new, pos)
990         end
991 end
992
993 minetest.register_entity(":signs:text", {
994         collisionbox = { 0, 0, 0, 0, 0, 0 },
995         visual = "upright_sprite",
996         textures = {},
997
998         on_activate = signs_text_on_activate,
999 })
1000
1001 -- And the good stuff here! :-)
1002
1003 function signs_lib.register_fence_with_sign(fencename, fencewithsignname)
1004         local def = minetest.registered_nodes[fencename]
1005         local def_sign = minetest.registered_nodes[fencewithsignname]
1006         if not (def and def_sign) then
1007                 minetest.log("warning", "[signs_lib] "..S("Attempt to register unknown node as fence"))
1008                 return
1009         end
1010         def = signs_lib.table_copy(def)
1011         def_sign = signs_lib.table_copy(def_sign)
1012         fences_with_sign[fencename] = fencewithsignname
1013
1014         def_sign.on_place = function(itemstack, placer, pointed_thing, ...)
1015                 local node_above = minetest.get_node_or_nil(pointed_thing.above)
1016                 local node_under = minetest.get_node_or_nil(pointed_thing.under)
1017                 local def_above = node_above and minetest.registered_nodes[node_above.name]
1018                 local def_under = node_under and minetest.registered_nodes[node_under.name]
1019                 local fdir = minetest.dir_to_facedir(placer:get_look_dir())
1020                 local playername = placer:get_player_name()
1021
1022                 if minetest.is_protected(pointed_thing.under, playername) then
1023                         minetest.record_protection_violation(pointed_thing.under, playername)
1024                         return itemstack
1025                 end
1026
1027                 if minetest.is_protected(pointed_thing.above, playername) then
1028                         minetest.record_protection_violation(pointed_thing.above, playername)
1029                         return itemstack
1030                 end
1031
1032                 if def_under and def_under.on_rightclick then
1033                         return def_under.on_rightclick(pointed_thing.under, node_under, placer, itemstack, pointed_thing) or itemstack
1034                 elseif def_under and def_under.buildable_to then
1035                         minetest.add_node(pointed_thing.under, {name = fencename, param2 = fdir})
1036                         if not signs_lib.expect_infinite_stacks then
1037                                 itemstack:take_item()
1038                         end
1039                         placer:set_wielded_item(itemstack)
1040                 elseif def_above and def_above.buildable_to then
1041                         minetest.add_node(pointed_thing.above, {name = fencename, param2 = fdir})
1042                         if not signs_lib.expect_infinite_stacks then
1043                                 itemstack:take_item()
1044                         end
1045                         placer:set_wielded_item(itemstack)
1046                 end
1047                 return itemstack
1048         end
1049         def_sign.on_construct = function(pos, ...)
1050                 signs_lib.construct_sign(pos)
1051         end
1052         def_sign.on_destruct = function(pos, ...)
1053                 signs_lib.destruct_sign(pos)
1054         end
1055         def_sign.on_receive_fields = function(pos, formname, fields, sender)
1056                 signs_lib.receive_fields(pos, formname, fields, sender)
1057         end
1058         def_sign.on_punch = function(pos, node, puncher, ...)
1059                 signs_lib.update_sign(pos,nil,nil,node)
1060         end
1061         local fencename = fencename
1062         def_sign.after_dig_node = function(pos, node, ...)
1063                 node.name = fencename
1064                 minetest.add_node(pos, node)
1065         end
1066         def_sign.on_rotate = signs_lib.facedir_rotate_simple
1067
1068         def_sign.drop = default_sign
1069         minetest.register_node(":"..fencename, def)
1070         minetest.register_node(":"..fencewithsignname, def_sign)
1071         table.insert(signs_lib.sign_node_list, fencewithsignname)
1072         minetest.log("verbose", S("Registered @1 and @2", fencename, fencewithsignname))
1073 end
1074
1075 build_char_db()
1076
1077 minetest.register_alias("homedecor:fence_wood_with_sign", "signs:sign_post")
1078 minetest.register_alias("sign_wall_locked", "locked_sign:sign_wall_locked")
1079
1080 signs_lib.register_fence_with_sign("default:fence_wood", "signs:sign_post")
1081
1082 -- restore signs' text after /clearobjects and the like, the next time
1083 -- a block is reloaded by the server.
1084
1085 minetest.register_lbm({
1086         nodenames = signs_lib.sign_node_list,
1087         name = "signs_lib:restore_sign_text",
1088         label = "Restore sign text",
1089         run_at_every_load = true,
1090         action = function(pos, node)
1091                 signs_lib.update_sign(pos,nil,nil,node)
1092         end
1093 })
1094
1095 -- locked sign
1096
1097 minetest.register_craft({
1098                 output = "locked_sign:sign_wall_locked",
1099                 recipe = {
1100                         {default_sign},
1101                         {"default:steel_ingot"},
1102         },
1103 })
1104
1105 -- craft recipes for the metal signs
1106 if enable_colored_metal_signs then
1107
1108         minetest.register_craft( {
1109                 output = "signs:sign_wall_green",
1110                 recipe = {
1111                                 { "dye:dark_green", "dye:white", "dye:dark_green" },
1112                                 { "", default_sign_metal, "" }
1113                 },
1114         })
1115
1116         minetest.register_craft( {
1117                 output = "signs:sign_wall_green 2",
1118                 recipe = {
1119                                 { "dye:dark_green", "dye:white", "dye:dark_green" },
1120                                 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1121                 },
1122         })
1123
1124         minetest.register_craft( {
1125                 output = "signs:sign_wall_yellow",
1126                 recipe = {
1127                                 { "dye:yellow", "dye:black", "dye:yellow" },
1128                                 { "", default_sign_metal, "" }
1129                 },
1130         })
1131
1132         minetest.register_craft( {
1133                 output = "signs:sign_wall_yellow 2",
1134                 recipe = {
1135                                 { "dye:yellow", "dye:black", "dye:yellow" },
1136                                 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1137                 },
1138         })
1139
1140         minetest.register_craft( {
1141                 output = "signs:sign_wall_red",
1142                 recipe = {
1143                                 { "dye:red", "dye:white", "dye:red" },
1144                                 { "", default_sign_metal, "" }
1145                 },
1146         })
1147
1148         minetest.register_craft( {
1149                 output = "signs:sign_wall_red 2",
1150                 recipe = {
1151                                 { "dye:red", "dye:white", "dye:red" },
1152                                 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1153                 },
1154         })
1155
1156         minetest.register_craft( {
1157                 output = "signs:sign_wall_white_red",
1158                 recipe = {
1159                                 { "dye:white", "dye:red", "dye:white" },
1160                                 { "", default_sign_metal, "" }
1161                 },
1162         })
1163
1164         minetest.register_craft( {
1165                 output = "signs:sign_wall_white_red 2",
1166                 recipe = {
1167                                 { "dye:white", "dye:red", "dye:white" },
1168                                 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1169                 },
1170         })
1171
1172         minetest.register_craft( {
1173                 output = "signs:sign_wall_white_black",
1174                 recipe = {
1175                                 { "dye:white", "dye:black", "dye:white" },
1176                                 { "", default_sign_metal, "" }
1177                 },
1178         })
1179
1180         minetest.register_craft( {
1181                 output = "signs:sign_wall_white_black 2",
1182                 recipe = {
1183                                 { "dye:white", "dye:black", "dye:white" },
1184                                 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1185                 },
1186         })
1187
1188         minetest.register_craft( {
1189                 output = "signs:sign_wall_orange",
1190                 recipe = {
1191                                 { "dye:orange", "dye:black", "dye:orange" },
1192                                 { "", default_sign_metal, "" }
1193                 },
1194         })
1195
1196         minetest.register_craft( {
1197                 output = "signs:sign_wall_orange 2",
1198                 recipe = {
1199                                 { "dye:orange", "dye:black", "dye:orange" },
1200                                 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1201                 },
1202         })
1203
1204         minetest.register_craft( {
1205                 output = "signs:sign_wall_blue",
1206                 recipe = {
1207                                 { "dye:blue", "dye:white", "dye:blue" },
1208                                 { "", default_sign_metal, "" }
1209                 },
1210         })
1211
1212         minetest.register_craft( {
1213                 output = "signs:sign_wall_blue 2",
1214                 recipe = {
1215                                 { "dye:blue", "dye:white", "dye:blue" },
1216                                 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1217                 },
1218         })
1219
1220         minetest.register_craft( {
1221                 output = "signs:sign_wall_brown",
1222                 recipe = {
1223                                 { "dye:brown", "dye:white", "dye:brown" },
1224                                 { "", default_sign_metal, "" }
1225                 },
1226         })
1227
1228         minetest.register_craft( {
1229                 output = "signs:sign_wall_brown 2",
1230                 recipe = {
1231                                 { "dye:brown", "dye:white", "dye:brown" },
1232                                 { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1233                 },
1234         })
1235 end
1236
1237 if minetest.settings:get("log_mods") then
1238         minetest.log("action", S("[MOD] signs loaded"))
1239 end