125 lines
3.9 KiB
Lua
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)
|