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