527 lines
12 KiB
Lua
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
|