Alright so I hope I understood all of you, I got pretty confused but this is what I got in the end:
Server Side Code:
local TweenService = game:GetService("TweenService")
-- Remote Events and Remote Functions
local SpawnedEnemy = game.ReplicatedStorage:WaitForChild("SpawnedEnemy")
local RequestPaths = game.ReplicatedStorage:WaitForChild("RequestPaths")
-- Spline Calculations
local tableOfPaths = {}
function DrawPath()
local NodesTable = {}
local tensionMain = math.random(35, 85) / 100
local counter = 0
local Points = {}
for i = 1, #game.Workspace.Points:GetChildren() do
Points[i] = game.Workspace.Points[i].Position
end
local NumPoints = #Points
for i = 1, NumPoints - 1 do
local p0 = Points[i - 1] or Points[1]
local p1 = Points[i]
local p2 = Points[i + 1]
local p3 = Points[i + 2] or Points[NumPoints]
for j = 0, 1, 0.05 do
local tension = tensionMain
local t = j
local t2 = t * t
local t3 = t2 * t
local p = (
(-tension * t + 2 * tension * t2 - tension * t3) * p0 +
(2 + (tension - 6) * t2 + (4 - tension) * t3) * p1 +
(tension * t - 2 * (tension - 3) * t2 + (tension - 4) * t3) * p2 +
(-tension * t2 + tension * t3) * p3
) * 0.5
counter = counter + 1
NodesTable[counter] = { Position = p }
end
end
return NodesTable
end
for i = 1, 100 do
tableOfPaths[i] = DrawPath()
end
function RequestPath()
return tableOfPaths
end
RequestPaths.OnServerInvoke = RequestPath
task.wait(5)
local Precision = 10^2
local ServerTableOfEnemies = {}
local EnemyId = 1
function SpawnNPC()
local EnemyInfo = {
["EnemyId"] = EnemyId,
["Node"] = 1,
["Speed"] = 25,
["Path"] = math.random(1, 100),
["CFrame"] = CFrame.lookAt(game.Workspace.Points["1"].Position, game.Workspace.Points["2"].Position),
["Done"] = false,
["SyncTime"] = workspace.DistributedGameTime -- Initialized to current server time
}
ServerTableOfEnemies[EnemyInfo.EnemyId] = EnemyInfo
EnemyId = EnemyId + 1
local ClientEnemyInfo = {
[1] = EnemyInfo.EnemyId,
[2] = Vector3int16.new(EnemyInfo.CFrame.X * Precision, EnemyInfo.CFrame.Y * Precision, EnemyInfo.CFrame.Z * Precision),
[3] = EnemyInfo.Node,
[4] = EnemyInfo.Speed,
[5] = EnemyInfo.Path,
[6] = workspace.DistributedGameTime,
}
SpawnedEnemy:FireAllClients(ClientEnemyInfo)
end
-- Actual Movement Handling
function HandleMovement()
for i, Enemy in pairs(ServerTableOfEnemies) do
if Enemy.Done then
print("Server is Done")
ServerTableOfEnemies[i] = nil
else
local path = tableOfPaths[Enemy.Path]
if path then
local currentNode = path[Enemy.Node]
local nextNode = path[Enemy.Node + 1]
if currentNode and nextNode then
local direction = (nextNode.Position - Enemy.CFrame.Position).Unit
local distanceToNextNode = (nextNode.Position - Enemy.CFrame.Position).Magnitude
-- Move NPC by a constant speed
local moveDistance = Enemy.Speed * 0.01 -- Multiplying by SyncTime
local newPosition = Enemy.CFrame.Position + (direction * moveDistance)
-- Smooth transition and rotation
local targetCFrame = CFrame.lookAt(newPosition, nextNode.Position)
Enemy.CFrame = Enemy.CFrame:Lerp(targetCFrame, 0.5)
if (newPosition - nextNode.Position).Magnitude <= moveDistance then
Enemy.Node = Enemy.Node + 1
if Enemy.Node > #path - 1 then
Enemy.Done = true
else
currentNode = path[Enemy.Node]
nextNode = path[Enemy.Node + 1]
if currentNode and nextNode then
Enemy.CFrame = CFrame.new(currentNode.Position, nextNode.Position)
else
print("Error: currentNode or nextNode is nil after updating Enemy.Node for Enemy ID:", Enemy.EnemyId)
end
end
end
else
print("Error: currentNode or nextNode is nil for Enemy ID:", Enemy.EnemyId)
end
else
print("Error: Path not found for Enemy ID:", Enemy.EnemyId)
end
end
end
end
-- Main Routine
local enemyCount = 200
task.spawn(function()
for _ = 1, enemyCount do
task.wait(0.1) -- Spawn delay between each unit
SpawnNPC()
end
end)
while true do
task.wait(0.015)
HandleMovement()
end
Client Side Code:
local Precision = 10^2
local tableOfPaths = game.ReplicatedStorage:WaitForChild("RequestPaths"):InvokeServer()
local tableOfEnemies = {}
function SyncMoveEnemy(EnemyPart, EnemyData)
local currentTime = workspace.DistributedGameTime
local path = EnemyData.Path
if path then
local currentNode = path[EnemyData.Node]
local nextNode = path[EnemyData.Node + 1]
if currentNode and nextNode then
local direction = (nextNode.Position - EnemyPart.CFrame.Position).Unit
local distanceToNextNode = (nextNode.Position - EnemyPart.CFrame.Position).Magnitude
-- Move NPC by a constant speed
local elapsedTime = currentTime - EnemyData.SyncTime -- Calculate time elapsed
local moveDistance = EnemyData.Speed * elapsedTime -- Multiplying by SyncTime
local newPosition = EnemyPart.CFrame.Position + (direction * moveDistance)
local newOrientation = CFrame.lookAt(newPosition, nextNode.Position)
EnemyPart.CFrame = newOrientation
if (newPosition - nextNode.Position).Magnitude <= moveDistance then
EnemyData.Node = EnemyData.Node + 1
if EnemyData.Node > #path - 1 then
EnemyData.Done = true
else
currentNode = path[EnemyData.Node]
nextNode = path[EnemyData.Node + 1]
if currentNode and nextNode then
EnemyPart.CFrame = CFrame.new(currentNode.Position, nextNode.Position)
else
print("Error: currentNode or nextNode is nil after updating Enemy.Node for Enemy ID:", EnemyData["EnemyId"])
end
end
end
else
print("Error: currentNode or nextNode is nil for Enemy ID:", EnemyData["EnemyId"])
end
else
print("Error: Path not found for Enemy ID:", EnemyData["EnemyId"])
end
end
function Decompile(EnemyData)
return {
["EnemyId"] = EnemyData[1],
["EnemyPosition"] = EnemyData[2],
["Node"] = EnemyData[3],
["Speed"] = EnemyData[4],
["Path"] = tableOfPaths[EnemyData[5]],
["SyncTime"] = EnemyData[6],
["Done"] = false,
}
end
game.ReplicatedStorage.SpawnedEnemy.OnClientEvent:Connect(function(EnemyData)
local ClientTableEnemyInfo = Decompile(EnemyData)
local EnemyId = ClientTableEnemyInfo["EnemyId"]
tableOfEnemies[EnemyId] = ClientTableEnemyInfo
local CFrameNoOrientation = CFrame.new(ClientTableEnemyInfo["EnemyPosition"].X / Precision, ClientTableEnemyInfo["EnemyPosition"].Y / Precision, ClientTableEnemyInfo["EnemyPosition"].Z / Precision)
local cloneClientSide = game.ReplicatedStorage.ClientSide:Clone()
cloneClientSide.Name = ClientTableEnemyInfo["EnemyId"]
cloneClientSide.Parent = game.Workspace.Mobs
cloneClientSide.Position = game.Workspace.Points["1"].Position
cloneClientSide:PivotTo(CFrame.new(cloneClientSide.Position, CFrameNoOrientation.Position))
SyncMoveEnemy(cloneClientSide, ClientTableEnemyInfo)
end)
function HandleMovement()
for i, Enemy in pairs(tableOfEnemies) do
local clientTarget = game.Workspace.Mobs[Enemy["EnemyId"]]
if Enemy.Done then
print("Client is Done")
tableOfEnemies[i] = nil
game.Debris:AddItem(clientTarget, 0)
else
local path = Enemy.Path
if path then
local currentNode = path[Enemy.Node]
local nextNode = path[Enemy.Node + 1]
if currentNode and nextNode then
local direction = (nextNode.Position - clientTarget.CFrame.Position).Unit
local distanceToNextNode = (nextNode.Position - clientTarget.CFrame.Position).Magnitude
-- Move NPC by a constant speed
local moveDistance = Enemy.Speed * 0.01 -- Multiplying by SyncTime
local newPosition = clientTarget.CFrame.Position + (direction * moveDistance)
-- Smooth transition and rotation
local targetCFrame = CFrame.lookAt(newPosition, nextNode.Position)
clientTarget.CFrame = clientTarget.CFrame:Lerp(targetCFrame, 0.5)
if (newPosition - nextNode.Position).Magnitude <= moveDistance then
Enemy.Node = Enemy.Node + 1
if Enemy.Node > #path - 1 then
Enemy.Done = true
else
currentNode = path[Enemy.Node]
nextNode = path[Enemy.Node + 1]
if currentNode and nextNode then
clientTarget.CFrame = CFrame.new(currentNode.Position, nextNode.Position)
else
print("Error: currentNode or nextNode is nil after updating Enemy.Node for Enemy ID:", Enemy.EnemyId)
end
end
end
else
print("Error: currentNode or nextNode is nil for Enemy ID:", Enemy.EnemyId)
end
else
print("Error: Path not found for Enemy ID:", Enemy.EnemyId)
end
end
end
end
while true do
task.wait(0.01)
HandleMovement()
end
Now I’m still having an issue, the client seems to finish before the server does.
It’s weird because the HandleMovement() function is pretty much identical for the Server Side and the Client Side. Any ideas?