uhh okay, but its kinda big
EnemyClass
--!native
--!strict
--[[
__ _ _
_____ __/ /_ | || | ___
/ _ \ \/ / '_ \| || |_/ __|
| __/> <| (_) |__ _\__ \
\___/_/\_\\___/ |_| |___/ _ _
_ __ _ __ ___ __| |_ _ ___| |_(_) ___ _ __ ___
| '_ \| '__/ _ \ / _` | | | |/ __| __| |/ _ \| '_ \/ __|
| |_) | | | (_) | (_| | |_| | (__| |_| | (_) | | | \__ \
| .__/|_| \___/ \__,_|\__,_|\___|\__|_|\___/|_| |_|___/
|_|
]]
--[=[
@class Enemy
Enemy Class. Updates the Translated Value of Enemies,
and are in the .enemies in the enemyReplicator Script.
addEnemy on the enemyReplicator module creates these classes.
]=]
local Enemy = {}
local ServerStorage = game:GetService("ServerStorage")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local BezierPath = require(ReplicatedStorage.Packages:WaitForChild("BezierPath"))::any
local BridgeNet = require(ReplicatedStorage.Packages:WaitForChild("bridgenet2"))::any
local enemystats = require(ServerStorage:WaitForChild("EnemyStats"))
local bosshealthbar = ReplicatedStorage.AddHealthBar
local AbilityManager = require(ServerStorage.AbilityManager)
local Enemies = ReplicatedStorage.Enemies
local HashCreator = require(ServerStorage.HashCreator)
local deleteBridge = BridgeNet.ServerBridge("enemyDeletion")
local refresh_rate = 0.1
local deleteEvent = ReplicatedStorage.deletedCompletedEnemy
Enemy.__index = Enemy
export type enemy = {
speed: number,
health: number,
hash: string,
position: CFrame,
translated: number,
class: string,
renderID: number,
updateConnection: RBXScriptConnection,
}
--[=[
Creates an enemy
@param enemyName string --The class name of the enemy
@param path table --The path that the enemy follows
]=]
function Enemy.create(enemyName, path)
ReplicatedStorage.DebugValues.EnemyCount.Value += 1
local self = setmetatable({}, Enemy)
self.position = CFrame.new(0, 0, 0)
self.class = enemyName
local enemyData = enemystats.get(enemyName)
self.speed = enemyData.speed
self.health = enemyData.health
self.hash = HashCreator.retriveHash()
local _, boundingBox = Enemies[enemyName]:GetBoundingBox()
local sizeAddition = boundingBox.Y / 2
local rotationalOffset
local offset = math.random(-1, 1)
if enemyData.modifiers["boss"] then
bosshealthbar:FireAllClients(enemyData.name, self, self.health)
end
if enemyData.rotationOffset ~= nil then
rotationalOffset = enemyData.rotationOffset
else
rotationalOffset = Vector3.zero
end
if enemyData["abilities"] then
task.spawn(function()
for ability, _ in pairs(enemyData["abilities"]) do
AbilityManager.register("enemies", ability, self)
end
end)
end
local rotationalCFrame = CFrame.Angles(rotationalOffset.X, rotationalOffset.Y, rotationalOffset.Z)
local offsetCFrame = CFrame.new(offset, sizeAddition, 0)
local pathLength = path["path"]:GetPathLength()
self.updateConnection = task.spawn(function()
local PreviousTime = os.clock()
self.translated = 0
while self.translated < 1 do
--task.desynchronize()
self.translated += (os.clock() - PreviousTime) * self.speed / pathLength
PreviousTime = os.clock()
local transformedcframe: CFrame = path["path"]:CalculateUniformCFrame(self.translated)
* offsetCFrame
* rotationalCFrame
self.position = transformedcframe
task.wait(refresh_rate)
--task.synchronize()
end
ReplicatedStorage.DebugValues.EnemyCount.Value -= 1
deleteBridge:Fire(BridgeNet.AllPlayers(), self.hash)
deleteEvent:Fire(self.hash)
if self.health > 0 then
ReplicatedStorage.BaseHealth.Value -= self.health
self.health = 0
end
end)
return self
end
--[=[
Damaging function. Unused function, just use enemyReplicator.enemyEffect(health)
@param damage number --damage that will be given to the enemy
]=]
function Enemy:damage(damage): nil
local clampedDamage = math.clamp(self.health - damage, 0, 9999999999)
self.health -= clampedDamage
if self.health <= 0 then
self.health = 0
end
return nil
end
return Enemy
EnemyReplicator.lua
--!native
--[[
__ _ _
_____ __/ /_ | || | ___
/ _ \ \/ / '_ \| || |_/ __|
| __/> <| (_) |__ _\__ \
\___/_/\_\\___/ |_| |___/ _ _
_ __ _ __ ___ __| |_ _ ___| |_(_) ___ _ __ ___
| '_ \| '__/ _ \ / _` | | | |/ __| __| |/ _ \| '_ \/ __|
| |_) | | | (_) | (_| | |_| | (__| |_| | (_) | | | \__ \
| .__/|_| \___/ \__,_|\__,_|\___|\__|_|\___/|_| |_|___/
|_|
]]
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")
local replicator = {}
replicator.enemies = {}
replicator.pathTable = {}
replicator.enemyCount = 0
local BezierPath = require(ReplicatedStorage.Packages:WaitForChild("BezierPath"))::any
local BridgeNet = require(ReplicatedStorage.Packages:WaitForChild("bridgenet2"))::any
local enemyclass = require(ServerStorage:WaitForChild("EnemyClass"))
local packetBridge = BridgeNet.ServerBridge("enemyReplication")
--packetBridge.Logging = true
local createBridge = BridgeNet.ServerBridge("enemyCreation")
local deleteBridge = BridgeNet.ServerBridge("enemyDeletion")
local deleteEvent = ReplicatedStorage.deletedCompletedEnemy
--[=[
@class replicator
This handles enemy Replication, sends packets to the client, so the client can render and position
All the functions in this module handle enemies, and do things like damage or heal effects.
This module also handles creating enemies.
]=]
--[=[
@function setUp
This function sets up the Replicator, which connects BindableEvent
to deleteEnemies, and reset the EnemyCount.
@within replicator
]=]
replicator.setUp = function(_)
deleteEvent.Event:Connect(function(hash)
replicator.enemies[hash] = nil
replicator.enemyCount -= 1
end)
end
--[=[
@function createnavpath
This function creates an path with the BezierPath Module.
This function is called for each path in the map.
@param nodefolder Folder
@param index number
@within replicator
]=]
replicator.createnavpath = function(nodefolder, index)
local nodePositions = { nodefolder.enemyspawn.Position }
for i = 1, #nodefolder:GetChildren() - 2 do
table.insert(nodePositions, nodefolder[i].Position)
end
table.insert(nodePositions, nodefolder.exit.Position)
local BezPath = BezierPath.new(nodePositions, 3)
local pathtable = {
["path"] = BezPath,
["nodefolder"] = nodefolder,
}
replicator.pathTable[index] = pathtable
end
--[=[
@function getEnemyCount
Retrives the amount of enemies active
@within replicator
]=]
replicator.getEnemyCount = function()
return replicator.enemyCount
end
--[=[
@function getTargetingData
Compiles an table with all the enemies,
with condensed info like the index of translation, the position, the health, and the translated index.
@within replicator
]=]
replicator.getTargetingData = function()
task.desynchronize()
local enemyData = {}
for hash, enemy: enemyclass.enemy in pairs(replicator.enemies) do
table.insert(enemyData, { hash, enemy.position, enemy.health, enemy.translated })
end
task.synchronize()
return enemyData
end
--[=[
@function enemyEffect
Changes an property in the enemy with the hash applied.
If the effect is health, than it checks if the health is under 0, and if it is, deletes the enemy.
TODO give cash to everyone based on enemy
@param hash string
@param effect any
@param new any
@within replicator
]=]
replicator.enemyEffect = function(hash, effect, new)
local enemy = replicator.enemies[hash]
if enemy then
enemy[effect] = new
if effect == "health" then
if enemy["health"] <= 0 then
deleteBridge:Fire(BridgeNet.AllPlayers(), hash)
replicator.enemyCount -= 1
replicator.enemies[hash] = nil
end
end
end
end
--[=[
@function getSingleEnemy
SelfExplanatory, retrives an single enemy with the hash provided
@param enemyHash string
@within replicator
]=]
replicator.getSingleEnemy = function(enemyHash)
local enemy = replicator.enemies[enemyHash]
return enemy
end
--[=[
@function update
Compiles all the enemies into one condensed table, with the CFrame. Than, using bridgeNet2, sends
the infomation to the client, so it can be rendered on the client.
@within replicator
]=]
local enemyData = {}
replicator.update = function()
for hash: string, enemy: enemyclass.enemy in pairs(replicator.enemies) do
enemyData[hash] = enemy.position
--print("hash: "..tostring(hash).." ".."position: "..tostring(enemy.position))
end
packetBridge:Fire(BridgeNet.AllPlayers(), enemyData)
end
--[=[
@function addEnemy
Creates an enemy, and adds it to the replicator table in the module. Than,
fires an bridge to the client to create an enemy, and increases the enemyCount.
@param enemyName string
@param pathNum number
@within replicator
]=]
replicator.addEnemy = function(enemyName, pathNum)
local eClass = enemyclass.create(enemyName, replicator.pathTable[pathNum])
replicator.enemyCount += 1
local hash = eClass["hash"]
replicator.enemies[hash] = eClass
task.wait()
createBridge:Fire(BridgeNet.AllPlayers(), { eClass["hash"], eClass["class"] })
end
return replicator
render.client.lua
--!native
local Players = game:GetService("Players")
local player = Players.LocalPlayer
repeat
task.wait()
until player:WaitForChild("loaded").Value == true
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Map = ReplicatedStorage:WaitForChild("Map")
local Enemies = ReplicatedStorage:WaitForChild("Enemies")
local loadedEnemies = {}
local Workspace = game:GetService("Workspace")
local MapObject = Workspace:WaitForChild(Map.Value)
local BridgeNet2 = require(ReplicatedStorage.Packages.bridgenet2)
local enemyPacketBridge = BridgeNet2.ReferenceBridge("enemyReplication")
local enemyCreationBridge = BridgeNet2.ReferenceBridge("enemyCreation")
local enemyDeletionBridge = BridgeNet2.ReferenceBridge("enemyDeletion")
local RunService = game:GetService("RunService")
local cframeData = {}
local function createEnemy(content)
local hash, name = content[1], content[2]
local enemyModel = Enemies:FindFirstChild(name):Clone()
enemyModel.Name = hash
enemyModel:SetAttribute("class", name)
enemyModel.Parent = MapObject.enemies
loadedEnemies[hash] = enemyModel.PrimaryPart
local animation = enemyModel.Animations.walk
local track = enemyModel.AnimationController.Animator:LoadAnimation(animation)
track:Play()
end
local function deleteEnemy(content)
local enemy = loadedEnemies[content]
if enemy then
enemy.Parent:Destroy()
loadedEnemies[content] = nil
end
end
local function updatePosition(delta)
local Parts = {}
local CFrames = {}
local index = 1
for hash, cframe in pairs(cframeData) do
local enemyPrimary = loadedEnemies[hash]
if enemyPrimary then
local alpha = math.clamp(delta / 0.1, 0, 1)
Parts[index] = enemyPrimary
CFrames[index] = enemyPrimary.CFrame:Lerp(cframe, alpha)
index += 1
end
end
task.synchronize()
Workspace:BulkMoveTo(Parts, CFrames, Enum.BulkMoveMode.FireCFrameChanged)
end
local function updateData(content)
cframeData = content
end
enemyCreationBridge:Connect(createEnemy)
enemyPacketBridge:Connect(updateData)
enemyDeletionBridge:Connect(deleteEnemy)
RunService.Heartbeat:ConnectParallel(updatePosition)
replicator.update() is called every 0.1 seconds