I’d argue that’s a bad way to do this, I’m aware this is a necropost, but I have a much better way to do this.
So, I do the raycasting on the client using @EgoMoose’s UserInputService Mouse module. (I’m a big fan of open source modules) I then fire a RemoteEvent which then fires all of the clients to render the bullets. There’s a tick for when the bullet is shot, and there’s a tick for when the bullet is finished. The bullet is also given a unique id that I use to check which tween was finished. I have the clients have a Touched event where it fires an ended event with the “Unique Id” I mentioned earlier. So here’s where the hit registration comes in. I use @EgoMoose’s RotatedRegion3 to do hit registration. I take the beginning tick and the ended shot tick and subtract them which gives how long it’s been since the tween ended. I take this time and divide it by the time it took to tween the bullets (Credit to @Headstackk’s thread for the equation) This gives me a value from 0 to 1 that I can use to lerp. I can lerp this to get the rough position of where the bullet hit. I used the RotatedRegion3 with the rough position and size of the bullet and I get the touching parts of the RotatedRegion3. Finally, I iterate through all of the parts and look for which one has a humanoid and I check if the players are also on the same team. Then, if it finds the humanoid, it takes damage. Here’s the code:
-- server
local HttpService = game:GetService("HttpService")
local Debris = game:GetService("Debris")
local TweenService = game:GetService("TweenService")
local Players = game:GetService("Players")
local require = require(game:GetService("ReplicatedStorage"):WaitForChild("Nevermore"))
local TimeSyncService = require("TimeSyncService")
local RotatedRegion3 = require("RotatedRegion3")
TimeSyncService:Init()
local GetRemoteEvent, GetRemoteFunction = require("GetRemoteEvent"), require("GetRemoteFunction")
local newClock = TimeSyncService:WaitForSyncedClock()
local RENDER_BULLET_EVENT = GetRemoteEvent("RenderBullet")
local BULLET_SHOOT_FINISHED = GetRemoteEvent("BulletFinished")
local IKService = require("IKService")
IKService:Init()
local BULLET_VELOCITY = 50
local projectileTable = {}
local BULLET_SIZE = Vector3.new(0.8, 0.2, 0.2)
function IsTeamMate(Player1, Player2)
return (Player1 and Player2 and not Player1.Neutral and not Player2.Neutral and Player1.TeamColor == Player2.TeamColor)
end
RENDER_BULLET_EVENT.OnServerEvent:Connect(function(player, startCFrame, endCFrame, damage, mainPart)
local projectileId = HttpService:GenerateGUID(false)
projectileTable[projectileId] = newClock:GetTime()
endCFrame = CFrame.fromMatrix(endCFrame.Position, endCFrame.LookVector, endCFrame.UpVector, endCFrame.RightVector)
startCFrame = CFrame.fromMatrix(startCFrame.Position, endCFrame.RightVector, endCFrame.UpVector, -endCFrame.LookVector)
local time = (endCFrame.Position - startCFrame.Position).Magnitude * 0.3 / BULLET_VELOCITY
RENDER_BULLET_EVENT:FireAllClients(player, time, startCFrame, endCFrame, projectileId, mainPart, projectileTable[projectileId])
local connection
connection = BULLET_SHOOT_FINISHED.OnServerEvent:Connect(function(_, newProjectileId)
if projectileId == newProjectileId then
local endTime = newClock:GetTime()
local difference = endTime - projectileTable[projectileId]
local alpha = TweenService:GetValue(difference / time, Enum.EasingStyle.Linear, Enum.EasingDirection.In)
local roughPosition = startCFrame:Lerp(endCFrame, alpha)
local newRegion3 = RotatedRegion3.new(roughPosition, BULLET_SIZE)
local parts = newRegion3:FindPartsInRegion3(player.Character)
local humanoid
for _, part in ipairs(parts) do
humanoid = part.Parent:FindFirstChildWhichIsA("Humanoid") or part.Parent.Parent:FindFirstChildWhichIsA("Humanoid")
if humanoid then
break
end
end
if humanoid then
local attackPlayer = Players:GetPlayerFromCharacter(humanoid.Parent)
if attackPlayer and IsTeamMate(player, attackPlayer) then
return
end
local Creator_Tag = Instance.new("ObjectValue")
Creator_Tag.Name = "creator"
Creator_Tag.Value = attackPlayer
Debris:AddItem(Creator_Tag, 2)
Creator_Tag.Parent = humanoid
humanoid:TakeDamage(damage)
end
projectileTable[projectileId] = nil
connection:Disconnect()
end
end)
end)
-- client
local player = game.Players.LocalPlayer
local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")
local require = require(game:GetService("ReplicatedStorage"):WaitForChild("Nevermore"))
local PartCache = require("PartCache")
local TimeSyncService = require("TimeSyncService")
local GetRemoteEvent, GetRemoteFunction = require("GetRemoteEvent"), require("GetRemoteFunction")
local camera = workspace.CurrentCamera
TimeSyncService:Init()
local newClock = TimeSyncService:WaitForSyncedClock()
local RenderBullet = GetRemoteEvent("RenderBullet")
local BulletFinished = GetRemoteEvent("BulletFinished")
local bullet = game:GetService("ReplicatedStorage"):WaitForChild("Bullet")
local cache = PartCache.new(bullet, 25)
cache:SetCacheParent(workspace:WaitForChild("CachedParts"))
local Spring = require("QuentySpring")
local cameraSpring = Spring.new(Vector3.new())
cameraSpring.Damper = 0.5
RenderBullet.OnClientEvent:Connect(function(player, tweenTime, startCFrame, endCFrame, projectileId, mainPart, time0)
local newBullet = cache:GetPart()
newBullet.Parent = workspace.GameStuff
newBullet.CFrame = startCFrame
local tweenConnection, alpha
local function ended()
BulletFinished:FireServer(projectileId)
tweenConnection:Disconnect()
newBullet.Trail.Enabled = false
if not newBullet:IsDescendantOf(workspace.CachedParts) then
cache:ReturnPart(newBullet)
end
end
tweenConnection = RunService.RenderStepped:Connect(function(delta)
local time = newClock:GetTime() + delta
local currentTime = time - time0
alpha = currentTime / tweenTime
alpha = TweenService:GetValue(alpha, Enum.EasingStyle.Linear, Enum.EasingDirection.In)
local position = startCFrame:Lerp(endCFrame, alpha)
newBullet.CFrame = position -- lerp the bullet’s cframe so it’s synced with all other clients
if alpha >= 1 then
ended()
end
end)
newBullet.Trail.Enabled = true
if mainPart then
for _, particleEmitter in ipairs(mainPart:GetChildren()) do
if particleEmitter:IsA("ParticleEmitter") then
particleEmitter:Emit(10)
end
end
end
local connection
connection = newBullet.Touched:Connect(function(hit)
if not hit:IsDescendantOf(player.Character) then
ended()
end
end)
cameraSpring:Impulse(Vector3.new(0, 1, 0.5) / (player:DistanceFromCharacter(newBullet.Position) + 1))
end)
local RunService = game:GetService("RunService")
RunService.RenderStepped:Connect(function(delta)
cameraSpring:TimeSkip(delta * 16)
local cameraCFrame = cameraSpring.Position
camera.CFrame = camera.CFrame * CFrame.Angles(cameraCFrame.Z / 10, 0, cameraCFrame.Y / 5)
end)
Remember, never have the server depend on data that can be easily manipulated, especially if it can cause a player to have an advantage.
(I also use @EtiTheSpirit’s PartCache and modules from @Quenty’s NevermoreEngine).