init.lua 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. local vadd = vector.add
  2. local floor, pi = math.floor, math.pi
  3. local sign_positions = {
  4. [0] = {{x = 0, y = 0.18, z = -0.07}, pi},
  5. [1] = {{x = -0.07, y = 0.18, z = 0}, pi * 0.5},
  6. [2] = {{x = 0, y = 0.18, z = 0.07}, 0},
  7. [3] = {{x = 0.07, y = 0.18, z = 0}, pi * 1.5}
  8. }
  9. local wall_sign_positions = {
  10. [0] = {{x = 0.43, y = -0.005, z = 0}, pi * 0.5},
  11. [1] = {{x = -0.43, y = -0.005, z = 0}, pi * 1.5},
  12. [2] = {{x = 0, y = -0.005, z = 0.43}, pi},
  13. [3] = {{x = 0, y = -0.005, z = -0.43}, 0}
  14. }
  15. local function generate_sign_line_texture(str, row)
  16. local leftover = floor((20 - #str) * 16 / 2)
  17. local texture = ""
  18. for i = 1, 20 do
  19. local char = str:byte(i)
  20. if char and (char >= 32 and char <= 126) then
  21. texture = texture .. ":" .. (i - 1) * 16 + leftover .. ","
  22. .. row * 20 .. "=signs_" .. char .. ".png"
  23. end
  24. end
  25. return texture
  26. end
  27. local function find_any(str, pair, start)
  28. local ret = 0 -- 0 if not found (indices start at 1)
  29. for _, needle in pairs(pair) do
  30. local first = str:find(needle, start)
  31. if first then
  32. if ret == 0 or first < ret then
  33. ret = first
  34. end
  35. end
  36. end
  37. return ret
  38. end
  39. local disposable_chars = {["\n"] = true, ["\r"] = true, ["\t"] = true, [" "] = true}
  40. local wrap_chars = {"\n", "\r", "\t", " ", "-", "/", ";", ":", ",", ".", "?", "!"}
  41. local slugify = dofile(minetest.get_modpath("signs") .. "/slugify.lua")
  42. local function generate_sign_texture(str)
  43. if not str or str == "" then
  44. return "blank.png"
  45. end
  46. local row = 0
  47. local texture = "[combine:" .. 16 * 20 .. "x100"
  48. local result = {}
  49. -- Transliterate text
  50. str = slugify(str)
  51. while #str > 0 do
  52. if row > 4 then
  53. break
  54. end
  55. local wrap_i = 0
  56. local keep_i = 0 -- The last character that was kept
  57. while wrap_i < #str do
  58. wrap_i = find_any(str, wrap_chars, wrap_i + 1)
  59. if wrap_i > 20 then
  60. if keep_i > 1 then
  61. wrap_i = keep_i
  62. else
  63. wrap_i = 20
  64. end
  65. break
  66. elseif wrap_i == 0 then
  67. if #str <= 20 then
  68. wrap_i = #str
  69. elseif keep_i > 0 then
  70. wrap_i = keep_i
  71. else
  72. wrap_i = #str
  73. end
  74. break
  75. elseif str:sub(wrap_i, wrap_i) == "\n" then
  76. break
  77. end
  78. if not disposable_chars[str:sub(wrap_i, wrap_i)] then
  79. keep_i = wrap_i
  80. elseif wrap_i > 1 and not disposable_chars[str:sub(wrap_i - 1, wrap_i - 1)] then
  81. keep_i = wrap_i - 1
  82. end
  83. end
  84. if wrap_i > 20 then
  85. wrap_i = 20
  86. end
  87. local start_remove = 0
  88. if disposable_chars[str:sub(1, 1)] then
  89. start_remove = 1
  90. end
  91. local end_remove = 0
  92. if disposable_chars[str:sub(wrap_i, wrap_i)] then
  93. end_remove = 1
  94. end
  95. local line_string = str:sub(1 + start_remove, wrap_i - end_remove)
  96. str = str:sub(wrap_i + 1)
  97. if line_string ~= "" then
  98. result[row] = line_string
  99. end
  100. row = row + 1
  101. end
  102. local empty = 0
  103. if row == 1 then
  104. empty = 2
  105. elseif row < 4 then
  106. empty = 1
  107. end
  108. for r, s in pairs(result) do
  109. texture = texture .. generate_sign_line_texture(s, r + empty)
  110. end
  111. return texture
  112. end
  113. minetest.register_entity("signs:sign_text", {
  114. visual = "upright_sprite",
  115. visual_size = {x = 0.7, y = 0.6},
  116. collisionbox = {0},
  117. pointable = false,
  118. on_activate = function(self)
  119. local ent = self.object
  120. local pos = ent:get_pos()
  121. -- remove entity for missing sign
  122. local node_name = minetest.get_node(pos).name
  123. if not node_name == "signs:sign" and not node_name == "signs:wall_sign" then
  124. ent:remove()
  125. return
  126. end
  127. local meta = minetest.get_meta(pos)
  128. local meta_texture = meta:get_string("sign_texture")
  129. local texture
  130. if meta_texture and meta_texture ~= "" then
  131. texture = meta_texture
  132. else
  133. local meta_text = meta:get_string("sign_text")
  134. if meta_text and meta_text ~= "" then
  135. texture = generate_sign_texture(meta_text)
  136. else
  137. texture = "blank.png"
  138. end
  139. meta:set_string("sign_texture", texture)
  140. end
  141. ent:set_properties({
  142. textures = {texture, "blank.png"}
  143. })
  144. end
  145. })
  146. local function check_text(pos)
  147. local meta = minetest.get_meta(pos)
  148. local text = meta:get_string("sign_text")
  149. local objects = minetest.get_objects_inside_radius(pos, 0.5)
  150. if text and text ~= "" then
  151. local count = 0
  152. for _, obj in pairs(objects) do
  153. local ent = obj:get_luaentity()
  154. if ent and ent.name == "signs:sign_text" then
  155. count = count + 1
  156. if count > 1 then
  157. obj:remove()
  158. end
  159. end
  160. end
  161. if count == 0 then
  162. local node = minetest.get_node(pos)
  163. local p2 = node.param2 or -1
  164. local sign_pos = sign_positions
  165. if node.name == "signs:wall_sign" then
  166. p2 = p2 - 2
  167. sign_pos = wall_sign_positions
  168. end
  169. if p2 > 3 or p2 < 0 then return end
  170. local obj = minetest.add_entity(
  171. vadd(pos, sign_pos[p2][1]), "signs:sign_text")
  172. obj:set_yaw(sign_pos[p2][2])
  173. end
  174. else
  175. for _, obj in pairs(objects) do
  176. local ent = obj:get_luaentity()
  177. if ent and ent.name == "signs:sign_text" then
  178. obj:remove()
  179. end
  180. end
  181. end
  182. end
  183. minetest.register_lbm({
  184. label = "Check for sign text",
  185. name = "signs:sign_text",
  186. nodenames = {"signs:sign", "signs:wall_sign"},
  187. run_at_every_load = true,
  188. action = check_text
  189. })
  190. local function construct(pos)
  191. local meta = minetest.get_meta(pos)
  192. meta:set_string("formspec", "size[5,3]" ..
  193. "textarea[1.15,0.3;3.3,2;Dtext;" .. "Enter your text:" .. ";${sign_text}]" ..
  194. "button_exit[0.85,2;3.3,1;;" .. "Save" .. "]")
  195. end
  196. local function destruct(pos)
  197. for _, obj in pairs(minetest.get_objects_inside_radius(pos, 0.5)) do
  198. local ent = obj:get_luaentity()
  199. if ent and ent.name == "signs:sign_text" then
  200. obj:remove()
  201. end
  202. end
  203. end
  204. local function receive_fields(pos, _, fields, sender, wall)
  205. local text = fields.Dtext
  206. if not text then return end
  207. if minetest.is_protected(pos, sender:get_player_name()) then
  208. return
  209. end
  210. text = text:sub(1, 256)
  211. local p2 = minetest.get_node(pos).param2
  212. local sign_pos = sign_positions
  213. if wall then
  214. p2 = p2 - 2
  215. sign_pos = wall_sign_positions
  216. end
  217. if not p2 or p2 > 3 or p2 < 0 then return end
  218. local sign
  219. for _, obj in pairs(minetest.get_objects_inside_radius(pos, 0.5)) do
  220. local ent = obj:get_luaentity()
  221. if ent and ent.name == "signs:sign_text" then
  222. sign = obj
  223. break
  224. end
  225. end
  226. if not sign then
  227. sign = minetest.add_entity(
  228. vadd(pos, sign_pos[p2][1]), "signs:sign_text")
  229. else
  230. sign:set_pos(vadd(pos, sign_pos[p2][1]))
  231. end
  232. local texture = generate_sign_texture(text)
  233. sign:set_properties({
  234. textures = {texture, "blank.png"}
  235. })
  236. sign:set_yaw(sign_pos[p2][2])
  237. local meta = minetest.get_meta(pos)
  238. meta:set_string("sign_text", text)
  239. meta:set_string("sign_texture", texture)
  240. meta:set_string("infotext", text)
  241. end
  242. minetest.register_node("signs:sign", {
  243. description = "Sign",
  244. tiles = {"signs_wood.png"},
  245. drawtype = "nodebox",
  246. paramtype = "light",
  247. paramtype2 = "facedir",
  248. node_placement_prediction = "",
  249. sunlight_propagates = true,
  250. node_box = {
  251. type = "fixed",
  252. fixed = {
  253. {-0.375, -0.125, -0.063, 0.375, 0.5, 0.063},
  254. {-0.063, -0.5, -0.063, 0.063, -0.125, 0.063}
  255. }
  256. },
  257. groups = {oddly_breakable_by_hand = 1, choppy = 3, attached_node = 1},
  258. on_place = function(itemstack, placer, pointed_thing)
  259. if pointed_thing.type == "node" then
  260. local under = pointed_thing.under
  261. local node = minetest.get_node(under)
  262. local node_def = minetest.registered_nodes[node.name]
  263. if node_def and node_def.on_rightclick and
  264. not (placer and placer:is_player() and
  265. placer:get_player_control().sneak) then
  266. return node_def.on_rightclick(under, node, placer, itemstack,
  267. pointed_thing) or itemstack
  268. end
  269. local undery = pointed_thing.under.y
  270. local posy = pointed_thing.above.y
  271. local _, result
  272. if undery < posy then -- Floor sign
  273. itemstack, result = minetest.item_place(itemstack,
  274. placer, pointed_thing)
  275. elseif undery == posy then -- Wall sign
  276. _, result = minetest.item_place(ItemStack("signs:wall_sign"),
  277. placer, pointed_thing)
  278. if result and not (creative and creative.is_enabled_for and
  279. creative.is_enabled_for(placer)) then
  280. itemstack:take_item()
  281. end
  282. end
  283. if result then
  284. minetest.sound_play({name = "default_place_node_hard"},
  285. {pos = pointed_thing.above})
  286. end
  287. end
  288. return itemstack
  289. end,
  290. on_construct = construct,
  291. on_destruct = destruct,
  292. on_punch = check_text,
  293. on_receive_fields = receive_fields
  294. })
  295. minetest.register_node("signs:wall_sign", {
  296. tiles = {"signs_wood.png"},
  297. drawtype = "nodebox",
  298. paramtype = "light",
  299. paramtype2 = "wallmounted",
  300. node_box = {
  301. type = "wallmounted",
  302. wall_side = {-0.5, -0.313, -0.438, -0.438, 0.313, 0.438}
  303. },
  304. drop = "signs:sign",
  305. walkable = false,
  306. groups = {oddly_breakable_by_hand = 1, choppy = 3,
  307. not_in_creative_inventory = 1, attached_node = 1},
  308. on_construct = construct,
  309. on_destruct = destruct,
  310. on_punch = check_text,
  311. on_receive_fields = function(pos, _, fields, sender)
  312. receive_fields(pos, _, fields, sender, true)
  313. end
  314. })
  315. minetest.register_craft({
  316. output = "signs:sign 3",
  317. recipe = {
  318. {"group:wood", "group:wood", "group:wood"},
  319. {"group:wood", "group:wood", "group:wood"},
  320. {"", "group:stick", ""}
  321. }
  322. })
  323. minetest.register_craft({
  324. type = "fuel",
  325. recipe = "signs:sign",
  326. burntime = 10
  327. })