hello roblox developers! as you can see in the title, npc positions are different on the client and server and this is a massive roadblock to the rest of my game, a fundamental mechanic, whatever you would like to call it. the reason for this is that i am trying to make a stealth game and i need the positions to stay the same for dragging unconcious npcs. I can’t have them be diffeent because then other alive npcs may see an unconcious npc in a different area than where the client sees them, if that makes sense. basically when i change the humanoidstatetype on the humanoid of the npc to “Physics” (on client or server) it does set it to physics, but when the npc falls it is different on the client and server because of the constraints that i have added to the npc on the client (it doesn’t work as well on server, otherwise problem solved) i should also note that the rig that i’m using is R6. here are the 2 scripts:
in my opinion i did not write this good, so chatgpt probably can explain it better:
When I change the HumanoidStateType of an NPC to “Physics” to enable dragging, it introduces discrepancies between the client and server. Despite successfully setting the state to physics, the NPCs’ positions differ upon falling due to constraints applied on the client side. This discrepancy poses a major obstacle as it can lead to inconsistencies where alive NPCs might see unconscious NPCs in different locations depending on the player’s perspective.
video showcasing:
anyways yeah, here’s the scripts:
server script:
local debris=game:GetService('Debris')
local event=game:GetService('ReplicatedStorage'):WaitForChild('Events'):WaitForChild('Ragdoll')
wait(4)
for i,v in pairs(workspace:GetChildren()) do
if v:IsA("Model") and v:FindFirstChildOfClass("Humanoid") and not game:GetService("Players"):GetPlayerFromCharacter(v) then
wait(.25)
v.Humanoid:ChangeState(Enum.HumanoidStateType.Physics)
local function push()
local velRange = math.random() < 0.5 and {-100, -80} or {80, 100}
local angularVelRange = math.random() < 0.5 and {-500, -90} or {90, 500}
v.Torso:ApplyImpulse(Vector3.new(math.random(velRange[1], velRange[2]), 0, math.random(velRange[1], velRange[2])))
v.Torso:ApplyAngularImpulse(Vector3.new(0,math.random(angularVelRange[1], angularVelRange[2]),0)) --(Vector3.new(math.random(angularVelRange[1], angularVelRange[2]), math.random(angularVelRange[1], angularVelRange[2]), math.random(angularVelRange[1], angularVelRange[2])))
end
push()
event:FireAllClients(v)
end
end
local script:
local player = game:GetService('Players').LocalPlayer
local Character = player.Character or player.CharacterAdded:Wait()
local debris = game:GetService('Debris')
local event = game:GetService('ReplicatedStorage'):WaitForChild('Events'):WaitForChild('Ragdoll')
Character:WaitForChild('Humanoid').Died:Connect(function()
event:FireServer(Character.Name)
end)
event.OnClientEvent:Connect(function(model)
local character = model
if not character then return end
if character.Name ~= Character.Name then
local attachmentCFrames = {
["Neck"] = {CFrame.new(0, 1, 0, 0, -1, 0, 1, 0, -0, 0, 0, 1), CFrame.new(0, -0.5, 0, 0, -1, 0, 1, 0, -0, 0, 0, 1)},
["Left Shoulder"] = {CFrame.new(-1.3, 0.75, 0, -1, 0, 0, 0, -1, 0, 0, 0, 1), CFrame.new(0.2, 0.75, 0, -1, 0, 0, 0, -1, 0, 0, 0, 1)},
["Right Shoulder"] = {CFrame.new(1.3, 0.75, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1), CFrame.new(-0.2, 0.75, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1)},
["Left Hip"] = {CFrame.new(-0.5, -1, 0, 0, 1, -0, -1, 0, 0, 0, 0, 1), CFrame.new(0, 1, 0, 0, 1, -0, -1, 0, 0, 0, 0, 1)},
["Right Hip"] = {CFrame.new(0.5, -1, 0, 0, 1, -0, -1, 0, 0, 0, 0, 1), CFrame.new(0, 1, 0, 0, 1, -0, -1, 0, 0, 0, 0, 1)},
}
character.Archivable = true
local clone = character:Clone()
clone.Name = ''
clone.HumanoidRootPart.CanCollide = false
local joints = clone:GetDescendants()
clone.Humanoid.WalkSpeed = 0
clone.Humanoid.JumpHeight = 0
clone.Humanoid.PlatformStand = true
clone.Humanoid.AutoRotate = false
character.Parent = game:GetService('ReplicatedStorage'):WaitForChild('RagdollCache')
debris:AddItem(character, 5)
clone.Parent = workspace
clone:FindFirstChild("Humanoid"):ChangeState(Enum.HumanoidStateType.Physics)
if clone:FindFirstChild('Clothing') then
for _, v in clone:FindFirstChild('Clothing'):GetDescendants() do
if v:IsA('BasePart') or v:IsA('UnionOperation') or v:IsA('MeshPart') then
v.CanCollide = false
end
end
end
local function push()
local velRange = math.random() < 0.5 and {-100, -80} or {80, 100}
local angularVelRange = math.random() < 0.5 and {-500, -90} or {90, 500}
clone.Torso:ApplyImpulse(Vector3.new(math.random(velRange[1], velRange[2]), 0, math.random(velRange[1], velRange[2])))
clone.Torso:ApplyAngularImpulse(Vector3.new(0,math.random(angularVelRange[1], angularVelRange[2]),0)) --(Vector3.new(math.random(angularVelRange[1], angularVelRange[2]), math.random(angularVelRange[1], angularVelRange[2]), math.random(angularVelRange[1], angularVelRange[2])))
end
local CPP = PhysicalProperties.new(3,.2,0.5)
for i,v in pairs(clone:GetChildren()) do
if v:IsA("BasePart") and not v.Name:find("Leg") then
v.CustomPhysicalProperties = CPP
end
end
push()
local function createColliderPart(part: BasePart)
if not part then return end
local rp = Instance.new("Part")
rp.Name = "ColliderPart"
rp.Size = part.Size/1.7
rp.Massless = true
rp.CFrame = part.CFrame
rp.Transparency = 1
local wc = Instance.new("WeldConstraint")
wc.Part0 = rp
wc.Part1 = part
wc.Parent = rp
rp.Parent = part
end
for _, motor in pairs(joints) do
if motor:IsA("Motor6D") then
if not attachmentCFrames[motor.Name] then return end
motor.Enabled = false;
local a0, a1 = Instance.new("Attachment"), Instance.new("Attachment")
a0.CFrame = attachmentCFrames[motor.Name][1]
a1.CFrame = attachmentCFrames[motor.Name][2]
a0.Name = "RagdollAttachment"
a1.Name = "RagdollAttachment"
createColliderPart(motor.Part1)
local b = Instance.new("BallSocketConstraint")
b.Attachment0 = a0
b.Attachment1 = a1
b.Name = "RagdollConstraint"
-- Set different constraint properties based on the joint
if motor.Name == "Neck" then
b.Radius = 0.1
b.LimitsEnabled = true
b.TwistLimitsEnabled = true
b.UpperAngle = 45
b.TwistLowerAngle = -70
b.TwistUpperAngle = 70
elseif motor.Name == "Left Shoulder" or motor.Name == "Right Shoulder" then
b.Radius = 0.2
b.LimitsEnabled = true
b.TwistLimitsEnabled = true
b.UpperAngle = 50
b.TwistLowerAngle = -30
b.TwistUpperAngle = 30
elseif motor.Name == "Left Hip" or motor.Name == "Right Hip" then
b.Radius = 0.3
b.LimitsEnabled = true
b.TwistLimitsEnabled = true
b.UpperAngle = 25
b.TwistLowerAngle = -30
b.TwistUpperAngle = 30
end
b.MaxFrictionTorque = 0
b.Restitution = 0
a0.Parent = motor.Part0
a1.Parent = motor.Part1
b.Parent = motor.Parent
end
end
end
end)
thanks very much for bothering to look at this topic, any and all help is greatly appreciated!