]> git.lizzy.rs Git - signs_lib.git/blob - init.lua
e0bafe27b86d3a062be21047e1f8d22090e6db1a
[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         print("called signs_lib.determine_sign_type")
541         local name
542         name = minetest.get_node(pointed_thing.under).name
543         if fences_with_sign[name] then
544                 if minetest.is_protected(pointed_thing.under, placer:get_player_name()) then
545                         minetest.record_protection_violation(pointed_thing.under,
546                                 placer:get_player_name())
547                         return itemstack
548                 end
549         else
550                 name = minetest.get_node(pointed_thing.above).name
551                 local def = minetest.registered_nodes[name]
552                 if not def.buildable_to then
553                         return itemstack
554                 end
555                 if minetest.is_protected(pointed_thing.above, placer:get_player_name()) then
556                         minetest.record_protection_violation(pointed_thing.above,
557                                 placer:get_player_name())
558                         return itemstack
559                 end
560         end
561
562         print("protection and position okay")
563         local node=minetest.get_node(pointed_thing.under)
564
565         if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
566                 return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack)
567         else
568                 print("target didn't have rightclick")
569                 local above = pointed_thing.above
570                 local under = pointed_thing.under
571                 local dir = {x = under.x - above.x,
572                                          y = under.y - above.y,
573                                          z = under.z - above.z}
574
575                 local wdir = minetest.dir_to_wallmounted(dir)
576
577                 local placer_pos = placer:getpos()
578                 if placer_pos then
579                         dir = {
580                                 x = above.x - placer_pos.x,
581                                 y = above.y - placer_pos.y,
582                                 z = above.z - placer_pos.z
583                         }
584                 end
585
586                 local fdir = minetest.dir_to_facedir(dir)
587                 print("facedir = "..fdir)
588                 local pt_name = minetest.get_node(under).name
589                 local signname = itemstack:get_name()
590
591                 if fences_with_sign[pt_name] and signname == default_sign then
592                         minetest.add_node(under, {name = fences_with_sign[pt_name], param2 = fdir})
593                 elseif wdir == 0 and signname == default_sign then
594                         minetest.add_node(above, {name = "signs:sign_hanging", param2 = fdir})
595                 elseif wdir == 1 and signname == default_sign then
596                         minetest.add_node(above, {name = "signs:sign_yard", param2 = fdir})
597                 elseif signname == default_sign_metal then
598                         minetest.add_node(above, {name = signname, param2 = wdir })
599                         print("line 597")
600                 elseif signname ~= default_sign
601                   and signname ~= default_sign_metal
602                   and signname ~= "locked_sign:sign_wall_locked" then -- it's a signs_lib colored metal wall sign.
603                         minetest.add_node(above, {name = signname, param2 = fdir})
604                         print("line 602")
605                 else -- it must be a default or locked wooden wall sign
606                         minetest.add_node(above, {name = signname, param2 = wdir }) -- note it's wallmounted here!
607                         print("line 605")
608                         if locked then
609                                 local meta = minetest.get_meta(above)
610                                 local owner = placer:get_player_name()
611                                 meta:set_string("owner", owner)
612                         end
613                 end
614
615                 if not signs_lib.expect_infinite_stacks then
616                         itemstack:take_item()
617                 end
618                 return itemstack
619         end
620 end
621
622 function signs_lib.receive_fields(pos, formname, fields, sender, lock)
623         if minetest.is_protected(pos, sender:get_player_name()) then
624                 minetest.record_protection_violation(pos,
625                         sender:get_player_name())
626                 return
627         end
628         local lockstr = lock and "locked " or ""
629         if fields and fields.text and fields.ok then
630                 minetest.log("action", S("%s wrote \"%s\" to "..lockstr.."sign at %s"):format(
631                         (sender:get_player_name() or ""),
632                         fields.text,
633                         minetest.pos_to_string(pos)
634                 ))
635                 if lock then
636                         signs_lib.update_sign(pos, fields, sender:get_player_name())
637                 else
638                         signs_lib.update_sign(pos, fields)
639                 end
640         end
641 end
642
643 minetest.register_node(":"..default_sign, {
644         description = S("Sign"),
645         inventory_image = default_sign_image,
646         wield_image = default_sign_image,
647         node_placement_prediction = "",
648         sunlight_propagates = true,
649         paramtype = "light",
650         paramtype2 = "wallmounted",
651         drawtype = "nodebox",
652         node_box = signs_lib.regular_wall_sign_model.nodebox,
653         tiles = {"signs_wall_sign.png"},
654         groups = sign_groups,
655
656         on_place = function(itemstack, placer, pointed_thing)
657                 return signs_lib.determine_sign_type(itemstack, placer, pointed_thing)
658         end,
659         on_construct = function(pos)
660                 signs_lib.construct_sign(pos)
661         end,
662         on_destruct = function(pos)
663                 signs_lib.destruct_sign(pos)
664         end,
665         on_receive_fields = function(pos, formname, fields, sender)
666                 signs_lib.receive_fields(pos, formname, fields, sender)
667         end,
668         on_punch = function(pos, node, puncher)
669                 signs_lib.update_sign(pos)
670         end,
671         on_rotate = signs_lib.wallmounted_rotate
672 })
673
674 minetest.register_node(":signs:sign_yard", {
675     paramtype = "light",
676         sunlight_propagates = true,
677     paramtype2 = "facedir",
678     drawtype = "nodebox",
679     node_box = signs_lib.yard_sign_model.nodebox,
680         selection_box = {
681                 type = "fixed",
682                 fixed = {-0.4375, -0.5, -0.0625, 0.4375, 0.375, 0}
683         },
684     tiles = {"signs_top.png", "signs_bottom.png", "signs_side.png", "signs_side.png", "signs_back.png", "signs_front.png"},
685     groups = {choppy=2, dig_immediate=2},
686     drop = default_sign,
687
688     on_construct = function(pos)
689         signs_lib.construct_sign(pos)
690     end,
691     on_destruct = function(pos)
692         signs_lib.destruct_sign(pos)
693     end,
694         on_receive_fields = function(pos, formname, fields, sender)
695                 signs_lib.receive_fields(pos, formname, fields, sender)
696         end,
697         on_punch = function(pos, node, puncher)
698                 signs_lib.update_sign(pos)
699         end,
700 })
701
702 minetest.register_node(":signs:sign_hanging", {
703     paramtype = "light",
704         sunlight_propagates = true,
705     paramtype2 = "facedir",
706     drawtype = "nodebox",
707     node_box = signs_lib.hanging_sign_model.nodebox,
708     selection_box = {
709                 type = "fixed",
710                 fixed = {-0.45, -0.275, -0.049, 0.45, 0.5, 0.049}
711         },
712     tiles = {
713                 "signs_hanging_top.png",
714                 "signs_hanging_bottom.png",
715                 "signs_hanging_side.png",
716                 "signs_hanging_side.png",
717                 "signs_hanging_back.png",
718                 "signs_hanging_front.png"
719         },
720     groups = {choppy=2, dig_immediate=2},
721     drop = default_sign,
722
723     on_construct = function(pos)
724         signs_lib.construct_sign(pos)
725     end,
726     on_destruct = function(pos)
727         signs_lib.destruct_sign(pos)
728     end,
729         on_receive_fields = function(pos, formname, fields, sender)
730                 signs_lib.receive_fields(pos, formname, fields, sender)
731         end,
732         on_punch = function(pos, node, puncher)
733                 signs_lib.update_sign(pos)
734         end,
735 })
736
737 minetest.register_node(":signs:sign_post", {
738     paramtype = "light",
739         sunlight_propagates = true,
740     paramtype2 = "facedir",
741     drawtype = "nodebox",
742     node_box = signs_lib.sign_post_model.nodebox,
743     tiles = {
744                 "signs_post_top.png",
745                 "signs_post_bottom.png",
746                 "signs_post_side.png",
747                 "signs_post_side.png",
748                 "signs_post_back.png",
749                 "signs_post_front.png",
750         },
751     groups = {choppy=2, dig_immediate=2},
752     drop = {
753                 max_items = 2,
754                 items = {
755                         { items = { default_sign }},
756                         { items = { "default:fence_wood" }},
757                 },
758     },
759 })
760
761 -- Locked wall sign
762
763 minetest.register_privilege("sign_editor", "Can edit all locked signs")
764
765 minetest.register_node(":locked_sign:sign_wall_locked", {
766         description = S("Sign"),
767         inventory_image = "signs_locked_inv.png",
768         wield_image = "signs_locked_inv.png",
769         node_placement_prediction = "",
770         sunlight_propagates = true,
771         paramtype = "light",
772         paramtype2 = "wallmounted",
773         drawtype = "nodebox",
774         node_box = signs_lib.regular_wall_sign_model.nodebox,
775         tiles = { "signs_wall_sign_locked.png" },
776         groups = sign_groups,
777         on_place = function(itemstack, placer, pointed_thing)
778                 return signs_lib.determine_sign_type(itemstack, placer, pointed_thing, true)
779         end,
780         on_construct = function(pos)
781                 signs_lib.construct_sign(pos, true)
782         end,
783         on_destruct = function(pos)
784                 signs_lib.destruct_sign(pos)
785         end,
786         on_receive_fields = function(pos, formname, fields, sender)
787                 local meta = minetest.get_meta(pos)
788                 local owner = meta:get_string("owner")
789                 local pname = sender:get_player_name() or ""
790                 if pname ~= owner and pname ~= minetest.setting_get("name")
791                   and not minetest.check_player_privs(pname, {sign_editor=true}) then
792                         return
793                 end
794                 signs_lib.receive_fields(pos, formname, fields, sender, true)
795         end,
796         on_punch = function(pos, node, puncher)
797                 signs_lib.update_sign(pos)
798         end,
799         can_dig = function(pos, player)
800                 local meta = minetest.get_meta(pos)
801                 local owner = meta:get_string("owner")
802                 local pname = player:get_player_name()
803                 return pname == owner or pname == minetest.setting_get("name")
804                         or minetest.check_player_privs(pname, {sign_editor=true})
805         end,
806         on_rotate = signs_lib.wallmounted_rotate
807 })
808
809 -- default metal sign, if defined
810
811 if minetest.registered_nodes["default:sign_wall_steel"] then
812         minetest.register_node(":"..default_sign_metal, {
813                 description = S("Sign"),
814                 inventory_image = default_sign_metal_image,
815                 wield_image = default_sign_metal_image,
816                 node_placement_prediction = "",
817                 sunlight_propagates = true,
818                 paramtype = "light",
819                 paramtype2 = "wallmounted",
820                 drawtype = "nodebox",
821                 node_box = signs_lib.regular_wall_sign_model.nodebox,
822                 tiles = {"signs_wall_sign_metal.png"},
823                 groups = sign_groups,
824
825                 on_place = function(itemstack, placer, pointed_thing)
826                         return signs_lib.determine_sign_type(itemstack, placer, pointed_thing)
827                 end,
828                 on_construct = function(pos)
829                         signs_lib.construct_sign(pos)
830                 end,
831                 on_destruct = function(pos)
832                         signs_lib.destruct_sign(pos)
833                 end,
834                 on_receive_fields = function(pos, formname, fields, sender)
835                         signs_lib.receive_fields(pos, formname, fields, sender)
836                 end,
837                 on_punch = function(pos, node, puncher)
838                         signs_lib.update_sign(pos)
839                 end,
840                 on_rotate = signs_lib.wallmounted_rotate
841         })
842 end
843
844 -- metal, colored signs
845
846 local sign_colors = { "green", "yellow", "red", "white_red", "white_black", "orange", "blue", "brown" }
847
848 for _, color in ipairs(sign_colors) do
849         minetest.register_node(":signs:sign_wall_"..color, {
850                 description = S("Sign ("..color..", metal)"),
851                 inventory_image = "signs_"..color.."_inv.png",
852                 wield_image = "signs_"..color.."_inv.png",
853                 node_placement_prediction = "",
854                 paramtype = "light",
855                 sunlight_propagates = true,
856                 paramtype2 = "facedir",
857                 drawtype = "nodebox",
858                 node_box = signs_lib.metal_wall_sign_model.nodebox,
859                 tiles = {
860                         "signs_metal_tb.png",
861                         "signs_metal_tb.png",
862                         "signs_metal_sides.png",
863                         "signs_metal_sides.png",
864                         "signs_metal_back.png",
865                         "signs_"..color.."_front.png"
866                 },
867                 groups = sign_groups,
868                 on_place = function(itemstack, placer, pointed_thing)
869                         return signs_lib.determine_sign_type(itemstack, placer, pointed_thing)
870                 end,
871                 on_construct = function(pos)
872                         signs_lib.construct_sign(pos)
873                 end,
874                 on_destruct = function(pos)
875                         signs_lib.destruct_sign(pos)
876                 end,
877                 on_receive_fields = function(pos, formname, fields, sender)
878                         signs_lib.receive_fields(pos, formname, fields, sender)
879                 end,
880                 on_punch = function(pos, node, puncher)
881                         signs_lib.update_sign(pos)
882                 end,
883         })
884 end
885
886 local signs_text_on_activate
887
888 signs_text_on_activate = function(self)
889         local meta = minetest.get_meta(self.object:getpos())
890         local text = meta:get_string("text")
891         local new = (meta:get_int("__signslib_new_format") ~= 0)
892         if text then
893                 text = trim_input(text)
894                 set_obj_text(self.object, text, new)
895         end
896 end
897
898 minetest.register_entity(":signs:text", {
899     collisionbox = { 0, 0, 0, 0, 0, 0 },
900     visual = "upright_sprite",
901     textures = {},
902
903         on_activate = signs_text_on_activate,
904 })
905
906 -- And the good stuff here! :-)
907
908 function signs_lib.register_fence_with_sign(fencename, fencewithsignname)
909     local def = minetest.registered_nodes[fencename]
910     local def_sign = minetest.registered_nodes[fencewithsignname]
911     if not (def and def_sign) then
912         minetest.log("warning", "[signs_lib] Attempt to register unknown node as fence")
913         return
914     end
915     def = signs_lib.table_copy(def)
916     def_sign = signs_lib.table_copy(def_sign)
917     fences_with_sign[fencename] = fencewithsignname
918
919     def_sign.on_place = function(itemstack, placer, pointed_thing, ...)
920                 local node_above = minetest.get_node_or_nil(pointed_thing.above)
921                 local node_under = minetest.get_node_or_nil(pointed_thing.under)
922                 local def_above = node_above and minetest.registered_nodes[node_above.name]
923                 local def_under = node_under and minetest.registered_nodes[node_under.name]
924                 local fdir = minetest.dir_to_facedir(placer:get_look_dir())
925                 local playername = placer:get_player_name()
926
927                 if minetest.is_protected(pointed_thing.under, playername) then
928                         minetest.record_protection_violation(pointed_thing.under, playername)
929                         return itemstack
930                 end
931
932                 if minetest.is_protected(pointed_thing.above, playername) then
933                         minetest.record_protection_violation(pointed_thing.above, playername)
934                         return itemstack
935                 end
936
937                 if def_under and def_under.on_rightclick then
938                         return def_under.on_rightclick(pointed_thing.under, node_under, placer, itemstack) or itemstack
939                 elseif def_under and def_under.buildable_to then
940                         minetest.add_node(pointed_thing.under, {name = fencename, param2 = fdir})
941                         if not signs_lib.expect_infinite_stacks then
942                                 itemstack:take_item()
943                         end
944                         placer:set_wielded_item(itemstack)
945                 elseif def_above and def_above.buildable_to then
946                         minetest.add_node(pointed_thing.above, {name = fencename, param2 = fdir})
947                         if not signs_lib.expect_infinite_stacks then
948                                 itemstack:take_item()
949                         end
950                         placer:set_wielded_item(itemstack)
951                 end
952                 return itemstack
953         end
954         def_sign.on_construct = function(pos, ...)
955                 signs_lib.construct_sign(pos)
956         end
957         def_sign.on_destruct = function(pos, ...)
958                 signs_lib.destruct_sign(pos)
959         end
960         def_sign.on_receive_fields = function(pos, formname, fields, sender)
961                 signs_lib.receive_fields(pos, formname, fields, sender)
962         end
963         def_sign.on_punch = function(pos, node, puncher, ...)
964                 signs_lib.update_sign(pos)
965         end
966         local fencename = fencename
967         def_sign.after_dig_node = function(pos, node, ...)
968             node.name = fencename
969             minetest.add_node(pos, node)
970         end
971     def_sign.drop = default_sign
972         minetest.register_node(":"..fencename, def)
973         minetest.register_node(":"..fencewithsignname, def_sign)
974         table.insert(signs_lib.sign_node_list, fencewithsignname)
975         minetest.log("verbose", S("Registered %s and %s"):format(fencename, fencewithsignname))
976 end
977
978 build_char_db()
979
980 minetest.register_alias("homedecor:fence_wood_with_sign", "signs:sign_post")
981 minetest.register_alias("sign_wall_locked", "locked_sign:sign_wall_locked")
982
983 signs_lib.register_fence_with_sign("default:fence_wood", "signs:sign_post")
984
985 -- restore signs' text after /clearobjects and the like, the next time
986 -- a block is reloaded by the server.
987
988 minetest.register_lbm({
989         nodenames = signs_lib.sign_node_list,
990         name = "signs_lib:restore_sign_text",
991         label = "Restore sign text",
992         run_at_every_load = true,
993         action = function(pos, node)
994                 signs_lib.update_sign(pos)
995         end
996 })
997
998 -- locked sign
999
1000 minetest.register_craft({
1001         output = "locked_sign:sign_wall_locked",
1002         recipe = {
1003                 {"group:wood", "group:wood", "group:wood"},
1004                 {"group:wood", "group:wood", "default:steel_ingot"},
1005                 {"", "group:stick", ""},
1006         }
1007 })
1008
1009 --Alternate recipes.
1010
1011 minetest.register_craft({
1012         output = "locked_sign:sign_wall_locked",
1013         recipe = {
1014                 {default_sign},
1015                 {"default:steel_ingot"},
1016     },
1017 })
1018
1019 minetest.register_craft({
1020         output = "locked_sign:sign_wall_locked",
1021         recipe = {
1022                 {default_sign_metal},
1023                 {"default:steel_ingot"},
1024     },
1025 })
1026
1027 -- craft recipes for the metal signs
1028
1029 minetest.register_craft( {
1030         output = "signs:sign_wall_green",
1031         recipe = {
1032                         { "dye:dark_green", "dye:white", "dye:dark_green" },
1033                         { "", default_sign_metal, "" }
1034         },
1035 })
1036
1037 minetest.register_craft( {
1038         output = "signs:sign_wall_green 2",
1039         recipe = {
1040                         { "dye:dark_green", "dye:white", "dye:dark_green" },
1041                         { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1042         },
1043 })
1044
1045 minetest.register_craft( {
1046         output = "signs:sign_wall_yellow",
1047         recipe = {
1048                         { "dye:yellow", "dye:black", "dye:yellow" },
1049                         { "", default_sign_metal, "" }
1050         },
1051 })
1052
1053 minetest.register_craft( {
1054         output = "signs:sign_wall_yellow 2",
1055         recipe = {
1056                         { "dye:yellow", "dye:black", "dye:yellow" },
1057                         { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1058         },
1059 })
1060
1061 minetest.register_craft( {
1062         output = "signs:sign_wall_red",
1063         recipe = {
1064                         { "dye:red", "dye:white", "dye:red" },
1065                         { "", default_sign_metal, "" }
1066         },
1067 })
1068
1069 minetest.register_craft( {
1070         output = "signs:sign_wall_red 2",
1071         recipe = {
1072                         { "dye:red", "dye:white", "dye:red" },
1073                         { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1074         },
1075 })
1076
1077 minetest.register_craft( {
1078         output = "signs:sign_wall_white_red",
1079         recipe = {
1080                         { "dye:white", "dye:red", "dye:white" },
1081                         { "", default_sign_metal, "" }
1082         },
1083 })
1084
1085 minetest.register_craft( {
1086         output = "signs:sign_wall_white_red 2",
1087         recipe = {
1088                         { "dye:white", "dye:red", "dye:white" },
1089                         { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1090         },
1091 })
1092
1093 minetest.register_craft( {
1094         output = "signs:sign_wall_white_black",
1095         recipe = {
1096                         { "dye:white", "dye:black", "dye:white" },
1097                         { "", default_sign_metal, "" }
1098         },
1099 })
1100
1101 minetest.register_craft( {
1102         output = "signs:sign_wall_white_black 2",
1103         recipe = {
1104                         { "dye:white", "dye:black", "dye:white" },
1105                         { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1106         },
1107 })
1108
1109 minetest.register_craft( {
1110         output = "signs:sign_wall_orange",
1111         recipe = {
1112                         { "dye:orange", "dye:black", "dye:orange" },
1113                         { "", default_sign_metal, "" }
1114         },
1115 })
1116
1117 minetest.register_craft( {
1118         output = "signs:sign_wall_orange 2",
1119         recipe = {
1120                         { "dye:orange", "dye:black", "dye:orange" },
1121                         { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1122         },
1123 })
1124
1125 minetest.register_craft( {
1126         output = "signs:sign_wall_blue",
1127         recipe = {
1128                         { "dye:blue", "dye:white", "dye:blue" },
1129                         { "", default_sign_metal, "" }
1130         },
1131 })
1132
1133 minetest.register_craft( {
1134         output = "signs:sign_wall_blue 2",
1135         recipe = {
1136                         { "dye:blue", "dye:white", "dye:blue" },
1137                         { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1138         },
1139 })
1140
1141 minetest.register_craft( {
1142         output = "signs:sign_wall_brown",
1143         recipe = {
1144                         { "dye:brown", "dye:white", "dye:brown" },
1145                         { "", default_sign_metal, "" }
1146         },
1147 })
1148
1149 minetest.register_craft( {
1150         output = "signs:sign_wall_brown 2",
1151         recipe = {
1152                         { "dye:brown", "dye:white", "dye:brown" },
1153                         { "steel:sheet_metal", "steel:sheet_metal", "steel:sheet_metal" }
1154         },
1155 })
1156
1157 if minetest.setting_get("log_mods") then
1158         minetest.log("action", S("signs loaded"))
1159 end