2024-07-27 23:26:11 -04:00

125 lines
3.9 KiB
Lua

-- LUALOCALS < ---------------------------------------------------------
local getmetatable, minetest, pairs, string, tonumber, type
= getmetatable, minetest, pairs, string, tonumber, type
local string_format
= string.format
-- LUALOCALS > ---------------------------------------------------------
if minetest.features._hotfix_teleport_retry then return end
minetest.features._hotfix_teleport_retry = true
------------------------------------------------------------------------
local modname = minetest.get_current_modname()
local function conf(n)
return minetest.settings:get(modname .. "_" .. n)
end
-- How fast the player is likely able to move under their own
-- power or other game mechanics
local max_likely_speed = tonumber(conf("speed")) or 50
-- Max seconds we will keep tracking a teleport for
local max_track_expire = tonumber(conf("maxtime")) or 60
-- How frequently we will check for and retry failed teleports
local teleport_retry_interval = tonumber(conf("interval")) or 0.5
-- Minimum teleport distance that will be retryable
local min_teleport_dist = tonumber(conf("mindist")) or 2
------------------------------------------------------------------------
local playerdata = {}
local function now() return minetest.get_us_time() / 1000000 end
local pstr = function(v) return minetest.pos_to_string(v, 0) end
local function pinfo(pname)
local info = minetest.get_player_information(pname)
if not (info.min_rtt and info.avg_rtt and info.max_rtt
and info.min_jitter and info.avg_jitter and info.max_jitter)
then return pname end
return string_format("%s (rtt %.2f/%.2f/%.2f jitter %.2f/%.2f/%.2f)",
pname, info.min_rtt, info.avg_rtt, info.max_rtt,
info.min_jitter, info.avg_jitter, info.max_jitter)
end
local raw_set_pos
local function check(player, pname, pdata)
if now() >= pdata.stamp + pdata.exp then
minetest.log("info", string_format("teleport tracking for %s expired",
pname))
playerdata[pname] = nil
return
end
if player:get_attach() then return end
local age = now() - pdata.stamp
if age < teleport_retry_interval then return end
local pos = player:get_pos()
local odist = vector.distance(pos, pdata.old)
local ndist = vector.distance(pos, pdata.pos)
if odist < ndist then
minetest.log("warning", string_format("correcting teleport regression of %s"
.. " found at %s, teleported from %s (%d m) to %s (%d m)"
.. " %.3f s ago",
pinfo(pname), pstr(pos), pstr(pdata.old), odist, pstr(pdata.pos),
ndist, now() - pdata.stamp))
pdata.stamp = now()
return raw_set_pos(player, pdata.pos)
end
end
minetest.register_globalstep(function()
for _, player in pairs(minetest.get_connected_players()) do
local pname = player:get_player_name()
local pdata = playerdata[pname]
if pdata then check(player, pname, pdata) end
end
end)
local function patchplayers()
local anyplayer = (minetest.get_connected_players())[1]
if not anyplayer then
return minetest.after(0, patchplayers)
end
local meta = getmetatable(anyplayer)
meta = meta and meta.__index or meta
if not meta.set_pos then return end
raw_set_pos = meta.set_pos
function meta:set_pos(pos, ...)
if (not self) or (not self.is_player) or (not self:is_player())
or (not pos) or type(pos) ~= "table" or (not pos.x) or (not pos.y)
or (not pos.z) then
return raw_set_pos(self, pos, ...)
end
local pname = self:get_player_name()
local old = self:get_pos()
local dist = vector.distance(old, pos)
if dist <= min_teleport_dist then
return raw_set_pos(self, pos, ...)
end
local exp = dist / max_likely_speed
if exp > max_track_expire then exp = max_track_expire end
minetest.log("action", string_format("teleport tracking for %s"
.. " from %s to %s, for %.3f s",
pname, pstr(old), pstr(pos), exp))
playerdata[pname] = {
old = old,
pos = pos,
stamp = now(),
exp = exp
}
return raw_set_pos(self, pos, ...)
end
minetest.log("info", "teleport regression detection player:set_pos hooked")
end
minetest.after(0, patchplayers)