Here is the place file if you want to check it out
Thruster.rbxl (64.1 KB)
I have the tool structure as shown.
Here is the Server script
local tool = script.Parent
local thrusterEvent = tool:WaitForChild("ThrusterRequest")
local thrusterTemplate = tool:WaitForChild("Thruster"):Clone()
local thrusterTemplateScale = 1
for _,i in pairs(thrusterTemplate:GetDescendants()) do
if i:IsA("BasePart") then
i.CanCollide = false
end
end
thrusterTemplate:ScaleTo(thrusterTemplateScale) --because the thruster may be smaller in hand than actually placed
thrusterEvent.OnServerEvent:Connect(function(player, ServerData)
if ServerData then
local target = ServerData.Instance
local c0 = ServerData.C0
local thruster = thrusterTemplate:Clone()
local cf = target.CFrame:ToWorldSpace(c0)
thruster:PivotTo(cf)
local weld = Instance.new("Weld")
weld.Part0 = target
weld.Part1 = thruster.PrimaryPart
weld.C0 = c0
weld.C1 = thruster.PrimaryPart.PivotOffset
weld.Parent = thruster
thruster.Parent = target
end
end)
Here is the Client script
local RunService = game:GetService("RunService")
local tool = script.Parent
local player = game.Players.LocalPlayer
local mouse = player:GetMouse()
local thrusterEvent = tool:WaitForChild("ThrusterRequest")
local ServerData = nil
--setup marker
local Marker = tool:FindFirstChild("Thruster"):Clone()
local markerScale = 1
for _,i in pairs(Marker:GetDescendants()) do
if i:IsA("BasePart") then
i.Transparency = .5
i.CanCollide = false
i.Massless = true
end
end
Marker:ScaleTo(markerScale) --because the thruster may be smaller in hand than actually placed
local RunServiceConnection = nil
function Cleanup()
if RunServiceConnection then
RunServiceConnection:Disconnect()
RunServiceConnection = nil
end
end
local function getRotationBetween(u, v, axis)
local dot, uxv = u:Dot(v), u:Cross(v)
if (dot < -0.99999) then return CFrame.fromAxisAngle(axis, math.pi) end
return CFrame.new(0, 0, 0, uxv.x, uxv.y, uxv.z, 1 + dot)
end
local function getSurfaceCF(part, lnormal)
local transition = getRotationBetween(Vector3.yAxis, lnormal, Vector3.zAxis)
return part.CFrame * transition
end
local RotationMode = "Normal" --Could be "Normal" "Character" "Camera"
function PlaceInstance(material,normal,inst,position)
local cf = CFrame.new(position) --cframe is set by position by default
if inst then --if we have an instance, then we can use that to get the proper cframe
if RotationMode ~= "Normal" then
normal = normal * Vector3.new(0,1,0)
end
cf = getSurfaceCF(inst,inst.CFrame:VectorToObjectSpace(normal))
cf = (cf-cf.Position)+position
end
if RotationMode == "Player" then
local target = (player.Character.HumanoidRootPart.Position * Vector3.new(1,0,1) ) + Vector3.new(0,position.Y,0)
cf = CFrame.lookAt(position,target,Vector3.yAxis)
end
--apply any extra rotation
local extraRotation = nil
local rotation = extraRotation or Vector3.new(0,0,0)
cf = cf * CFrame.Angles(math.rad(rotation.X),math.rad(rotation.Y),math.rad(rotation.Z))
if inst then
ServerData = {
Instance = inst,
C0 = inst.CFrame:ToObjectSpace(cf),
CF = cf
}
end
Marker:PivotTo(cf)
end
local PlaceMode = "Mouse" --can be Mouse or Camera (Camera is best for first person)
local Mouse = player:GetMouse()
local raycastParams = RaycastParams.new()
local function onRenderStep(deltaTime)
if on and Marker then
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
raycastParams.IgnoreWater = true
local filterList = {Marker,player.Character}
local ray = nil
if PlaceMode == "Camera" then
ray = Ray.new(workspace.CurrentCamera.CFrame.Position, workspace.CurrentCamera.CFrame.LookVector)
else
ray = Mouse.UnitRay
end
raycastParams.FilterDescendantsInstances = filterList
local raycastResult = workspace:Raycast(ray.Origin, ray.Direction*100, raycastParams)
if raycastResult then
local inst = raycastResult.Instance
PlaceInstance(raycastResult.Material,raycastResult.Normal,raycastResult.Instance,raycastResult.Position)
else
PlaceInstance(nil,Vector3.yAxis,nil,ray.Origin + (ray.Direction*100))
end
end
end
tool.Equipped:Connect(function()
Cleanup()
Marker.Parent = workspace
on = true
RunServiceConnection = RunService.RenderStepped:Connect(onRenderStep)
end)
tool.Unequipped:Connect(function()
Cleanup()
on = false
Marker.Parent = nil
end)
tool.Activated:Connect(function()
if on and Marker and Marker.Parent then
if ServerData then
thrusterEvent:FireServer(ServerData)
end
end
end)
The client gets the position of where to attach the thruster, but it only sends the offset (C0) of the thing you are attaching it to (Instance) to the server, so that the server can make the weld and place the actual part. So if the object have moved from the time you click to the time the server gets the message (due to lag) the server can accurately place the thruster.
Also, the tool has the âThrusterâ at a scale of .5, so it shows up easily in hand, then the client and server scripts clone this model, and scale it back up to a scale of 1.0 so it can be used as a marker and placed as the instance at normal scale.
Hope this helps you out.