527 lines
12 KiB
Lua

local S = minetest.get_translator_auto(true)
local pi = math.pi
local upper = string.upper
local tconcat = table.concat
local vadd = vector.add
local b = "blank.png"
local esc = minetest.formspec_escape
local function obj_inside_radius(p)
return minetest.get_objects_inside_radius(p, 0.5)
end
local ENTITY = "signs:sign_text"
-- 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, z = 0}, pi * 0.5},
[1] = {{x = -0.43, y = 0, z = 0}, pi * 1.5},
[2] = {{x = 0, y = 0, z = 0.43}, pi},
[3] = {{x = 0, y = 0, z = -0.43}, 0}
}
local font = {width = 16, height = 20}
local lenght, rows = 20, 5
local colors_list = {
"Black", "Silver", "Gray", "White",
"Maroon", "Red", "Purple", "Fuchsia",
"Green", "Lime", "Olive", "Yellow",
"Navy", "Blue", "Teal", "Aqua"
}
local function generate_sign_line_texture(str, row)
local leftover = (lenght - #str) * font.width / 2
row = (row - 0.85) * 0.85
local texture = {}
for i = 1, lenght do
local byte = str:byte(i)
if not byte or byte < 32 or byte > 126 then
byte = 32 -- space
end
texture[#texture + 1] = (":%u,%u=signs_%u.png"):format(
font.width * (i - 1) + leftover,
font.height * row + (font.height / rows), byte)
end
return tconcat(texture)
end
local function find_any(str, pair, start)
local ret = 0 -- 0 if not found (indices start at 1)
for _, needle in ipairs(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, color)
local result = {}
-- Transliterate text
str = slugify(str)
while #str > 0 do
if #result >= (rows) 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 > lenght then
if keep_i > 1 then
wrap_i = keep_i
else
wrap_i = lenght
end
break
elseif wrap_i == 0 then
if #str <= lenght 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 > lenght then
wrap_i = lenght
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)
if line_string ~= "" then
result[#result + 1] = line_string
end
str = str:sub(wrap_i + 1)
end
local empty = 0
if #result == 1 then
empty = 2
elseif #result == (rows - 1) then
empty = 0.5
elseif #result < (rows - 1) then
empty = 1
end
-- Generate texture modifier
local texture = {
("[combine:%ux%u"):format(font.width * lenght, font.height * rows)
}
for r, s in ipairs(result) do
texture[#texture + 1] = generate_sign_line_texture(s, r + empty)
end
if color and color ~= "" then
texture[#texture + 1] = "^[colorize:" .. color
end
return tconcat(texture)
end
minetest.register_entity(ENTITY, {
visual = "upright_sprite",
visual_size = {x = 0.7, y = 0.7},
textures = {b, b},
collisionbox = {0},
physical = 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
local meta_color = meta:get_string("sign_color")
texture = generate_sign_texture(meta_text, meta_color)
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 = result})
end
end
return itemstack
end
local function destruct(pos)
for _, obj in ipairs(obj_inside_radius(pos)) do
local ent = obj:get_luaentity()
if ent and ent.name == ENTITY then
obj:remove()
end
end
end
local function check_text(pos)
local meta = minetest.get_meta(pos)
local text = meta:get_string("sign_text")
if text and text ~= "" then
local count = 0
for _, obj in ipairs(obj_inside_radius(pos)) do
local ent = obj:get_luaentity()
if ent and ent.name == ENTITY 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 sign = minetest.add_entity(vadd(pos, sign_pos[p2][1]), ENTITY)
if not sign then
return
end
sign:set_yaw(sign_pos[p2][2])
end
else
for _, obj in ipairs(obj_inside_radius(pos)) do
local ent = obj:get_luaentity()
if ent and ent.name == ENTITY then
obj:remove()
end
end
end
-- Remove old node meta
local old_meta = {"formspec", "sign_texture2", "sign_texture3"}
for _, field in ipairs(old_meta) do
meta:set_string(field, "")
end
end
local function edit_text(pos, _, clicker)
local player_name = clicker:get_player_name()
local meta = minetest.get_meta(pos)
local text = esc(meta:get_string("sign_text"))
local edit_fs = "size[5,3.4]" ..
"background[0,0;0,0;formspec_background_color.png^formspec_backround.png;true]"
if not minetest.is_protected(pos, player_name) then
local ccolor = meta:get_string("sign_color")
if ccolor then
ccolor = ccolor:gsub("^%l", upper)
end
local clist = {}
local csel = 1
for i, clr in ipairs(colors_list) do
if ccolor == clr then
csel = i
end
clist[#clist + 1] = clr
end
clist = tconcat(clist, ",")
edit_fs = edit_fs ..
"textarea[1.15,0.2;3.3,2;Dtext;" ..
S("Enter your text:") .. ";" .. text .. "]" ..
"dropdown[0.86,1.93;3.36;color;" .. clist .. ";" .. csel .. "]" ..
"button_exit[0.86,2.66;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.2;3.3,2.7;read_only;" ..
S("Sign") .. ":;" .. text .. "]" ..
"button_exit[0.86,2.66;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 pos = fields.spos and minetest.string_to_pos(fields.spos)
if not pos then
return
end
local meta = minetest.get_meta(pos)
local text = fields.Dtext
local color = fields.color
if not text and not color then
return
elseif not text then
text = meta:get_string("sign_text")
end
if color then
color = color:lower()
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 ipairs(obj_inside_radius(pos)) do
local ent = obj:get_luaentity()
if ent and ent.name == ENTITY then
sign = obj
break
end
end
if not sign then
sign = minetest.add_entity(vadd(pos, sign_pos[p2][1]), ENTITY)
else
sign:set_pos(vadd(pos, sign_pos[p2][1]))
end
if not sign then
return
end
local texture = b
if text and 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, color)
sign:set_properties({
textures = {texture, b}
})
end
sign:set_yaw(sign_pos[p2][2])
meta:set_string("sign_text", text)
meta:set_string("sign_texture", texture)
meta:set_string("sign_color", color)
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",
use_texture_alpha = "clip",
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,
mesecon = {on_mvps_move = check_text},
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"},
use_texture_alpha = "clip",
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, attached_node = 1,
not_in_creative_inventory = 1},
on_rotate = false,
mesecon = {on_mvps_move = check_text},
mvps_sticky = function(pos, node)
local dir = minetest.wallmounted_to_dir(node.param2)
return {vadd(pos, dir)}
end,
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:check_text",
nodenames = {"signs:sign", "signs:wall_sign"},
run_at_every_load = true,
action = check_text
})
if mesecon and mesecon.register_mvps_stopper then
mesecon.register_mvps_unmov(ENTITY)
end