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