Need a way to make an better optimized entity system

So I’m making this type of tower defense and was trying to code stuff, until i noticed that my recv is going up. Within a few entities gets me up to 100 KB/s of recv. The script that moves the entities use lerp then CFrame replication, and as in server side is a basepart then the client side is the model of the character. I’m dumb enough to fix it, but i found one in the script that moves the entities, and the remote that plays the animation from server to local side.

Tried to test on 20 enemies, and GOD my game lagged, and got 150-200 KB/s of recv.
image

So here’s the script. Messy stuff i have to fix @_@

NPCSYSTEM
local System = {}
System.CurrentFunctionRunnin = {}
System.ClientFunctionRunnin = {}
local RS = game:GetService('RunService')
local Util = require(game.ReplicatedStorage.Utilities)
local Store = game.ReplicatedStorage.StoreRunninSystem
function System.MoveTo(obj,pos) -- Moves the part, just like humanoid MoveTo
	pos = Vector3.new(pos.X,pos.Y,pos.Z)
	for i,v in pairs(System.CurrentFunctionRunnin) do
		if i:find(obj.Name,1,true) then
			System.CurrentFunctionRunnin[i] = nil
		end
	end
	local nam = obj.Name..math.random(-9999,9999)
	System.CurrentFunctionRunnin[nam] = true

	local Values = require(obj:WaitForChild('Stats'))
	local durTimer = 0
	local startPos = obj.Position
	local duration = (pos - startPos).Magnitude / Values.walkspeed
	local dir = (pos - startPos).Unit
	local curDir = obj.CFrame.LookVector
	local durMult = 1 / duration
	local hb
	hb = RS.Heartbeat:Connect(function(dt)
		if (not System.CurrentFunctionRunnin[nam] or System.CurrentFunctionRunnin[nam] == nil) or startPos == pos then
			hb:Disconnect()
			return
		end
		durTimer += dt
		if durTimer >= duration then
			durTimer = duration
			System.CurrentFunctionRunnin[nam] = nil
			hb:Disconnect()
		end
		local ratio = durTimer * durMult
		local pos = startPos:Lerp(pos,ratio)
		
		obj.CFrame = CFrame.new(pos,pos + curDir:Lerp(dir,math.min(durTimer * 10,1)))
	end)
end

function System.ClientModelConnect(obj,part,offsetpos) -- Attaches client model to a part
	local function Show(inst,bool)
		for i,v in ipairs(inst:GetChildren()) do
			if (v:IsA('Part') or v:IsA('MeshPart')) and v.Name ~= 'HumanoidRootPart' then
				v.Transparency = bool and 0 or 1
			end
		end
	end
	for i,v in pairs(System.ClientFunctionRunnin) do
		if i:find(obj.Name,1,true) then
			System.ClientFunctionRunnin[i] = nil
		end
	end
	local nam = obj.Name..math.random(-99999,999999)
	System.ClientFunctionRunnin[nam] = true

	local objpos = obj.PrimaryPart.Position + offsetpos
	local x, y, z = obj.PrimaryPart.CFrame:ToEulerAnglesYXZ()

	local camera = workspace.CurrentCamera
	local hb
	hb = RS.RenderStepped:Connect(function(dt)
		objpos = objpos:Lerp(part.Position + offsetpos,dt * 10)
		local x2, y2, z2 = part.CFrame:ToEulerAnglesYXZ()
		x,y,z = Util.Lerp(x,x2,dt * 10),Util.Lerp(y,y2,dt * 10),Util.Lerp(z,z2,dt * 10)
		obj:SetPrimaryPartCFrame(CFrame.new(objpos) * CFrame.Angles(x, y, z))
		local vector, onScreen = camera:WorldToScreenPoint(obj.PrimaryPart.Position)
		if onScreen then Show(obj,true) else Show(obj,false) end

		if not System.ClientFunctionRunnin[nam] then
			hb:Disconnect()
		end
	end)
end
function System.StopClientModel(obj)
	for i,v in pairs(System.ClientFunctionRunnin) do
		if i:find(obj,1,true) then
			System.ClientFunctionRunnin[i] = nil
		end
	end
end

function System.IsMoving(obj)
	for i,v in pairs(System.CurrentFunctionRunnin) do
		if i:find(obj.Name,1,true) and v then
			return true
		end
	end
	return false 
end

function System.StopMoving(obj)
	for i,v in pairs(System.CurrentFunctionRunnin) do
		if i:find(obj.Name,1,true) then
			System.CurrentFunctionRunnin[i] = nil
		end
	end
end

return System

I also think the playing animation in enemy/unit entity script can cause that too. The remote event fires from serverside to client side, and worse it’s executed repeatedly. Honestly I’m too dumb to implement an optimization of it.

local animation = {
	Idle = {'Idle',false,0.5,0.2},
	Run = {'Run',false,0.5,0.2},
	Attack = {'Attack',false,0.15,0.2},
	SpecAttack = {'SpecAttack',false,0.15,0.2}
}				-- name, isplaying, playfade, stopfade
-- ignore the isplaying, i tried to optimize using that bool and couldn't make it so when the anim ends it set to false. (due to remote optimization)

local animstring = 'Run'

function updateAnim(string)
	local ent = isEnemy and workspace.Enemy or workspace.Unit
	for i,v in pairs(animation) do
		if i ~= string then
			ClientModelEvent:FireAllClients('stopAnim',stats.ClientModelName,{Parent = ent,animName = animation[i][1],fadetime = animation[string][4]})
		end
	end
	ClientModelEvent:FireAllClients('playAnim',stats.ClientModelName,{Parent = ent,animName = animation[string][1],fadetime = animation[string][3],force = false})
end

There are some Roblox built in functions that are deprecated (i think). So please mention it!