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