2023-12-02 16:41:24 -05:00

309 lines
8.2 KiB
Lua

-- LUALOCALS < ---------------------------------------------------------
local error, ipairs, minetest, pairs, string, table, tonumber, type
= error, ipairs, minetest, pairs, string, table, tonumber, type
local string_format, table_concat, table_remove, table_sort
= string.format, table.concat, table.remove, table.sort
-- LUALOCALS > ---------------------------------------------------------
local modname = minetest.get_current_modname()
local worldkey = minetest.settings:get(modname .. "_key") or ""
if #worldkey < 1 then error(modname .. "_key must be set!") end
local recent = tonumber(minetest.settings:get(modname .. "_recent")) or 5
local function tcencode(pos)
pos = vector.round(pos)
local hash = minetest.hash_node_position(pos)
hash = string_format("%x", hash)
while #hash < 12 do hash = "0" .. hash end
local cksum = minetest.sha1(worldkey .. hash):sub(1, 4)
local enckey = minetest.sha1(worldkey .. cksum)
local chars = {}
for i = 1, 12 do
chars[#chars + 1] = string_format("%x",
tonumber(enckey:sub(i, i), 16)
+ tonumber(hash:sub(i, i), 16))
:sub(-1)
end
hash = table_concat(chars)
return hash:sub(1, 4) .. "-" .. hash:sub(5, 8) .. "-"
.. hash:sub(9, 12) .. "-" .. cksum
end
local function tcdecode(str)
if not str or type(str) ~= "string" then return end
str = str:gsub("-", "")
if not str:match("^[0-9a-f]*$") then return end
if #str ~= 16 then return end
local hash = str:sub(1, 12)
local cksum = str:sub(13, 16)
local enckey = minetest.sha1(worldkey .. cksum)
local chars = {}
for i = 1, 12 do
chars[#chars + 1] = string_format("%x",
16 + tonumber(hash:sub(i, i), 16)
- tonumber(enckey:sub(i, i), 16))
:sub(-1)
end
hash = table_concat(chars)
local mysum = minetest.sha1(worldkey .. hash):sub(1, 4)
if mysum ~= cksum then return end
hash = tonumber(hash, 16)
return minetest.get_position_from_hash(hash)
end
local function bookmarks(player)
local data = player:get_meta():get_string(modname) or ""
data = data ~= "" and minetest.deserialize(data) or {}
return data, function()
player:get_meta():set_string(modname, minetest.serialize(data))
end
end
local function bkfind(player, str)
local data = bookmarks(player)
if not data then return nil, 'no match' end
if str ~= "" and data[str] then return {{k = str, v = data[str]}} end
local found = {}
for k, v in pairs(data) do
if k:sub(1, #str) == str then found[#found + 1] = {k = k, v = v} end
end
if #found > 0 then return found end
local pat = str:gsub("([^%w])", "%%%1")
for k, v in pairs(data) do
if k:match(pat) then found[#found + 1] = {k = k, v = v} end
end
return found
end
local function tcfind(player, str)
local pos = tcdecode(str)
if pos then return pos end
local found = bkfind(player, str)
if #found == 1 then return found[1].v end
if #found > 1 then return nil, 'ambiguous' end
return nil, 'no match'
end
local function tccatch(player, str)
local data, save, known, dirty
local words = str:split(' ')
for i = 1, #words do
local pos = tcdecode(words[i])
if pos then
if not data then
data, save = bookmarks(player)
known = {}
for _, v in pairs(data) do
known[minetest.pos_to_string(v)] = true
end
end
local key = minetest.pos_to_string(pos)
if not known[key] then
known[key] = true
if data["^" .. recent] then
known[minetest.pos_to_string(data["^" .. recent])] = nil
end
for j = recent, 3, -1 do
data["^" .. j] = data["^" .. (j - 1)]
end
data["^2"] = data["^"]
data["^"] = pos
dirty = true
end
end
end
if dirty then save() end
end
local old_all = minetest.chat_send_all
function minetest.chat_send_all(msg, ...)
if msg and type(msg) == "string" then
for _, player in pairs(minetest.get_connected_players()) do
tccatch(player, msg)
end
end
return old_all(msg, ...)
end
local old_send = minetest.chat_send_player
function minetest.chat_send_player(pname, msg, ...)
if msg and type(msg) == "string" then
local player = minetest.get_player_by_name(pname)
if player then
tccatch(player, msg)
end
end
return old_send(pname, msg, ...)
end
local function poof(pos)
minetest.add_particlespawner({
amount = 200,
time = 0.05,
minpos = {x = pos.x - 0.5, y = pos.y - 0.5, z = pos.z - 0.5},
maxpos = {x = pos.x + 0.5, y = pos.y + 1.5, z = pos.z + 0.5},
minacc = {x = 0, y = 0, z = 0},
maxacc = {x = 0, y = 0, z = 0},
minvel = {x = -2, y = -2, z = -2},
maxvel = {x = 2, y = 2, z = 2},
minexptime = 0.5,
maxexptime = 2,
minsize = 0.25,
maxsize = 1,
texture = "szutil_telecode.png"
})
minetest.sound_play("szutil_telecode", {
pos = {x = pos.x, y = pos.y + 1, z = pos.z},
gain = 2
})
end
minetest.register_chatcommand("tc", {
description = "Teleport by telecode",
params = "[telecode]",
func = function(pname, param)
local player = minetest.get_player_by_name(pname)
if not player then return false, "must be in game world" end
if player:get_attach() then
return false, "cannot teleport while attached"
end
param = param or ""
if param == "" then
return true, "telecode for your location: " .. tcencode(player:get_pos())
end
local pos, err = tcfind(player, param)
if not pos then
return false, "telecode not found: " .. err
end
local opos = player:get_pos()
tccatch(player, tcencode(opos))
poof(opos)
player:set_pos(pos)
poof(pos)
return true
end
})
minetest.register_chatcommand("tcsave", {
description = "Save telecode bookmark",
params = "<name> [telecode]",
func = function(pname, param)
local player = minetest.get_player_by_name(pname)
if not player then return false, "must be in game world" end
local words = param:split(' ')
for i = #words, 1, -1 do
if not words[i]:match("%S") then
table_remove(words, i)
end
end
if #words < 1 then return false, "name required" end
local pos = words[#words]:match("^%^+$")
and tcfind(player, words[#words])
or tcdecode(words[#words])
if pos then
words[#words] = nil
else
pos = vector.round(player:get_pos())
end
local keyname = table_concat(words, " ")
if not keyname:match("%S") then
return false, "name cannot be blank"
end
local data, save = bookmarks(player)
data[keyname] = pos
save()
return true, tcencode(pos) .. " saved as " .. keyname
end
})
minetest.register_chatcommand("tcls", {
description = "List telecode bookmarks",
params = "<search>",
func = function(pname, param)
local player = minetest.get_player_by_name(pname)
if not player then return false, "must be in game world" end
local found = bkfind(player, param)
if #found < 1 then return false, "no match found" end
table_sort(found, function(a, b) return a.k < b.k end)
for _, e in ipairs(found) do
minetest.chat_send_player(pname, "- " .. tcencode(e.v)
.. ": " .. e.k)
end
end
})
minetest.register_chatcommand("tcrm", {
description = "Remove telecode bookmark",
params = "<name>",
func = function(pname, param)
local player = minetest.get_player_by_name(pname)
if not player then return false, "must be in game world" end
local data, save = bookmarks(player)
if not data[param] then return false, "bookmark not found" end
data[param] = nil
save()
end
})
minetest.register_chatcommand("tcsend", {
description = "Share telecode",
params = "<*|player> [search]",
func = function(pname, param)
local player = minetest.get_player_by_name(pname)
if not player then return false, "must be in game world" end
local dest, name = param:match("(%S+)%s*(.*)")
local send
if dest == "*" then
send = function(t)
return minetest.chat_send_all("telecode shared by "
.. pname .. ": " .. t)
end
elseif dest then
local targ = minetest.get_player_by_name(dest)
if not targ then return false, "player not found" end
send = function(t)
return minetest.chat_send_player(dest,
"telecode privately sent by " .. pname .. ": " .. t),
"telecode sent"
end
else
return false, "share target not specified"
end
local pos
if name and name ~= "" then
pos = tcfind(player, name)
if not pos then return false, "location not found" end
else
pos = vector.round(player:get_pos())
end
return send(tcencode(pos))
end
})