2021-07-13 19:17:21 +03:00

462 lines
11 KiB
Lua

local translator = minetest.get_translator
local S = translator and translator("signs") or intllib.make_gettext_pair()
if translator and not minetest.is_singleplayer() then
local lang = minetest.settings:get("language")
if lang and lang == "ru" then
S = intllib.make_gettext_pair()
end
end
local floor, pi = math.floor, math.pi
local vadd = vector.add
local b = "blank.png"
local esc = minetest.formspec_escape
local function objects_inside_radius(p)
return minetest.get_objects_inside_radius(p, 0.5)
end
-- Cyrillic transliteration library
local slugify = dofile(minetest.get_modpath("signs") .. "/slugify.lua")
local sign_positions = {
[0] = {{x = 0, y = 0.18, z = -0.07}, pi},
[1] = {{x = -0.07, y = 0.18, z = 0}, pi * 0.5},
[2] = {{x = 0, y = 0.18, z = 0.07}, 0},
[3] = {{x = 0.07, y = 0.18, z = 0}, pi * 1.5}
}
local wall_sign_positions = {
[0] = {{x = 0.43, y = -0.005, z = 0}, pi * 0.5},
[1] = {{x = -0.43, y = -0.005, z = 0}, pi * 1.5},
[2] = {{x = 0, y = -0.005, z = 0.43}, pi},
[3] = {{x = 0, y = -0.005, z = -0.43}, 0}
}
local function generate_sign_line_texture(str, row)
local leftover = floor((20 - #str) * 16 / 2)
local texture = ""
for i = 1, 20 do
local char = str:byte(i)
if char and (char >= 32 and char <= 126) then
texture = texture .. ":" .. (i - 1) * 16 + leftover .. ","
.. row * 20 .. "=signs_" .. char .. ".png"
end
end
return texture
end
local function find_any(str, pair, start)
local ret = 0 -- 0 if not found (indices start at 1)
for _, needle in pairs(pair) do
local first = str:find(needle, start)
if first then
if ret == 0 or first < ret then
ret = first
end
end
end
return ret
end
local disposable_chars = {
["\n"] = true, ["\r"] = true, ["\t"] = true, [" "] = true
}
local wrap_chars = {
"\n", "\r", "\t", " ", "-", "/", ";", ":", ",", ".", "?", "!"
}
local function generate_sign_texture(str)
local row = 0
local texture = "[combine:" .. 16 * 20 .. "x100"
local result = {}
-- Transliterate text
str = slugify(str)
while #str > 0 do
if row > 4 then
break
end
local wrap_i = 0
local keep_i = 0 -- The last character that was kept
while wrap_i < #str do
wrap_i = find_any(str, wrap_chars, wrap_i + 1)
if wrap_i > 20 then
if keep_i > 1 then
wrap_i = keep_i
else
wrap_i = 20
end
break
elseif wrap_i == 0 then
if #str <= 20 then
wrap_i = #str
elseif keep_i > 0 then
wrap_i = keep_i
else
wrap_i = #str
end
break
elseif str:sub(wrap_i, wrap_i) == "\n" then
break
end
if not disposable_chars[str:sub(wrap_i, wrap_i)] then
keep_i = wrap_i
elseif wrap_i > 1 and
not disposable_chars[str:sub(wrap_i - 1, wrap_i - 1)] then
keep_i = wrap_i - 1
end
end
if wrap_i > 20 then
wrap_i = 20
end
local start_remove = 0
if disposable_chars[str:sub(1, 1)] then
start_remove = 1
end
local end_remove = 0
if disposable_chars[str:sub(wrap_i, wrap_i)] then
end_remove = 1
end
local line_string = str:sub(1 + start_remove, wrap_i - end_remove)
str = str:sub(wrap_i + 1)
if line_string ~= "" then
result[row] = line_string
end
row = row + 1
end
local empty = 0
if row == 1 then
empty = 2
elseif row < 4 then
empty = 1
end
for r, s in pairs(result) do
texture = texture .. generate_sign_line_texture(s, r + empty)
end
return texture
end
minetest.register_entity("signs:sign_text", {
visual = "upright_sprite",
visual_size = {x = 0.7, y = 0.6},
collisionbox = {0},
pointable = false,
on_activate = function(self)
local ent = self.object
local pos = ent:get_pos()
-- remove entity for missing sign
local node_name = minetest.get_node(pos).name
if node_name ~= "signs:sign" and
node_name ~= "signs:wall_sign" then
ent:remove()
return
end
local meta = minetest.get_meta(pos)
local meta_texture = meta:get_string("sign_texture")
local texture
if meta_texture and meta_texture ~= "" then
texture = meta_texture
else
local meta_text = meta:get_string("sign_text")
if meta_text and meta_text ~= "" then
texture = generate_sign_texture(meta_text)
else
texture = b
end
meta:set_string("sign_texture", texture)
end
ent:set_properties({
textures = {texture, b}
})
end
})
local function place(itemstack, placer, pointed_thing)
if pointed_thing.type == "node" then
local under = pointed_thing.under
local node = minetest.get_node(under)
local node_def = minetest.registered_nodes[node.name]
if node_def and node_def.on_rightclick and
not (placer and placer:is_player() and
placer:get_player_control().sneak) then
return node_def.on_rightclick(under, node, placer, itemstack,
pointed_thing) or itemstack
end
local undery = pointed_thing.under.y
local posy = pointed_thing.above.y
local _, result
if undery < posy then -- Floor sign
itemstack, result = minetest.item_place(itemstack,
placer, pointed_thing)
elseif undery == posy then -- Wall sign
_, result = minetest.item_place(ItemStack("signs:wall_sign"),
placer, pointed_thing)
if result and not
minetest.is_creative_enabled(placer:get_player_name()) then
itemstack:take_item()
end
end
if result then
minetest.sound_play({name = "default_place_node_hard"},
{pos = pointed_thing.above})
end
end
return itemstack
end
local function destruct(pos)
for _, obj in pairs(objects_inside_radius(pos)) do
local ent = obj:get_luaentity()
if ent and ent.name == "signs:sign_text" then
obj:remove()
end
end
end
local function check_text(pos)
local meta = minetest.get_meta(pos)
local text = meta:get_string("sign_text")
local objects = objects_inside_radius(pos)
if text and text ~= "" then
local count = 0
for _, obj in pairs(objects) do
local ent = obj:get_luaentity()
if ent and ent.name == "signs:sign_text" then
count = count + 1
if count > 1 then
obj:remove()
end
end
end
if count == 0 then
local node = minetest.get_node(pos)
local p2 = node.param2 or -1
local sign_pos = sign_positions
if node.name == "signs:wall_sign" then
p2 = p2 - 2
sign_pos = wall_sign_positions
end
if p2 > 3 or p2 < 0 then return end
local obj = minetest.add_entity(
vadd(pos, sign_pos[p2][1]), "signs:sign_text")
obj:set_yaw(sign_pos[p2][2])
end
else
for _, obj in pairs(objects) do
local ent = obj:get_luaentity()
if ent and ent.name == "signs:sign_text" then
obj:remove()
end
end
end
-- Remove old on_construct fs
local fs = meta:get_string("formspec")
if fs and fs ~= "" then
meta:set_string("formspec", "")
end
end
local function edit_text(pos, _, clicker)
local player_name = clicker:get_player_name()
local text = esc(minetest.get_meta(pos):get_string("sign_text"))
local edit_fs = "size[5,3]" ..
"background[0,0;0,0;formspec_background_color.png^" ..
"formspec_backround.png;true]"
if not minetest.is_protected(pos, player_name) then
edit_fs = edit_fs ..
"textarea[1.15,0.3;3.3,2;Dtext;" ..
S("Enter your text:") .. ";" .. text .. "]" ..
"button_exit[0.85,2;3.3,1;;" .. S("Save") .. "]" ..
"field[0,0;0,0;spos;;" .. minetest.pos_to_string(pos) .. "]"
else
edit_fs = edit_fs ..
"textarea[1.15,0.3;3.3,2;read_only;" ..
S("Sign") .. ":;" .. text .. "]" ..
"button_exit[0.85,2;3.3,1;;" .. S("Close") .. "]"
end
minetest.show_formspec(player_name, "signs:edit_text", edit_fs)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "signs:edit_text" then
return
end
local text = fields.Dtext
local pos = fields.spos and minetest.string_to_pos(fields.spos)
if not text or not pos then
return
end
if minetest.is_protected(pos, player:get_player_name()) then
return
end
local node = minetest.get_node(pos)
local p2 = node.param2
local sign_pos = sign_positions
if node.name:find("wall") then
p2 = p2 - 2
sign_pos = wall_sign_positions
end
if not p2 or p2 > 3 or p2 < 0 then
return
end
local sign
for _, obj in pairs(objects_inside_radius(pos)) do
local ent = obj:get_luaentity()
if ent and ent.name == "signs:sign_text" then
sign = obj
break
end
end
if not sign then
sign = minetest.add_entity(
vadd(pos, sign_pos[p2][1]), "signs:sign_text")
else
sign:set_pos(vadd(pos, sign_pos[p2][1]))
end
if not sign then
return
end
local texture = b
if text ~= "" then
-- Serialization longer values may cause a crash
-- because we are serializing the texture too
text = text:sub(1, 256)
texture = generate_sign_texture(text)
sign:set_properties({
textures = {texture, b}
})
end
sign:set_yaw(sign_pos[p2][2])
local meta = minetest.get_meta(pos)
meta:set_string("sign_text", text)
meta:set_string("sign_texture", texture)
meta:set_string("infotext", text)
end)
-- Sign nodes
minetest.register_node("signs:sign", {
description = S("Sign"),
tiles = {
"signs_top.png", "signs_top.png", "signs_top.png",
"signs_top.png", "signs_sign.png", "signs_sign.png"
},
inventory_image = "signs_item.png",
wield_image = "signs_item.png",
drawtype = "nodebox",
paramtype = "light",
paramtype2 = "facedir",
node_placement_prediction = "",
sunlight_propagates = true,
node_box = {
type = "fixed",
fixed = {
{-0.375, -0.125, -0.063, 0.375, 0.5, 0.063},
{-0.063, -0.5, -0.063, 0.063, -0.125, 0.063}
}
},
groups = {oddly_breakable_by_hand = 1, choppy = 3, attached_node = 1},
on_rotate = function(pos, node, user, mode)
local pn = user and user:get_player_name() or ""
if not minetest.is_protected(pos, pn) and mode == 1 then
node.param2 = (node.param2 % 8) + 1
if node.param2 > 3 then
node.param2 = 0
end
minetest.swap_node(pos, node)
-- Checks can be skipped if there is no text
local meta = minetest.get_meta(pos)
local text = meta:get_string("sign_text")
if text and text ~= "" then
destruct(pos)
check_text(pos)
end
return true
end
return false
end,
on_place = place,
on_destruct = destruct,
on_punch = check_text,
on_rightclick = edit_text
})
minetest.register_node("signs:wall_sign", {
tiles = {"signs_wall_sign.png"},
drawtype = "nodebox",
paramtype = "light",
paramtype2 = "wallmounted",
node_box = {
type = "wallmounted",
wall_side = {-0.5, -0.313, -0.438, -0.438, 0.313, 0.438}
},
drop = "signs:sign",
walkable = false,
groups = {oddly_breakable_by_hand = 1, choppy = 3,
not_in_creative_inventory = 1, attached_node = 1},
on_rotate = false,
on_destruct = destruct,
on_punch = check_text,
on_rightclick = edit_text
})
-- Craft
minetest.register_craft({
output = "signs:sign 3",
recipe = {
{"group:wood", "group:wood", "group:wood"},
{"group:wood", "group:wood", "group:wood"},
{"", "default:stick", ""}
}
})
minetest.register_craft({
type = "fuel",
recipe = "signs:sign",
burntime = 10
})
-- LBM for restoring text
minetest.register_lbm({
label = "Check for sign text",
name = "signs:sign_text",
nodenames = {"signs:sign", "signs:wall_sign"},
run_at_every_load = true,
action = check_text
})