local I=game:GetService("\080\108\097\121\101\114\115")local K=game:GetService("\082\117\110\083\101\114\118\105\099\101")local Y=script:FindFirstAncestor("\069\110\101\109\121\077\111\100\117\108\101")local l=require(Y.Packages.t)local C=require(Y.events)local g=require(Y.EnemySettings)local X=require(Y.Modules.AttackClass)local d=require(Y.Modules.ToolAttackClass)local L=require(Y.EnemyNameByEnemyType)local E=Y.Enemies local u=Y.DefaultAnimations local i=Y.HealthBarBillboard local Q=10 local N=50 local v=7 local w=3 local c=7 local j=15 local m=20 local V=2 local A=6 local a=1 local k=1 local x=1 local O=3 local U=10 local H=5 local r=300 local e=Random.new()local J=.0001 local Z={}for I,K in ipairs(E:GetChildren())do Z[K.Name]=K end local M={}M.__index=M function M.new(I,K,Y,X)assert(Z[I],"\066\097\100 \097\114\103\117\109\101\110\116 \035\049 \116\111 \069\110\101\109\121\077\111\100\117\108\101\046\099\114\101\097\116\101\069\110\101\109\121\058 \105\110\118\097\108\105\100 \101\110\101\109\121 \110\097\109\101")assert(l.CFrame(K),"\066\097\100 \097\114\103\117\109\101\110\116 \035\050 \116\111 \069\110\101\109\121\077\111\100\117\108\101\046\099\114\101\097\116\101\069\110\101\109\121\058 \101\120\112\101\099\116\101\100 \097 \067\070\114\097\109\101")assert(l.table(Y),"\066\097\100 \097\114\103\117\109\101\110\116 \035\051 \116\111 \069\110\101\109\121\077\111\100\117\108\101\046\099\114\101\097\116\101\069\110\101\109\121\058 \101\120\112\101\099\116\101\100 \097 \116\097\098\108\101")assert(l.boolean(X),"\066\097\100 \097\114\103\117\109\101\110\116 \035\052 \116\111 \069\110\101\109\121\077\111\100\117\108\101\046\099\114\101\097\116\101\069\110\101\109\121\058 \101\120\112\101\099\116\101\100 \097 \098\111\111\108\101\097\110")local d=setmetatable({},M)d._enemyName=I d._enemy=Z[I]:Clone()d._spawnLocation=K d._respawnable=X d._animations=Y d._enemyDied=C.enemyDied d._respawnTime=g.RespawnTime[L[I]]or U d._active=true d._patrol=false local E={}for I,K in ipairs(d._animations)do E[K.Name]=K end d._animations=E d:_initialize()return d end function M._loadAnimation(K,I)local Y=K._animations[I]or u:FindFirstChild(I)if Y then return K.animator:LoadAnimation(Y)end return nil end function M._initializeAnimations(I)I.idleAnimation=I:_loadAnimation("\073\100\108\101\065\110\105\109")I.runAnimation=I:_loadAnimation("\082\117\110\065\110\105\109")I.walkAnimation=I:_loadAnimation("\087\097\108\107\065\110\105\109")I.jumpAnimation=I:_loadAnimation("\074\117\109\112\065\110\105\109")I.hitAnimation=I:_loadAnimation("\072\105\116\065\110\105\109")I.attackAnimation=I:_loadAnimation("\065\116\116\097\099\107\065\110\105\109")I.attackAnimation2=I:_loadAnimation("\065\116\116\097\099\107\065\110\105\109\050")I.attackAnimation3=I:_loadAnimation("\065\116\116\097\099\107\065\110\105\109\051")if I.attackAnimation then table.insert(I.valitAttackAnimations,I.attackAnimation)end if I.attackAnimation2 then table.insert(I.valitAttackAnimations,I.attackAnimation2)end if I.attackAnimation3 then table.insert(I.valitAttackAnimations,I.attackAnimation3)end end function M._setNetworkOwnership(I)for I,K in ipairs(I._enemy:GetDescendants())do if K:IsA("\066\097\115\101\080\097\114\116")then K:SetNetworkOwner(nil)end end end function M._initialize(I)I.humanoid=I._enemy.Humanoid I.animator=I.humanoid.Animator I._tool=I._enemy:FindFirstChildOfClass("\084\111\111\108")if I._tool then I.toolInstance=d.new(I._tool)end I.healthBarBillboard=i:Clone()I.healthBarBillboard.Adornee=I._enemy.Head I.healthBarBillboard.Parent=I._enemy I.target=nil I.hittingTarget=nil I.attacking=false I.inAttackRange=false I.valitAttackAnimations={}I.worldAttachment=Instance.new("\065\116\116\097\099\104\109\101\110\116")I.worldAttachment.Parent=I._enemy.HumanoidRootPart I.alignOrientation=Instance.new("\065\108\105\103\110\079\114\105\101\110\116\097\116\105\111\110")I.alignOrientation.Mode=Enum.OrientationAlignmentMode.OneAttachment I.alignOrientation.Enabled=false I.alignOrientation.Attachment0=I.worldAttachment I.alignOrientation.AlignType=Enum.AlignType.PrimaryAxisParallel I.alignOrientation.RigidityEnabled=true I.alignOrientation.Parent=I._enemy.HumanoidRootPart I.humanoid:SetStateEnabled(Enum.HumanoidStateType.Jumping,false)I.humanoid:SetStateEnabled(Enum.HumanoidStateType.Climbing,false)I.humanoid:SetStateEnabled(Enum.HumanoidStateType.Swimming,false)I.humanoidDiedConnection=I.humanoid.Died:Connect(function()I._enemyDied:Fire(I._enemyName,I.hittingTarget)task.delay(O,function()I:destroy()I:_respawn()end)end)I.humanoidHealthConnection=I.humanoid.HealthChanged:Connect(function()I:_updateHealthPercentage()end)I.humanoidRunningConnection=I.humanoid.Running:Connect(function(K)I:_handleRunning(K)end)I.location=I._spawnLocation I.respawnable=I._respawnable I.respawnTask=nil I.currentAnim=nil I.currentAnimTrack=nil I.currentAnimSpeed=1.0 I.regenerationElapsedTime=0 I.targetCheckElapsedTime=0 I.teleportCheckElapsedTime=0 I.lastAttackTime=0 I.searchingForTargets=false I.heartbeatConnection=K.Heartbeat:Connect(function(K)I:_onHeartbeat(K)end)I:_spawnEnemy()I:_setNetworkOwnership()I:_initializeAnimations()end function M._setRunSpeed(K,I)local Y=1 local l=(I*1.25)/Y if l~=K.currentAnimSpeed then K.currentAnimSpeed=l K.runAnimation:AdjustSpeed(l)end end function M._playRunAnimation(I)if I.currentAnim=="\114\117\110"then return end I.idleAnimation:Stop(.1)I.currentAnimTrack=I.runAnimation I.currentAnimTrack.Priority=Enum.AnimationPriority.Core I.currentAnimTrack:Play()I.currentAnim="\114\117\110"I.currentAnimSpeed=1.0 end function M._playIdleAnimation(I)if I.currentAnim=="\105\100\108\101"then return end if I.currentAnimTrack then I.currentAnimTrack:Stop(.1)end I.currentAnimTrack=I.idleAnimation I.currentAnimTrack.Priority=Enum.AnimationPriority.Core I.currentAnimTrack:Play()I.currentAnim="\105\100\108\101"end function M._handleRunning(K,I)if I>.75 then K:_playRunAnimation(I)K:_setRunSpeed(I/16.0)else K:_playIdleAnimation(I)end end function M._startHealthRegeneration(K,I)K.regenerationElapsedTime=K.regenerationElapsedTime+I if K.regenerationElapsedTime>=x then K.regenerationElapsedTime=0 if K.humanoid.Health>0 and K.humanoid.Health<K.humanoid.MaxHealth then K.humanoid.Health=math.min(K.humanoid.Health+k,K.humanoid.MaxHealth)end end end function M._updateHealthPercentage(I)local K=I.humanoid local Y=I.healthBarBillboard local l=Y.Container.HealthBar local C=l.ProgressBar local g=l.TextLabel local X=math.floor((K.Health/K.MaxHealth)*100)local d=math.clamp(K.Health/K.MaxHealth,0,1)C:TweenSize(UDim2.new(d,0,1,0),Enum.EasingDirection.Out,Enum.EasingStyle.Quad,.5,true)g.Text=tostring(X).."\037"end function M._getRandomPointInCircle(I)task.desynchronize()local K=math.sqrt(e:NextNumber())*m local Y=e:NextNumber(0,math.pi*2)local l=I.location.X+K*math.cos(Y)local C=I.location.Z+K*math.sin(Y)local g=Vector3.new(l,I.location.Y,C)task.synchronize()return g end function M._updateAlignOrientation(I)if not I._active then return end local K=I.hittingTarget or I.target if K then I.alignOrientation.Enabled=true I.alignOrientation.CFrame=CFrame.new(I._enemy.PrimaryPart.Position,K.PrimaryPart.Position)else I.alignOrientation.Enabled=false end end function M._findTarget(Y)if Y.searchingForTargets then return end Y.searchingForTargets=true local l=nil local C=nil if K:IsServer()then local I=OverlapParams.new()I.FilterType=Enum.RaycastFilterType.Exclude I.FilterDescendantsInstances={Y._enemy}task.desynchronize()local K=workspace:GetPartBoundsInRadius(Y._enemy.PrimaryPart.Position,N,I)local g={}for I,K in ipairs(K)do local X=K.Parent if X and not g[X]then local I=X:FindFirstChildOfClass("\072\117\109\097\110\111\105\100")local K=X:GetAttribute("\069\110\101\109\121\070\114\105\101\110\100")if I and not K then local I=X local K=(Y._enemy.PrimaryPart.Position-I.PrimaryPart.Position).Magnitude if not C or K<C then l=I C=K end g[X]=true end end end for I,K in pairs(g)do g[I]=nil end else local K=I.LocalPlayer.Character local C=(Y._enemy.PrimaryPart.Position-K.PrimaryPart.Position).Magnitude if C<N then l=K end end task.synchronize()Y.searchingForTargets=false return l end function M._attackTargetInRange(I)if#I.valitAttackAnimations==0 then return end local K=I.valitAttackAnimations[math.random(#I.valitAttackAnimations)]if K then K:Play()end local Y=I._enemy:FindFirstChild("\072\117\109\097\110\111\105\100\082\111\111\116\080\097\114\116")if not Y then return end local l=RaycastParams.new()l.FilterDescendantsInstances={I._enemy}l.FilterType=Enum.RaycastFilterType.Exclude task.desynchronize()local C=I.hittingTarget or I.target local g=(C.PrimaryPart.Position-Y.Position).Unit*j local X=workspace:Raycast(Y.Position,g,l)task.synchronize()if X then local K=X.Instance local Y=K.Parent:FindFirstChildOfClass("\072\117\109\097\110\111\105\100")if Y then local K=Y.Parent:GetAttribute("\066\108\111\099\107\105\110\103")local l=Y.Parent:GetAttribute("\069\110\101\109\121\070\114\105\101\110\100")if not K and not l then I:_damageTarget(Y)else end end end end function M._attackTarget(I)local K=tick()I.attacking=true if K-I.lastAttackTime>w then if I._tool then I.toolInstance:attackTarget()end I:_attackTargetInRange()I.lastAttackTime=K I.attacking=false end end function M._damageTarget(K,I)I:TakeDamage(Q)end function M._spawnEnemy(I)I._enemy:PivotTo(I.location)I._enemy.Parent=workspace end function M._cloneNewEnemy(I)return Z[I._enemyName]:Clone()end function M._respawn(I)if not I.respawnable then return end I._enemy=I:_cloneNewEnemy()I.respawnTask=task.delay(I._respawnTime,function()I:_initialize()if I._patrol then I:start()end if not I._active then I:stop()end I.respawnTask=nil end)end function M._handleHitEffects(I)if I.hitAnimation then I.hitAnimation:Play()end end function M._checkRecentlyHit(K)local Y=K._enemy:GetAttribute("\082\101\099\101\110\116\108\121\072\105\116")if Y then K:_handleHitEffects()K._enemy:SetAttribute("\082\101\099\101\110\116\108\121\072\105\116",nil)local l=I:FindFirstChild(Y)if l and l.Character then K.hittingTarget=l.Character end end end function M._moveToSpawnLocation(I)I.humanoid:MoveTo(I.location.Position)end function M._checkDistanceFromSpawn(K,I)K.teleportCheckElapsedTime=K.teleportCheckElapsedTime+I if K.teleportCheckElapsedTime>=H then K.teleportCheckElapsedTime=0 local I=(K._enemy.HumanoidRootPart.Position-K.location.Position).Magnitude if I>r then K._enemy:PivotTo(K.location)end end end function M._checkPriorityTargetDistance(I)local K=(I._enemy.HumanoidRootPart.Position-I.hittingTarget.HumanoidRootPart.Position).Magnitude if K>N*2 then I.hittingTarget=nil end end function M._followTarget(I)if not I._enemy or not I._active then return end local K=I.hittingTarget or I.target if not K then return end task.desynchronize()local Y=K.PrimaryPart.Position local l=K.PrimaryPart.AssemblyLinearVelocity local C=Y+l*.5 local g=(C-I._enemy.PrimaryPart.Position).Unit local X=C-g*c local d=(I._enemy.PrimaryPart.Position-X).Magnitude task.synchronize()if d<v then I.inAttackRange=true I:_attackTarget()end I.inAttackRange=false I.humanoid:MoveTo(X)end function M._followPath(I)if not I._enemy then return end while I._patrol do if not I.inAttackRange then local K=I:_getRandomPointInCircle()I.humanoid:MoveTo(K)end task.wait(e:NextInteger(V,A))end end function M._searchForTarget(K,I)if not K._active then return end if not K.hittingTarget then K.targetCheckElapsedTime=K.targetCheckElapsedTime+I if K.targetCheckElapsedTime>=a then K.targetCheckElapsedTime=0 K.target=K:_findTarget()end end end function M._onHeartbeat(K,I)K:_searchForTarget(I)K:_followTarget()K:_checkRecentlyHit(I)K:_checkDistanceFromSpawn(I)K:_startHealthRegeneration(I)if K.hittingTarget then K:_checkPriorityTargetDistance()end K:_updateAlignOrientation()if not K.target and(not K.hittingTarget and not K._patrol)then K:_moveToSpawnLocation()end end function M.start(I)I._patrol=true I.corutine=(coroutine.wrap(function()I:_followPath()end))()end function M.stop(I)I._active=false I._patrol=false if I.corutine then coroutine.close(I.corutine)I.corutine=nil end I.humanoid.WalkSpeed=0 end function M.destroy(I)I.humanoidDiedConnection:Disconnect()I.humanoidHealthConnection:Disconnect()I.humanoidRunningConnection:Disconnect()I.heartbeatConnection:Disconnect()if I._enemy then I._enemy:Destroy()I._enemy=nil end if I.respawnTask then task.cancel(I.respawnTask)I.respawnTask=nil end if I.corutine then coroutine.close(I.corutine)I.corutine=nil end task.wait()end return M
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local EnemyModule = script:FindFirstAncestor("EnemyModule")

local t = require(EnemyModule.Packages.t)
local events = require(EnemyModule.events)
local EnemySettings = require(EnemyModule.EnemySettings)
local AttackClass = require(EnemyModule.Modules.AttackClass)
local ToolAttackClass = require(EnemyModule.Modules.ToolAttackClass)
local EnemyNameByEnemyType = require(EnemyModule.EnemyNameByEnemyType)

local EnemiesFolder = EnemyModule.Enemies
local DefaultAnimations = EnemyModule.DefaultAnimations
local HealthBarBillboard = EnemyModule.HealthBarBillboard

-- Attack configuration
local ATTACK_DAMAGE = 10
local ATTACK_RADIUS = 50
local ATTACK_RANGE = 7
local ATTACK_DELAY = 3
local DISTANCE_OFFSET = 7
local DETECTION_RANGE_RAY = 15

-- Patrol configuration
local PATROL_RADIUS = 20
local MIN_REPOSITION_TIME = 2
local MAX_REPOSITION_TIME = 6

-- Target configuration
local TARGET_CHECK_INTERVAL = 1

-- Health regeneration configuration
local REGENERATION_RATE = 1
local REGENERATION_INTERVAL = 1
local DEATH_DESTROY_DELAY = 3
local RESPAWN_TIME = 10

-- Teleport configuration
local TELEPORT_CHECK_INTERVAL = 5
local TELEPORT_DISTANCE_THRESHOLD = 300

local random = Random.new()
local smallButNotZero = 0.0001

local enemies = {}
for _, child in ipairs(EnemiesFolder:GetChildren()) do
	enemies[child.Name] = child
end

local EnemyManager = {}
EnemyManager.__index = EnemyManager

function EnemyManager.new(enemyName, enemySpawn, animations, enemyRespawn)
	assert(enemies[enemyName], "Bad argument #1 to EnemyModule.createEnemy: invalid enemy name")
	assert(t.CFrame(enemySpawn), "Bad argument #2 to EnemyModule.createEnemy: expected a CFrame")
	assert(t.table(animations), "Bad argument #3 to EnemyModule.createEnemy: expected a table")
	assert(t.boolean(enemyRespawn), "Bad argument #4 to EnemyModule.createEnemy: expected a boolean")

	local self = setmetatable({}, EnemyManager)

	self._enemyName = enemyName
	self._enemy = enemies[enemyName]:Clone()
	self._spawnLocation = enemySpawn
	self._respawnable = enemyRespawn
	self._animations = animations
	self._enemyDied = events.enemyDied
	self._respawnTime = EnemySettings.RespawnTime[EnemyNameByEnemyType[enemyName]] or RESPAWN_TIME
	
	self._active = true
	self._patrol = false
	
	local animations = {}
	for _, child in ipairs(self._animations) do
		animations[child.Name] = child
	end
	self._animations = animations
	
	self:_initialize()

	return self
end

function EnemyManager:_loadAnimation(animationName)
	local animation = self._animations[animationName] or DefaultAnimations:FindFirstChild(animationName)
	if animation then
		return self.animator:LoadAnimation(animation)
	end

	return nil
end

function EnemyManager:_initializeAnimations()
	-- Load necessary animations
	self.idleAnimation = self:_loadAnimation("IdleAnim")
	self.runAnimation = self:_loadAnimation("RunAnim")
	self.walkAnimation = self:_loadAnimation("WalkAnim")
	self.jumpAnimation = self:_loadAnimation("JumpAnim")

	self.hitAnimation = self:_loadAnimation("HitAnim")
	self.attackAnimation = self:_loadAnimation("AttackAnim")
	self.attackAnimation2 = self:_loadAnimation("AttackAnim2")
	self.attackAnimation3 = self:_loadAnimation("AttackAnim3")
	
	-- Collect valid attack animations
	if self.attackAnimation then table.insert(self.valitAttackAnimations, self.attackAnimation) end
	if self.attackAnimation2 then table.insert(self.valitAttackAnimations, self.attackAnimation2) end
	if self.attackAnimation3 then table.insert(self.valitAttackAnimations, self.attackAnimation3) end
end

function EnemyManager:_setNetworkOwnership()
	for _, part in ipairs(self._enemy:GetDescendants()) do
		if part:IsA("BasePart") then
			part:SetNetworkOwner(nil)
		end
	end
end

function EnemyManager:_initialize()
	self.humanoid = self._enemy.Humanoid
	self.animator = self.humanoid.Animator
	
	self._tool = self._enemy:FindFirstChildOfClass("Tool")
	if self._tool then
		self.toolInstance = ToolAttackClass.new(self._tool)
	end

	self.healthBarBillboard = HealthBarBillboard:Clone()
	self.healthBarBillboard.Adornee = self._enemy.Head
	self.healthBarBillboard.Parent = self._enemy

	self.target = nil
	self.hittingTarget = nil
	self.attacking = false
	self.inAttackRange = false
	
	self.valitAttackAnimations = {}
	
	-- Add AlignOrientation and Attachments
	self.worldAttachment = Instance.new("Attachment")
	self.worldAttachment.Parent = self._enemy.HumanoidRootPart

	self.alignOrientation = Instance.new("AlignOrientation")
	self.alignOrientation.Mode = Enum.OrientationAlignmentMode.OneAttachment
	self.alignOrientation.Enabled = false
	self.alignOrientation.Attachment0 = self.worldAttachment
	self.alignOrientation.AlignType = Enum.AlignType.PrimaryAxisParallel
	self.alignOrientation.RigidityEnabled = true
	self.alignOrientation.Parent = self._enemy.HumanoidRootPart
	
	-- Disable unnecessary behaviors of humanoid to make game more efficient
	self.humanoid:SetStateEnabled(Enum.HumanoidStateType.Jumping, false)
	self.humanoid:SetStateEnabled(Enum.HumanoidStateType.Climbing, false)
	self.humanoid:SetStateEnabled(Enum.HumanoidStateType.Swimming, false)

	self.humanoidDiedConnection = self.humanoid.Died:Connect(function()
		-- TODO: Animate
		self._enemyDied:Fire(self._enemyName, self.hittingTarget)
		
		-- Clean up the current enemy instance
		task.delay(DEATH_DESTROY_DELAY, function()
			self:destroy()
			self:_respawn()
		end)
	end)

	self.humanoidHealthConnection = self.humanoid.HealthChanged:Connect(function()
		self:_updateHealthPercentage()
	end)
	
	-- 
	self.humanoidRunningConnection = self.humanoid.Running:Connect(function(speed)
		self:_handleRunning(speed)
	end)
	
	self.location = self._spawnLocation
	self.respawnable = self._respawnable
	
	self.respawnTask = nil
	
	self.currentAnim = nil
	self.currentAnimTrack = nil
	self.currentAnimSpeed = 1.0
	
	self.regenerationElapsedTime = 0
	self.targetCheckElapsedTime = 0
	self.teleportCheckElapsedTime = 0
	self.lastAttackTime = 0
	self.searchingForTargets = false
	
	self.heartbeatConnection = RunService.Heartbeat:Connect(function(deltaTime)
		self:_onHeartbeat(deltaTime)
	end)
	
	self:_spawnEnemy()
	self:_setNetworkOwnership()
	self:_initializeAnimations()
end

function EnemyManager:_setRunSpeed(speed)
	local heightScale = 1
	local runSpeed = speed * 1.25 / heightScale

	if runSpeed ~= self.currentAnimSpeed then
		self.currentAnimSpeed = runSpeed
		self.runAnimation:AdjustSpeed(runSpeed)
	end
end

function EnemyManager:_playRunAnimation()
	if self.currentAnim == "run" then
		return
	end

	self.idleAnimation:Stop(0.1)

	self.currentAnimTrack = self.runAnimation

	self.currentAnimTrack.Priority = Enum.AnimationPriority.Core
	self.currentAnimTrack:Play()

	self.currentAnim = "run"
	self.currentAnimSpeed = 1.0
end

function EnemyManager:_playIdleAnimation()
	if self.currentAnim == "idle" then
		return
	end

	if self.currentAnimTrack then
		self.currentAnimTrack:Stop(0.1)
	end

	self.currentAnimTrack = self.idleAnimation
	self.currentAnimTrack.Priority = Enum.AnimationPriority.Core
	self.currentAnimTrack:Play()

	self.currentAnim = "idle"
end

function EnemyManager:_handleRunning(speed)
	if speed > 0.75 then
		self:_playRunAnimation(speed)
		self:_setRunSpeed(speed / 16.0)
	else
		self:_playIdleAnimation(speed)
	end
end

function EnemyManager:_startHealthRegeneration(deltaTime)
	self.regenerationElapsedTime = self.regenerationElapsedTime + deltaTime
	if self.regenerationElapsedTime >= REGENERATION_INTERVAL then
		self.regenerationElapsedTime = 0
		if self.humanoid.Health > 0 and self.humanoid.Health < self.humanoid.MaxHealth then
			self.humanoid.Health = math.min(self.humanoid.Health + REGENERATION_RATE, self.humanoid.MaxHealth)
		end
	end
end

function EnemyManager:_updateHealthPercentage()
	local humanoid = self.humanoid
	
	local healthBarBillboard = self.healthBarBillboard
	local healthBar = healthBarBillboard.Container.HealthBar
	local progressBar = healthBar.ProgressBar
	local textLabel = healthBar.TextLabel
	
	-- Update the health percentage
	local healthPercentage = math.floor(humanoid.Health / humanoid.MaxHealth * 100)
	
	-- Calculate the new size of the progress bar based on the health percentage
	local newSize = math.clamp(humanoid.Health / humanoid.MaxHealth, 0, 1)
	
	progressBar:TweenSize(
		UDim2.new(newSize, 0, 1, 0), -- endSize
		Enum.EasingDirection.Out, -- easingDirection
		Enum.EasingStyle.Quad, -- easingStyle
		0.5, -- time
		true -- override in-progress tweens
	)
	
	textLabel.Text = tostring(healthPercentage) .. "%"
end

function EnemyManager:_getRandomPointInCircle()
	-- PARALLEL: Simultaneous execution
	task.desynchronize()
	
	local radius = math.sqrt(random:NextNumber()) * PATROL_RADIUS
	local angle = random:NextNumber(0, math.pi * 2)
	local x = self.location.X + radius * math.cos(angle)
	local z = self.location.Z + radius * math.sin(angle)

	local position = Vector3.new(x, self.location.Y, z)
	
	-- SERIAL: Sequential execution
	task.synchronize()
	
	return position
end

function EnemyManager:_updateAlignOrientation()
	if not self._active then return end
	
	local target = self.hittingTarget or self.target
	if target then
		self.alignOrientation.Enabled = true
		self.alignOrientation.CFrame = CFrame.new(self._enemy.PrimaryPart.Position, target.PrimaryPart.Position)
	else
		self.alignOrientation.Enabled = false
	end
end

function EnemyManager:_findTarget()
	if self.searchingForTargets then return end
	
	self.searchingForTargets = true
	
	local nearestTarget = nil
	local newTargetDistance = nil
	
	if RunService:IsServer() then
		local overlapParams = OverlapParams.new()
		overlapParams.FilterType = Enum.RaycastFilterType.Exclude
		overlapParams.FilterDescendantsInstances = {self._enemy}
		
		-- PARALLEL: Simultaneous execution
		task.desynchronize()
		
		local hitParts = workspace:GetPartBoundsInRadius(self._enemy.PrimaryPart.Position, ATTACK_RADIUS, overlapParams)
		local detectedTargets = {}
		
		for _, hitPart in ipairs(hitParts) do
			local characterMaybe = hitPart.Parent
			if characterMaybe and not detectedTargets[characterMaybe] then
				local humanoid = characterMaybe:FindFirstChildOfClass("Humanoid")
				local isEnemyFriend = characterMaybe:GetAttribute("EnemyFriend")
				if humanoid and not isEnemyFriend then
					local target = characterMaybe
					local distance = (self._enemy.PrimaryPart.Position - target.PrimaryPart.Position).Magnitude
					
					-- Determine if the charater is the closest
					if not newTargetDistance or distance < newTargetDistance then
						nearestTarget = target
						newTargetDistance = distance
					end
					
					detectedTargets[characterMaybe] = true
				end
			end
		end
		
		-- Clear the table after processing
		for npc, _ in pairs(detectedTargets) do
			detectedTargets[npc] = nil
		end
	else
		local target = Players.LocalPlayer.Character
		local distance = (self._enemy.PrimaryPart.Position - target.PrimaryPart.Position).Magnitude
		
		if distance < ATTACK_RADIUS then
			nearestTarget = target
		end
	end
	
	-- SERIAL: Sequential execution
	task.synchronize()
	
	self.searchingForTargets = false
	
	return nearestTarget
end

function EnemyManager:_attackTargetInRange()
	-- TODO: Improve attack actions and handle them in the cliend
	if #self.valitAttackAnimations == 0 then
		return
	end

	local attackAnimation = self.valitAttackAnimations[math.random(#self.valitAttackAnimations)]

	if attackAnimation then
		attackAnimation:Play()
	end
	
	local humanoidRootPart = self._enemy:FindFirstChild("HumanoidRootPart")
	if not humanoidRootPart then
		return
	end

	local raycastParams = RaycastParams.new()
	raycastParams.FilterDescendantsInstances = {self._enemy}
	raycastParams.FilterType = Enum.RaycastFilterType.Exclude

	-- Concurrent execution across multiple threads
	task.desynchronize()
	
	local target = self.hittingTarget or self.target
	local direction = (target.PrimaryPart.Position - humanoidRootPart.Position).Unit * DETECTION_RANGE_RAY
	local raycastResult = workspace:Raycast(humanoidRootPart.Position, direction, raycastParams)

	-- Sequential execution, affecting shared state
	task.synchronize()

	if raycastResult then
		local hitPart = raycastResult.Instance
		local otherHumanoid = hitPart.Parent:FindFirstChildOfClass("Humanoid")

		if otherHumanoid then
			local isBlocking = otherHumanoid.Parent:GetAttribute("Blocking")
			local isEnemyFriend = otherHumanoid.Parent:GetAttribute("EnemyFriend")
			if not isBlocking and not isEnemyFriend then
				self:_damageTarget(otherHumanoid)
			else
				-- Handle case where player is blocking
			end
		end
	end
end

function EnemyManager:_attackTarget()
	local currentTime = tick()
	self.attacking = true
	
	if currentTime - self.lastAttackTime > ATTACK_DELAY then
		-- Use enemie's tool to attack the player
		if self._tool then
			self.toolInstance:attackTarget()
		end
		
		self:_attackTargetInRange()
		
		self.lastAttackTime = currentTime
		self.attacking = false
	end
end

function EnemyManager:_damageTarget(otherHumanoid)
	otherHumanoid:TakeDamage(ATTACK_DAMAGE)
end

function EnemyManager:_spawnEnemy()
	self._enemy:PivotTo(self.location)
	self._enemy.Parent = workspace
end

function EnemyManager:_cloneNewEnemy()
	return enemies[self._enemyName]:Clone()
end

function EnemyManager:_respawn()
	if not self.respawnable then
		return
	end
	
	-- Clone a new enemy instance
	self._enemy = self:_cloneNewEnemy()
	
	-- Schedule a new task for respawn
	self.respawnTask = task.delay(self._respawnTime, function()
		self:_initialize()

		if self._patrol then
			self:start()
		end

		if not self._active then
			self:stop()
		end
		
		-- Remove the task reference
		self.respawnTask = nil
	end)
end

function EnemyManager:_handleHitEffects()
	if self.hitAnimation then
		self.hitAnimation:Play()
	end
end

function EnemyManager:_checkRecentlyHit()
	local playerName = self._enemy:GetAttribute("RecentlyHit")
	if playerName then
		self:_handleHitEffects()
		self._enemy:SetAttribute("RecentlyHit", nil)

		-- Find the player by name
		local player = Players:FindFirstChild(playerName)
		if player and player.Character then
			self.hittingTarget = player.Character
		end
	end
end

function EnemyManager:_moveToSpawnLocation()
	self.humanoid:MoveTo(self.location.Position)
end

function EnemyManager:_checkDistanceFromSpawn(deltaTime)
	self.teleportCheckElapsedTime = self.teleportCheckElapsedTime + deltaTime
	if self.teleportCheckElapsedTime >= TELEPORT_CHECK_INTERVAL then
		self.teleportCheckElapsedTime = 0
		local currentDistance = (self._enemy.HumanoidRootPart.Position - self.location.Position).Magnitude
		if currentDistance > TELEPORT_DISTANCE_THRESHOLD then
			self._enemy:PivotTo(self.location)
		end
	end
end

function EnemyManager:_checkPriorityTargetDistance()
	local distance = (self._enemy.HumanoidRootPart.Position - self.hittingTarget.HumanoidRootPart.Position).Magnitude
	if distance > ATTACK_RADIUS * 2 then
		self.hittingTarget = nil
	end
end

function EnemyManager:_followTarget()
	if not self._enemy or not self._active then return end
	
	local target = self.hittingTarget or self.target
	if not target then return end
	
	-- PARALLEL: Simultaneous execution
	task.desynchronize()
	
	local targetPosition = target.PrimaryPart.Position
	local targetVelocity = target.PrimaryPart.AssemblyLinearVelocity
	local predictedPosition = targetPosition + targetVelocity * 0.5

	-- Compute a position that is DISTANCE_OFFSET studs away from the target
	local directionToTarget = (predictedPosition - self._enemy.PrimaryPart.Position).Unit
	local targetPositionWithOffset = predictedPosition - directionToTarget * DISTANCE_OFFSET

	-- Detect when the player gets closer than a certain distance to the target
	local distance = (self._enemy.PrimaryPart.Position - targetPositionWithOffset).Magnitude
	
	-- SERIAL: Sequential execution
	task.synchronize()
	
	if distance < ATTACK_RANGE then
		self.inAttackRange = true
		-- Use enemie's attack function
		self:_attackTarget()
	end
	
	self.inAttackRange = false
	self.humanoid:MoveTo(targetPositionWithOffset)
end

function EnemyManager:_followPath()
	if not self._enemy then return end
	
	while self._patrol do
		if not self.inAttackRange then
			local destination = self:_getRandomPointInCircle()
			self.humanoid:MoveTo(destination)
		end
		
		task.wait(random:NextInteger(MIN_REPOSITION_TIME, MAX_REPOSITION_TIME))
	end
end

function EnemyManager:_searchForTarget(deltaTime)
	if not self._active then return end
	
	if not self.hittingTarget then
		self.targetCheckElapsedTime = self.targetCheckElapsedTime + deltaTime
		if self.targetCheckElapsedTime >= TARGET_CHECK_INTERVAL then
			self.targetCheckElapsedTime = 0
			self.target = self:_findTarget()
		end
	end
end

function EnemyManager:_onHeartbeat(deltaTime)
	-- Check for targets periodically
	self:_searchForTarget(deltaTime)
	
	-- Follow and attack target if available
	self:_followTarget()
	
	-- Check if recently hit and handle hit effects
	self:_checkRecentlyHit(deltaTime)
	
	-- Check for teleport periodically
	self:_checkDistanceFromSpawn(deltaTime)
	
	-- Regenerate health over time
	self:_startHealthRegeneration(deltaTime)
	
	-- 
	if self.hittingTarget then
		self:_checkPriorityTargetDistance()
	end
	
	-- Update the enemy's orientation to face the direction it is moving in
	self:_updateAlignOrientation()
	
	-- Check if there are no targets and self._patrol is false
	if not self.target and not self.hittingTarget and not self._patrol then
		self:_moveToSpawnLocation()
	end
end

function EnemyManager:start()
	self._patrol = true
	
	self.corutine = coroutine.wrap(function()
		self:_followPath()
	end)()
end

function EnemyManager:stop()
	self._active = false
	self._patrol = false
	
	if self.corutine then
		coroutine.close(self.corutine)
		self.corutine = nil
	end
	
	self.humanoid.WalkSpeed = 0
end

function EnemyManager:destroy()
	self.humanoidDiedConnection:Disconnect()
	self.humanoidHealthConnection:Disconnect()
	self.humanoidRunningConnection:Disconnect()
	self.heartbeatConnection:Disconnect()
	
	--self.humanoidDiedConnection = nil
	--self.humanoidHealthConnection = nil
	--self.humanoidRunningConnection = nil
	--self.heartbeatConnection = nil
	
	if self._enemy then
		self._enemy:Destroy()
		self._enemy = nil
	end
	
	-- Cancel the previous scheduled task if it exists
	if self.respawnTask then
		task.cancel(self.respawnTask)
		self.respawnTask = nil
	end
	
	-- 
	if self.corutine then
		coroutine.close(self.corutine)
		self.corutine = nil
	end
	
	task.wait()
end

return EnemyManager
