Hey, and thanks for reading in advance.
One of the classes in the small CTF-style game I’m developing is capable of raising a shield to block all frontal attacks, negating their damage entirely. To be clear, I already know multiple ways of determining whether or not a position is ‘behind’ a player relative to their position/facing direction - that’s not the issue. I’m using a dot product and angular comparison to make this determination.
That said - the issue I’m having is the function returning false-positives due to latency - allowing my attacks to damage someone that is clearly blocking and clearly facing me.
For debugging, I’ve made the function create two parts - a blue box representing the server’s idea of the target’s HumanoidRootPart position, and another part placed at the position passed to the check that is either red or green depending on which result the ‘behind’ check returned - red for negative, green for positive.
This is the result of a particular test seen from my end. The blue box representing the Root position is close to perfectly accurate, and although obscured by my opponent’s model, a green part representing the position of the arrow’s raycast is behind his torso, indicating a successful ‘behind’ check even though my opponent is facing me and very clearly blocking.
This is the result of another test conducted, shown from my opponent’s perspective:
External MediaYou can very clearly see here that even accounting for the built-in lag compensation, the server’s idea of where my opponent is does not match his own perspective whatsoever, and it becomes obvious why the server thinks my shot is behind him.
If anyone knows what I might do to alleviate this or spots a mistake I’ve made, I’d appreciate the info.
Code (inside a server-side module):
function Core:IsBehind(CF, Target)
local RUN = game:GetService("RunService")
local Root = Target:FindFirstChild("HumanoidRootPart")
if Root then
local Player = game.Players:GetPlayerFromCharacter(Target)
local Latency = (Player and Player:GetAttribute("Latency")) or 0
local GuessPosition = Root.Position - (Root.AssemblyLinearVelocity * Latency)
local GuessCF = CFrame.new(GuessPosition, GuessPosition + Root.CFrame.LookVector)
local PassCheck = math.acos(GuessCF.LookVector:Dot((CF.Position - GuessPosition).Unit)) >= math.pi/2
--
local Marker = Instance.new("Part")
Marker.Size = Vector3.new(1, 1, 1)
Marker.Anchored = true; Marker.CanCollide = false; Marker.CanQuery = false
Marker.Color = (PassCheck and Color3.new(0, 1, 0)) or Color3.new(1, 0, 0)
Marker.Material = Enum.Material.Neon
Marker.Transparency = .5
Marker.CastShadow = false
Marker.Name = tostring(CF.Position)
Marker.CFrame = CF
Marker.Parent = workspace
game.Debris:AddItem(Marker, 2)
--
--
local FakeRoot = Instance.new("Part")
FakeRoot.Size = Root.Size
FakeRoot.Anchored = true; Marker.CanCollide = false; Marker.CanQuery = false
FakeRoot.Color = Color3.new(0, 0, 1)
FakeRoot.Material = Enum.Material.Neon
FakeRoot.Transparency = .5
FakeRoot.CastShadow = false
FakeRoot.Name = "FakeRoot"
FakeRoot.CFrame = GuessCF
FakeRoot.Parent = workspace
game.Debris:AddItem(FakeRoot, 2)
--]]
return PassCheck
end
return false
end