]> git.lizzy.rs Git - signs_lib.git/blob - init.lua
First commit.
[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
13 -- CWz's keyword interact mod uses this setting.
14 local current_keyword = minetest.setting_get("interact_keyword") or "iaccept"
15
16 signs_lib = {}
17 screwdriver = screwdriver or {}
18
19 signs_lib.wallmounted_rotate = function(pos, node, user, mode, new_param2)
20         if mode ~= screwdriver.ROTATE_AXIS then return false end
21         minetest.swap_node(pos, {name = node.name, param2 = (node.param2 + 1) % 6})
22         for _, v in ipairs(minetest.get_objects_inside_radius(pos, 0.5)) do
23                 local e = v:get_luaentity()
24                 if e and e.name == "signs:text" then
25                         v:remove()
26                 end
27         end
28         signs_lib.update_sign(pos)
29         return true
30 end
31
32 signs_lib.modpath = minetest.get_modpath("signs_lib")
33
34 signs_lib.regular_wall_sign_model = {
35         nodebox = {
36                 type = "wallmounted",
37                 wall_side =   { -0.5,    -0.25,   -0.4375, -0.4375,  0.375,  0.4375 },
38                 wall_bottom = { -0.4375, -0.5,    -0.25,    0.4375, -0.4375, 0.375 },
39                 wall_top =    { -0.4375,  0.4375, -0.375,   0.4375,  0.5,    0.25 }
40         },
41         textpos = {
42                 nil,
43                 nil,
44                 {delta = {x =  0.43,  y = 0.07, z =  0     }, yaw = math.pi / -2},
45                 {delta = {x = -0.43,  y = 0.07, z =  0     }, yaw = math.pi / 2},
46                 {delta = {x =  0,     y = 0.07, z =  0.43  }, yaw = 0},
47                 {delta = {x =  0,     y = 0.07, z = -0.43  }, yaw = math.pi},
48         }
49 }
50
51 signs_lib.metal_wall_sign_model = {
52         nodebox = {
53                 type = "fixed",
54                 fixed = {-0.4375, -0.25, 0.4375, 0.4375, 0.375, 0.5}
55         },
56         textpos = {
57                 {delta = {x =  0,     y = 0.07, z =  0.43  }, yaw = 0},
58                 {delta = {x =  0.43,  y = 0.07, z =  0     }, yaw = math.pi / -2},
59                 {delta = {x =  0,     y = 0.07, z = -0.43  }, yaw = math.pi},
60                 {delta = {x = -0.43,  y = 0.07, z =  0     }, yaw = math.pi / 2},
61         }
62 }
63
64 signs_lib.yard_sign_model = {
65         nodebox = {
66                 type = "fixed",
67                 fixed = {
68                                 {-0.4375, -0.25, -0.0625, 0.4375, 0.375, 0},
69                                 {-0.0625, -0.5, -0.0625, 0.0625, -0.1875, 0},
70                 }
71         },
72         textpos = {
73                 {delta = {x =  0,      y = 0.07, z = -0.068}, yaw = 0},
74                 {delta = {x = -0.068,  y = 0.07, z =  0    }, yaw = math.pi / -2},
75                 {delta = {x =  0,      y = 0.07, z =  0.068}, yaw = math.pi},
76                 {delta = {x =  0.068,  y = 0.07, z =  0    }, yaw = math.pi / 2},
77         }
78 }
79
80 signs_lib.hanging_sign_model = {
81         nodebox = {
82                 type = "fixed",
83                 fixed = {
84                                 {-0.4375, -0.3125, -0.0625, 0.4375, 0.3125, 0},
85                                 {-0.4375, 0.25, -0.03125, 0.4375, 0.5, -0.03125},
86                 }
87         },
88         textpos = {
89                 {delta = {x =  0,      y = -0.02, z = -0.063}, yaw = 0},
90                 {delta = {x = -0.063,  y = -0.02, z =  0    }, yaw = math.pi / -2},
91                 {delta = {x =  0,      y = -0.02, z =  0.063}, yaw = math.pi},
92                 {delta = {x =  0.063,  y = -0.02, z =  0    }, yaw = math.pi / 2},
93         }
94 }
95
96 signs_lib.sign_post_model = {
97         nodebox = {
98                 type = "fixed",
99                 fixed = {
100                                 {-0.4375, -0.25, -0.1875, 0.4375, 0.375, -0.125},
101                                 {-0.125, -0.5, -0.125, 0.125, 0.5, 0.125},
102                 }
103         },
104         textpos = {
105                 {delta = {x = 0,      y = 0.07, z = -0.188}, yaw = 0},
106                 {delta = {x = -0.188, y = 0.07, z = 0     }, yaw = math.pi / -2},
107                 {delta = {x = 0,      y = 0.07, z = 0.188 }, yaw = math.pi},
108                 {delta = {x = 0.188,  y = 0.07, z = 0     }, yaw = math.pi / 2},
109         }
110 }
111
112 -- Boilerplate to support localized strings if intllib mod is installed.
113 local S = rawget(_G, "intllib") and intllib.Getter() or function(s) return s end
114 signs_lib.gettext = S
115
116 -- the list of standard sign nodes
117
118 signs_lib.sign_node_list = {
119                 "default:sign_wall",
120                 "signs:sign_yard",
121                 "signs:sign_hanging",
122                 "signs:sign_wall_green",
123                 "signs:sign_wall_yellow",
124                 "signs:sign_wall_red",
125                 "signs:sign_wall_white_red",
126                 "signs:sign_wall_white_black",
127                 "signs:sign_wall_orange",
128                 "signs:sign_wall_blue",
129                 "signs:sign_wall_brown",
130                 "locked_sign:sign_wall_locked"
131 }
132
133 --table copy
134
135 function signs_lib.table_copy(t)
136     local nt = { };
137     for k, v in pairs(t) do
138         if type(v) == "table" then
139             nt[k] = signs_lib.table_copy(v)
140         else
141             nt[k] = v
142         end
143     end
144     return nt
145 end
146
147 -- infinite stacks
148
149 if minetest.get_modpath("unified_inventory") or not minetest.setting_getbool("creative_mode") then
150         signs_lib.expect_infinite_stacks = false
151 else
152         signs_lib.expect_infinite_stacks = true
153 end
154
155 -- CONSTANTS
156
157 local MP = minetest.get_modpath("signs_lib")
158
159 -- Used by `build_char_db' to locate the file.
160 local FONT_FMT = "%s/hdf_%02x.png"
161
162 -- Simple texture name for building text texture.
163 local FONT_FMT_SIMPLE = "hdf_%02x.png"
164
165 -- Path to the textures.
166 local TP = MP.."/textures"
167
168 local TEXT_SCALE = {x=0.8, y=0.5}
169
170 -- Lots of overkill here. KISS advocates, go away, shoo! ;) -- kaeza
171
172 local PNG_HDR = string.char(0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A)
173
174 -- Read the image size from a PNG file.
175 -- Returns image_w, image_h.
176 -- Only the LSB is read from each field!
177 local function read_image_size(filename)
178         local f = io.open(filename, "rb")
179         f:seek("set", 0x0)
180         local hdr = f:read(8)
181         if hdr ~= PNG_HDR then
182                 f:close()
183                 return
184         end
185         f:seek("set", 0x13)
186         local ws = f:read(1)
187         f:seek("set", 0x17)
188         local hs = f:read(1)
189         f:close()
190         return ws:byte(), hs:byte()
191 end
192
193 -- Set by build_char_db()
194 local LINE_HEIGHT
195 local SIGN_WIDTH
196 local COLORBGW, COLORBGH
197
198 -- Size of the canvas, in characters.
199 -- Please note that CHARS_PER_LINE is multiplied by the average character
200 -- width to get the total width of the canvas, so for proportional fonts,
201 -- either more or fewer characters may fit on a line.
202 local CHARS_PER_LINE = 30
203 local NUMBER_OF_LINES = 6
204
205 -- 6 rows, max 80 chars per, plus a bit of fudge to
206 -- avoid excess trimming (e.g. due to color codes)
207
208 local MAX_INPUT_CHARS = 600
209
210 -- This holds the individual character widths.
211 -- Indexed by the actual character (e.g. charwidth["A"])
212 local charwidth
213
214 -- helper functions to trim sign text input/output
215
216 local function trim_input(text)
217         return text:sub(1, math.min(MAX_INPUT_CHARS, text:len()))
218 end
219
220 local function build_char_db()
221
222         charwidth = { }
223
224         -- To calculate average char width.
225         local total_width = 0
226         local char_count = 0
227
228         for c = 32, 126 do
229                 local w, h = read_image_size(FONT_FMT:format(TP, c))
230                 if w and h then
231                         local ch = string.char(c)
232                         charwidth[ch] = w
233                         total_width = total_width + w
234                         char_count = char_count + 1
235                 end
236         end
237
238         COLORBGW, COLORBGH = read_image_size(TP.."/slc_n.png")
239         assert(COLORBGW and COLORBGH, "error reading bg dimensions")
240         LINE_HEIGHT = COLORBGH
241
242         -- XXX: Is there a better way to calc this?
243         SIGN_WIDTH = math.floor((total_width / char_count) * CHARS_PER_LINE)
244
245 end
246
247 local sign_groups = {choppy=2, dig_immediate=2}
248
249 local fences_with_sign = { }
250
251 -- some local helper functions
252
253 local function split_lines_and_words_old(text)
254         local lines = { }
255         local line = { }
256         if not text then return end
257         for word in text:gmatch("%S+") do
258                 if word == "|" then
259                         table.insert(lines, line)
260                         if #lines >= NUMBER_OF_LINES then break end
261                         line = { }
262                 elseif word == "\\|" then
263                         table.insert(line, "|")
264                 else
265                         table.insert(line, word)
266                 end
267         end
268         table.insert(lines, line)
269         return lines
270 end
271
272 local function split_lines_and_words(text)
273         if not text then return end
274         text = string.gsub(text, "@KEYWORD", current_keyword)
275         local lines = { }
276         for _, line in ipairs(text:split("\n")) do
277                 table.insert(lines, line:split(" "))
278         end
279         return lines
280 end
281
282 local math_max = math.max
283
284 local function fill_line(x, y, w, c)
285         c = c or "0"
286         local tex = { }
287         for xx = 0, math.max(0, w), COLORBGW do
288                 table.insert(tex, (":%d,%d=slc_%s.png"):format(x + xx, y, c))
289         end
290         return table.concat(tex)
291 end
292
293 local function make_line_texture(line, lineno)
294
295         local width = 0
296         local maxw = 0
297
298         local words = { }
299
300         local cur_color = 0
301
302         -- We check which chars are available here.
303         for word_i, word in ipairs(line) do
304                 local chars = { }
305                 local ch_offs = 0
306                 local word_l = #word
307                 local i = 1
308                 while i <= word_l  do
309                         local c = word:sub(i, i)
310                         if c == "#" then
311                                 local cc = tonumber(word:sub(i+1, i+1), 16)
312                                 if cc then
313                                         i = i + 1
314                                         cur_color = cc
315                                 end
316                         else
317                                 local w = charwidth[c]
318                                 if w then
319                                         width = width + w + 1
320                                         if width >= (SIGN_WIDTH - charwidth[" "]) then
321                                                 width = 0
322                                         else
323                                                 maxw = math_max(width, maxw)
324                                         end
325                                         if #chars < MAX_INPUT_CHARS then
326                                                 table.insert(chars, {
327                                                         off=ch_offs,
328                                                         tex=FONT_FMT_SIMPLE:format(c:byte()),
329                                                         col=("%X"):format(cur_color),
330                                                 })
331                                         end
332                                         ch_offs = ch_offs + w
333                                 end
334                         end
335                         i = i + 1
336                 end
337                 width = width + charwidth[" "] + 1
338                 maxw = math_max(width, maxw)
339                 table.insert(words, { chars=chars, w=ch_offs })
340         end
341
342         -- Okay, we actually build the "line texture" here.
343
344         local texture = { }
345
346         local start_xpos = math.floor((SIGN_WIDTH - maxw) / 2)
347
348         local xpos = start_xpos
349         local ypos = (LINE_HEIGHT * lineno)
350
351         cur_color = nil
352
353         for word_i, word in ipairs(words) do
354                 local xoffs = (xpos - start_xpos)
355                 if (xoffs > 0) and ((xoffs + word.w) > maxw) then
356                         table.insert(texture, fill_line(xpos, ypos, maxw, "n"))
357                         xpos = start_xpos
358                         ypos = ypos + LINE_HEIGHT
359                         lineno = lineno + 1
360                         if lineno >= NUMBER_OF_LINES then break end
361                         table.insert(texture, fill_line(xpos, ypos, maxw, cur_color))
362                 end
363                 for ch_i, ch in ipairs(word.chars) do
364                         if ch.col ~= cur_color then
365                                 cur_color = ch.col
366                                 table.insert(texture, fill_line(xpos + ch.off, ypos, maxw, cur_color))
367                         end
368                         table.insert(texture, (":%d,%d=%s"):format(xpos + ch.off, ypos, ch.tex))
369                 end
370                 table.insert(texture, (":%d,%d=hdf_20.png"):format(xpos + word.w, ypos))
371                 xpos = xpos + word.w + charwidth[" "]
372                 if xpos >= (SIGN_WIDTH + charwidth[" "]) then break end
373         end
374
375         table.insert(texture, fill_line(xpos, ypos, maxw, "n"))
376         table.insert(texture, fill_line(start_xpos, ypos + LINE_HEIGHT, maxw, "n"))
377
378         return table.concat(texture), lineno
379 end
380
381 local function make_sign_texture(lines)
382         local texture = { ("[combine:%dx%d"):format(SIGN_WIDTH, LINE_HEIGHT * NUMBER_OF_LINES) }
383         local lineno = 0
384         for i = 1, #lines do
385                 if lineno >= NUMBER_OF_LINES then break end
386                 local linetex, ln = make_line_texture(lines[i], lineno)
387                 table.insert(texture, linetex)
388                 lineno = ln + 1
389         end
390         table.insert(texture, "^[makealpha:0,0,0")
391         return table.concat(texture, "")
392 end
393
394 local function set_obj_text(obj, text, new)
395         local split = new and split_lines_and_words or split_lines_and_words_old
396         obj:set_properties({
397                 textures={make_sign_texture(split(text))},
398                 visual_size = TEXT_SCALE,
399         })
400 end
401
402 signs_lib.construct_sign = function(pos, locked)
403     local meta = minetest.get_meta(pos)
404         meta:set_string(
405                 "formspec",
406                 "size[6,4]"..
407                 "textarea[0,-0.3;6.5,3;text;;${text}]"..
408                 "button_exit[2,3.4;2,1;ok;Write]"..
409                 "background[-0.5,-0.5;7,5;bg_signs_lib.jpg]")
410         meta:set_string("infotext", "")
411 end
412
413 signs_lib.destruct_sign = function(pos)
414     local objects = minetest.get_objects_inside_radius(pos, 0.5)
415     for _, v in ipairs(objects) do
416                 local e = v:get_luaentity()
417         if e and e.name == "signs:text" then
418             v:remove()
419         end
420     end
421 end
422
423 local function make_infotext(text)
424         text = trim_input(text)
425         local lines = split_lines_and_words(text) or {}
426         local lines2 = { }
427         for _, line in ipairs(lines) do
428                 table.insert(lines2, (table.concat(line, " "):gsub("#[0-9a-fA-F]", ""):gsub("##", "#")))
429         end
430         return table.concat(lines2, "\n")
431 end
432
433 signs_lib.update_sign = function(pos, fields, owner)
434
435         -- First, check if the interact keyword from CWz's mod is being set,
436         -- or has been changed since the last restart...
437
438         local meta = minetest.get_meta(pos)
439         local stored_text = meta:get_string("text") or ""
440         current_keyword = rawget(_G, "mki_interact_keyword") or current_keyword
441
442         if fields then -- ...we're editing the sign.
443                 if fields.text and string.find(dump(fields.text), "@KEYWORD") then
444                         meta:set_string("keyword", current_keyword)
445                 else
446                         meta:set_string("keyword", nil)
447                 end
448         elseif string.find(dump(stored_text), "@KEYWORD") then -- we need to check if the password is being set/changed
449
450                 local stored_keyword = meta:get_string("keyword")
451                 if stored_keyword and stored_keyword ~= "" and stored_keyword ~= current_keyword then
452                         signs_lib.destruct_sign(pos)
453                         meta:set_string("keyword", current_keyword)
454                         local ownstr = ""
455                         if owner then ownstr = "Locked sign, owned by "..owner.."\n" end
456                         meta:set_string("infotext", ownstr..string.gsub(make_infotext(stored_text), "@KEYWORD", current_keyword).." ")
457                 end
458         end
459
460         local new
461
462         if fields then
463
464                 fields.text = trim_input(fields.text)
465
466                 local ownstr = ""
467                 if owner then ownstr = "Locked sign, owned by "..owner.."\n" end
468
469                 meta:set_string("infotext", ownstr..string.gsub(make_infotext(fields.text), "@KEYWORD", current_keyword).." ")
470                 meta:set_string("text", fields.text)
471                 
472                 meta:set_int("__signslib_new_format", 1)
473                 new = true
474         else
475                 new = (meta:get_int("__signslib_new_format") ~= 0)
476         end
477         local text = meta:get_string("text")
478         if text == nil then return end
479         local objects = minetest.get_objects_inside_radius(pos, 0.5)
480         local found
481         for _, v in ipairs(objects) do
482                 local e = v:get_luaentity()
483                 if e and e.name == "signs:text" then
484                         if found then
485                                 v:remove()
486                         else
487                                 set_obj_text(v, text, new)
488                                 found = true
489                         end
490                 end
491         end
492         if found then
493                 return
494         end
495
496         -- if there is no entity
497         local sign_info
498         local signnode = minetest.get_node(pos)
499         if signnode.name == "signs:sign_yard" then
500                 sign_info = signs_lib.yard_sign_model.textpos[minetest.get_node(pos).param2 + 1]
501         elseif signnode.name == "signs:sign_hanging" then
502                 sign_info = signs_lib.hanging_sign_model.textpos[minetest.get_node(pos).param2 + 1]
503         elseif string.find(signnode.name, "sign_wall") then
504                 if signnode.name == "default:sign_wall" 
505                   or signnode.name == "locked_sign:sign_wall_locked" then
506                         sign_info = signs_lib.regular_wall_sign_model.textpos[minetest.get_node(pos).param2 + 1]
507                 else
508                         sign_info = signs_lib.metal_wall_sign_model.textpos[minetest.get_node(pos).param2 + 1]
509                 end
510         else -- ...it must be a sign on a fence post.
511                 sign_info = signs_lib.sign_post_model.textpos[minetest.get_node(pos).param2 + 1]
512         end
513         if sign_info == nil then
514                 return
515         end
516         local text = minetest.add_entity({x = pos.x + sign_info.delta.x,
517                                                                                 y = pos.y + sign_info.delta.y,
518                                                                                 z = pos.z + sign_info.delta.z}, "signs:text")
519         text:setyaw(sign_info.yaw)
520 end
521
522 -- What kind of sign do we need to place, anyway?
523
524 function signs_lib.determine_sign_type(itemstack, placer, pointed_thing, locked)
525         local name
526         name = minetest.get_node(pointed_thing.under).name
527         if fences_with_sign[name] then
528                 if minetest.is_protected(pointed_thing.under, placer:get_player_name()) then
529                         minetest.record_protection_violation(pointed_thing.under,
530                                 placer:get_player_name())
531                         return itemstack
532                 end
533         else
534                 name = minetest.get_node(pointed_thing.above).name
535                 local def = minetest.registered_nodes[name]
536                 if not def.buildable_to then
537                         return itemstack
538                 end
539                 if minetest.is_protected(pointed_thing.above, placer:get_player_name()) then
540                         minetest.record_protection_violation(pointed_thing.above,
541                                 placer:get_player_name())
542                         return itemstack
543                 end
544         end
545
546         local node=minetest.get_node(pointed_thing.under)
547
548         if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
549                 return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack)
550         else
551                 local above = pointed_thing.above
552                 local under = pointed_thing.under
553                 local dir = {x = under.x - above.x,
554                                          y = under.y - above.y,
555                                          z = under.z - above.z}
556
557                 local wdir = minetest.dir_to_wallmounted(dir)
558
559                 local placer_pos = placer:getpos()
560                 if placer_pos then
561                         dir = {
562                                 x = above.x - placer_pos.x,
563                                 y = above.y - placer_pos.y,
564                                 z = above.z - placer_pos.z
565                         }
566                 end
567
568                 local fdir = minetest.dir_to_facedir(dir)
569
570                 local pt_name = minetest.get_node(under).name
571                 local signname = itemstack:get_name()
572
573                 if fences_with_sign[pt_name] and signname == "default:sign_wall" then
574                         minetest.add_node(under, {name = fences_with_sign[pt_name], param2 = fdir})
575                 elseif wdir == 0 and signname == "default:sign_wall" then
576                         minetest.add_node(above, {name = "signs:sign_hanging", param2 = fdir})
577                 elseif wdir == 1 and signname == "default:sign_wall" then
578                         minetest.add_node(above, {name = "signs:sign_yard", param2 = fdir})
579                 elseif signname ~= "default:sign_wall"
580                   and signname ~= "locked_sign:sign_wall_locked" then -- it's a metal wall sign.
581                         minetest.add_node(above, {name = signname, param2 = fdir})
582                 else -- it must be a default or locked wooden wall sign
583                         minetest.add_node(above, {name = signname, param2 = wdir }) -- note it's wallmounted here!
584                         if locked then
585                                 local meta = minetest.get_meta(above)
586                                 local owner = placer:get_player_name()
587                                 meta:set_string("owner", owner)
588                         end
589                 end
590
591                 if not signs_lib.expect_infinite_stacks then
592                         itemstack:take_item()
593                 end
594                 return itemstack
595         end
596 end
597
598 function signs_lib.receive_fields(pos, formname, fields, sender, lock)
599         if minetest.is_protected(pos, sender:get_player_name()) then
600                 minetest.record_protection_violation(pos,
601                         sender:get_player_name())
602                 return
603         end
604         local lockstr = lock and "locked " or ""
605         if fields and fields.text and fields.ok then
606                 minetest.log("action", S("%s wrote \"%s\" to "..lockstr.."sign at %s"):format(
607                         (sender:get_player_name() or ""),
608                         fields.text,
609                         minetest.pos_to_string(pos)
610                 ))
611                 if lock then
612                         signs_lib.update_sign(pos, fields, sender:get_player_name())
613                 else
614                         signs_lib.update_sign(pos, fields)
615                 end
616         end
617 end
618
619 minetest.register_node(":default:sign_wall", {
620         description = S("Sign"),
621         inventory_image = "default_sign_wall.png",
622         wield_image = "default_sign_wall.png",
623         node_placement_prediction = "",
624         sunlight_propagates = true,
625         paramtype = "light",
626         paramtype2 = "wallmounted",
627         drawtype = "nodebox",
628         node_box = signs_lib.regular_wall_sign_model.nodebox,
629         tiles = {"signs_wall_sign.png"},
630         groups = sign_groups,
631
632         on_place = function(itemstack, placer, pointed_thing)
633                 return signs_lib.determine_sign_type(itemstack, placer, pointed_thing)
634         end,
635         on_construct = function(pos)
636                 signs_lib.construct_sign(pos)
637         end,
638         on_destruct = function(pos)
639                 signs_lib.destruct_sign(pos)
640         end,
641         on_receive_fields = function(pos, formname, fields, sender)
642                 signs_lib.receive_fields(pos, formname, fields, sender)
643         end,
644         on_punch = function(pos, node, puncher)
645                 signs_lib.update_sign(pos)
646         end,
647         on_rotate = signs_lib.wallmounted_rotate
648 })
649
650 minetest.register_node(":signs:sign_yard", {
651     paramtype = "light",
652         sunlight_propagates = true,
653     paramtype2 = "facedir",
654     drawtype = "nodebox",
655     node_box = signs_lib.yard_sign_model.nodebox,
656         selection_box = {
657                 type = "fixed",
658                 fixed = {-0.4375, -0.5, -0.0625, 0.4375, 0.375, 0}
659         },
660     tiles = {"signs_top.png", "signs_bottom.png", "signs_side.png", "signs_side.png", "signs_back.png", "signs_front.png"},
661     groups = {choppy=2, dig_immediate=2},
662     drop = "default:sign_wall",
663
664     on_construct = function(pos)
665         signs_lib.construct_sign(pos)
666     end,
667     on_destruct = function(pos)
668         signs_lib.destruct_sign(pos)
669     end,
670         on_receive_fields = function(pos, formname, fields, sender)
671                 signs_lib.receive_fields(pos, formname, fields, sender)
672         end,
673         on_punch = function(pos, node, puncher)
674                 signs_lib.update_sign(pos)
675         end,
676 })
677
678 minetest.register_node(":signs:sign_hanging", {
679     paramtype = "light",
680         sunlight_propagates = true,
681     paramtype2 = "facedir",
682     drawtype = "nodebox",
683     node_box = signs_lib.hanging_sign_model.nodebox,
684     selection_box = {
685                 type = "fixed",
686                 fixed = {-0.45, -0.275, -0.049, 0.45, 0.5, 0.049}
687         },
688     tiles = {
689                 "signs_hanging_top.png",
690                 "signs_hanging_bottom.png",
691                 "signs_hanging_side.png",
692                 "signs_hanging_side.png",
693                 "signs_hanging_back.png",
694                 "signs_hanging_front.png"
695         },
696     groups = {choppy=2, dig_immediate=2},
697     drop = "default:sign_wall",
698
699     on_construct = function(pos)
700         signs_lib.construct_sign(pos)
701     end,
702     on_destruct = function(pos)
703         signs_lib.destruct_sign(pos)
704     end,
705         on_receive_fields = function(pos, formname, fields, sender)
706                 signs_lib.receive_fields(pos, formname, fields, sender)
707         end,
708         on_punch = function(pos, node, puncher)
709                 signs_lib.update_sign(pos)
710         end,
711 })
712
713 minetest.register_node(":signs:sign_post", {
714     paramtype = "light",
715         sunlight_propagates = true,
716     paramtype2 = "facedir",
717     drawtype = "nodebox",
718     node_box = signs_lib.sign_post_model.nodebox,
719     tiles = {
720                 "signs_post_top.png",
721                 "signs_post_bottom.png",
722                 "signs_post_side.png",
723                 "signs_post_side.png",
724                 "signs_post_back.png",
725                 "signs_post_front.png",
726         },
727     groups = {choppy=2, dig_immediate=2},
728     drop = {
729                 max_items = 2,
730                 items = {
731                         { items = { "default:sign_wall" }},
732                         { items = { "default:fence_wood" }},
733                 },
734     },
735 })
736
737 -- Locked wall sign
738
739 minetest.register_privilege("sign_editor", "Can edit all locked signs")
740
741 minetest.register_node(":locked_sign:sign_wall_locked", {
742         description = S("Sign"),
743         inventory_image = "signs_locked_inv.png",
744         wield_image = "signs_locked_inv.png",
745         node_placement_prediction = "",
746         sunlight_propagates = true,
747         paramtype = "light",
748         paramtype2 = "wallmounted",
749         drawtype = "nodebox",
750         node_box = signs_lib.regular_wall_sign_model.nodebox,
751         tiles = { "signs_wall_sign_locked.png" },
752         groups = sign_groups,
753         on_place = function(itemstack, placer, pointed_thing)
754                 return signs_lib.determine_sign_type(itemstack, placer, pointed_thing, true)
755         end,
756         on_construct = function(pos)
757                 signs_lib.construct_sign(pos, true)
758         end,
759         on_destruct = function(pos)
760                 signs_lib.destruct_sign(pos)
761         end,
762         on_receive_fields = function(pos, formname, fields, sender)
763                 local meta = minetest.get_meta(pos)
764                 local owner = meta:get_string("owner")
765                 local pname = sender:get_player_name() or ""
766                 if pname ~= owner and pname ~= minetest.setting_get("name")
767                   and not minetest.check_player_privs(pname, {sign_editor=true}) then
768                         return
769                 end
770                 signs_lib.receive_fields(pos, formname, fields, sender, true)
771         end,
772         on_punch = function(pos, node, puncher)
773                 signs_lib.update_sign(pos)
774         end,
775         can_dig = function(pos, player)
776                 local meta = minetest.get_meta(pos)
777                 local owner = meta:get_string("owner")
778                 local pname = player:get_player_name()
779                 return pname == owner or pname == minetest.setting_get("name")
780                         or minetest.check_player_privs(pname, {sign_editor=true})
781         end,
782         on_rotate = signs_lib.wallmounted_rotate
783 })
784
785 -- metal, colored signs
786
787 local sign_colors = { "green", "yellow", "red", "white_red", "white_black", "orange", "blue", "brown" }
788
789 for _, color in ipairs(sign_colors) do
790         minetest.register_node(":signs:sign_wall_"..color, {
791                 description = S("Sign ("..color..", metal)"),
792                 inventory_image = "signs_"..color.."_inv.png",
793                 wield_image = "signs_"..color.."_inv.png",
794                 node_placement_prediction = "",
795                 paramtype = "light",
796                 sunlight_propagates = true,
797                 paramtype2 = "facedir",
798                 drawtype = "nodebox",
799                 node_box = signs_lib.metal_wall_sign_model.nodebox,
800                 tiles = {
801                         "signs_metal_tb.png",
802                         "signs_metal_tb.png",
803                         "signs_metal_sides.png",
804                         "signs_metal_sides.png",
805                         "signs_metal_back.png",
806                         "signs_"..color.."_front.png"
807                 },
808                 groups = sign_groups,
809                 on_place = function(itemstack, placer, pointed_thing)
810                         return signs_lib.determine_sign_type(itemstack, placer, pointed_thing)
811                 end,
812                 on_construct = function(pos)
813                         signs_lib.construct_sign(pos)
814                 end,
815                 on_destruct = function(pos)
816                         signs_lib.destruct_sign(pos)
817                 end,
818                 on_receive_fields = function(pos, formname, fields, sender)
819                         signs_lib.receive_fields(pos, formname, fields, sender)
820                 end,
821                 on_punch = function(pos, node, puncher)
822                         signs_lib.update_sign(pos)
823                 end,
824         })
825 end
826
827 local signs_text_on_activate
828
829 signs_text_on_activate = function(self)
830         local meta = minetest.get_meta(self.object:getpos())
831         local text = meta:get_string("text")
832         local new = (meta:get_int("__signslib_new_format") ~= 0)
833         if text then
834                 text = trim_input(text)
835                 set_obj_text(self.object, text, new)
836         end
837 end
838
839 minetest.register_entity(":signs:text", {
840     collisionbox = { 0, 0, 0, 0, 0, 0 },
841     visual = "upright_sprite",
842     textures = {},
843
844         on_activate = signs_text_on_activate,
845 })
846
847 -- And the good stuff here! :-)
848
849 function signs_lib.register_fence_with_sign(fencename, fencewithsignname)
850     local def = minetest.registered_nodes[fencename]
851     local def_sign = minetest.registered_nodes[fencewithsignname]
852     if not (def and def_sign) then
853         minetest.log("warning", "[signs_lib] Attempt to register unknown node as fence")
854         return
855     end
856     def = signs_lib.table_copy(def)
857     def_sign = signs_lib.table_copy(def_sign)
858     fences_with_sign[fencename] = fencewithsignname
859
860     def_sign.on_place = function(itemstack, placer, pointed_thing, ...)
861                 local node_above = minetest.get_node_or_nil(pointed_thing.above)
862                 local node_under = minetest.get_node_or_nil(pointed_thing.under)
863                 local def_above = node_above and minetest.registered_nodes[node_above.name]
864                 local def_under = node_under and minetest.registered_nodes[node_under.name]
865                 local fdir = minetest.dir_to_facedir(placer:get_look_dir())
866                 local playername = placer:get_player_name()
867
868                 if minetest.is_protected(pointed_thing.under, playername) then
869                         minetest.record_protection_violation(pointed_thing.under, playername)
870                         return itemstack
871                 end
872
873                 if minetest.is_protected(pointed_thing.above, playername) then
874                         minetest.record_protection_violation(pointed_thing.above, playername)
875                         return itemstack
876                 end
877
878                 if def_under and def_under.on_rightclick then
879                         return def_under.on_rightclick(pointed_thing.under, node_under, placer, itemstack) or itemstack
880                 elseif def_under and def_under.buildable_to then
881                         minetest.add_node(pointed_thing.under, {name = fencename, param2 = fdir})
882                         if not signs_lib.expect_infinite_stacks then
883                                 itemstack:take_item()
884                         end
885                         placer:set_wielded_item(itemstack)
886                 elseif def_above and def_above.buildable_to then
887                         minetest.add_node(pointed_thing.above, {name = fencename, param2 = fdir})
888                         if not signs_lib.expect_infinite_stacks then
889                                 itemstack:take_item()
890                         end
891                         placer:set_wielded_item(itemstack)
892                 end
893                 return itemstack
894         end
895         def_sign.on_construct = function(pos, ...)
896                 signs_lib.construct_sign(pos)
897         end
898         def_sign.on_destruct = function(pos, ...)
899                 signs_lib.destruct_sign(pos)
900         end
901         def_sign.on_receive_fields = function(pos, formname, fields, sender)
902                 signs_lib.receive_fields(pos, formname, fields, sender)
903         end
904         def_sign.on_punch = function(pos, node, puncher, ...)
905                 signs_lib.update_sign(pos)
906         end
907         local fencename = fencename
908         def_sign.after_dig_node = function(pos, node, ...)
909             node.name = fencename
910             minetest.add_node(pos, node)
911         end
912     def_sign.drop = "default:sign_wall"
913         minetest.register_node(":"..fencename, def)
914         minetest.register_node(":"..fencewithsignname, def_sign)
915         table.insert(signs_lib.sign_node_list, fencewithsignname)
916         minetest.log("debug", S("Registered %s and %s"):format(fencename, fencewithsignname))
917 end
918
919 build_char_db()
920
921 minetest.register_alias("homedecor:fence_wood_with_sign", "signs:sign_post")
922 minetest.register_alias("sign_wall_locked", "locked_sign:sign_wall_locked")
923
924 signs_lib.register_fence_with_sign("default:fence_wood", "signs:sign_post")
925
926 -- restore signs' text after /clearobjects and the like
927
928 minetest.register_abm({
929         nodenames = signs_lib.sign_node_list,
930         interval = 15,
931         chance = 1,
932         action = function(pos, node, active_object_count, active_object_count_wider)
933                 signs_lib.update_sign(pos)
934         end
935 })
936
937 -- locked sign
938
939 minetest.register_craft({
940         output = "locked_sign:sign_wall_locked",
941         recipe = {
942                 {"group:wood", "group:wood", "group:wood"},
943                 {"group:wood", "group:wood", "default:steel_ingot"},
944                 {"", "group:stick", ""},
945         }
946 })
947
948 --Alternate recipe.
949
950 minetest.register_craft({
951         output = "locked_sign:sign_wall_locked",
952         recipe = {
953                 {"default:sign_wall"},
954                 {"default:steel_ingot"},
955     },
956 })
957
958 -- craft recipes for the metal signs
959
960 minetest.register_craft( {
961         output = "signs:sign_wall_green 4",
962         recipe = {
963                         { "dye:dark_green", "dye:white", "dye:dark_green" },
964                         { "default:steel_ingot", "default:steel_ingot", "default:steel_ingot" }
965         },
966 })
967
968 minetest.register_craft( {
969         output = "signs:sign_wall_green 2",
970         recipe = {
971                         { "dye:dark_green", "dye:white", "dye:dark_green" },
972                         { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
973         },
974 })
975
976 minetest.register_craft( {
977         output = "signs:sign_wall_yellow 4",
978         recipe = {
979                         { "dye:yellow", "dye:black", "dye:yellow" },
980                         { "default:steel_ingot", "default:steel_ingot", "default:steel_ingot" }
981         },
982 })
983
984 minetest.register_craft( {
985         output = "signs:sign_wall_yellow 2",
986         recipe = {
987                         { "dye:yellow", "dye:black", "dye:yellow" },
988                         { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
989         },
990 })
991
992 minetest.register_craft( {
993         output = "signs:sign_wall_red 4",
994         recipe = {
995                         { "dye:red", "dye:white", "dye:red" },
996                         { "default:steel_ingot", "default:steel_ingot", "default:steel_ingot" }
997         },
998 })
999
1000 minetest.register_craft( {
1001         output = "signs:sign_wall_red 2",
1002         recipe = {
1003                         { "dye:red", "dye:white", "dye:red" },
1004                         { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1005         },
1006 })
1007
1008 minetest.register_craft( {
1009         output = "signs:sign_wall_white_red 4",
1010         recipe = {
1011                         { "dye:white", "dye:red", "dye:white" },
1012                         { "default:steel_ingot", "default:steel_ingot", "default:steel_ingot" }
1013         },
1014 })
1015
1016 minetest.register_craft( {
1017         output = "signs:sign_wall_white_red 2",
1018         recipe = {
1019                         { "dye:white", "dye:red", "dye:white" },
1020                         { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1021         },
1022 })
1023
1024 minetest.register_craft( {
1025         output = "signs:sign_wall_white_black 4",
1026         recipe = {
1027                         { "dye:white", "dye:black", "dye:white" },
1028                         { "default:steel_ingot", "default:steel_ingot", "default:steel_ingot" }
1029         },
1030 })
1031
1032 minetest.register_craft( {
1033         output = "signs:sign_wall_white_black 2",
1034         recipe = {
1035                         { "dye:white", "dye:black", "dye:white" },
1036                         { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1037         },
1038 })
1039
1040 minetest.register_craft( {
1041         output = "signs:sign_wall_orange 4",
1042         recipe = {
1043                         { "dye:orange", "dye:black", "dye:orange" },
1044                         { "default:steel_ingot", "default:steel_ingot", "default:steel_ingot" }
1045         },
1046 })
1047
1048 minetest.register_craft( {
1049         output = "signs:sign_wall_orange 2",
1050         recipe = {
1051                         { "dye:orange", "dye:black", "dye:orange" },
1052                         { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1053         },
1054 })
1055
1056 minetest.register_craft( {
1057         output = "signs:sign_wall_blue 4",
1058         recipe = {
1059                         { "dye:blue", "dye:white", "dye:blue" },
1060                         { "default:steel_ingot", "default:steel_ingot", "default:steel_ingot" }
1061         },
1062 })
1063
1064 minetest.register_craft( {
1065         output = "signs:sign_wall_blue 2",
1066         recipe = {
1067                         { "dye:blue", "dye:white", "dye:blue" },
1068                         { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1069         },
1070 })
1071
1072 minetest.register_craft( {
1073         output = "signs:sign_wall_brown 4",
1074         recipe = {
1075                         { "dye:brown", "dye:white", "dye:brown" },
1076                         { "default:steel_ingot", "default:steel_ingot", "default:steel_ingot" }
1077         },
1078 })
1079
1080 minetest.register_craft( {
1081         output = "signs:sign_wall_brown 2",
1082         recipe = {
1083                         { "dye:brown", "dye:white", "dye:brown" },
1084                         { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1085         },
1086 })
1087
1088 if minetest.setting_get("log_mods") then
1089         minetest.log("action", S("signs loaded"))
1090 end