So after I coded these 319 lines of code that controls enemies behavior it was working fantastic! However, give it a couple of weeks and it’s broken again.
Problem(s):
- After beginning the attack animation the enemy will deal damage despite the player being outside/leaving their attack range.
- Upon being parried the enemy no longer plays the parry animation and the “SuccessfulParry” sound no longer plays. However, the damage is still nullified (sometimes).
I know for a fact some of ya’ll do NOT want to read 319 lines of code so I included the main problem first, After that follows the entire code. I apologize in advance for violating your existence with this Yandere Simulator type coding.
local function CheckForSword()
for _, child in pairs(target:GetChildren()) do
if child.ClassName == "Model" then
local Blade = child:FindFirstChild("Blade")
if not Blade then
return
end
local Flash = Blade:FindFirstChild("Flash")
if not Flash then
return
end
local SuccessfulParry = Blade:FindFirstChild("SuccessfulParry")
if SuccessfulParry then
SuccessfulParry:Play()
end
Flash.Enabled = true
wait(1)
Flash.Enabled = false
end
end
end
wait()
if BlockedAttack == false then
distance = checkDistance()
if distance > MaxAttackrange.Value then
return
end
while AttackTrack and AttackTrack.IsPlaying do
wait()
end
target.Humanoid:TakeDamage(DamageAmount) -- Inflict damage
elseif BlockedAttack == true then
distance = checkDistance()
if distance > MaxAttackrange.Value then
return
end
coroutine.wrap(CheckForSword)()
PlayBlockedAnimation()
end
And here’s the entire thing.
local CollectionService = game:GetService("CollectionService")
-- TAG ENEMIES ONLY (Model)
local function enemyBehavior(Enemy)
local humanoid = Enemy:FindFirstChildOfClass("Humanoid")
local PathfindingService = game:GetService("PathfindingService")
Enemy.PrimaryPart:SetNetworkOwner(nil)
-- Config Settings
local Attackrange = Enemy.Configuration.AttackRange.Value -- Measurement in studs
local MaxAttackrange = Enemy.Configuration:WaitForChild("MaxAttackDistance")
local DamageAmount = Enemy.Configuration.DamageAmount.Value
local AttackCooldown = Enemy.Configuration.AttackCooldown.Value -- Seconds
local maxDistanceValue = Enemy.Configuration.MaxDistance.Value
local AnimationLength = Enemy.Configuration.AnimationLength.Value
local CanJump = Enemy.Configuration:WaitForChild("AgentCanJump")
local Height = Enemy.Configuration:WaitForChild("AgentHeight")
local Width = Enemy.Configuration:WaitForChild("AgentRadius")
-- Get Animation
local AttackAnimation = Enemy:WaitForChild("AttackAnimation") -- Reference to the Animation object
local AttackTrack
local BlockAnimation = Enemy:WaitForChild("BlockedAnimation")
local BlockTrack
local function PlayAttackAnimation()
local char = Enemy
local humanoid = char:FindFirstChildOfClass("Humanoid")
if humanoid then
local animator = humanoid:FindFirstChild("Animator")
if not animator then
-- Attempt to wait for the Animator with a timeout
animator = humanoid:WaitForChild("Animator", 5) -- Timeout after 5 seconds
if not animator then
warn("Animator not found on", char.Name)
return -- Exit the function if Animator is not found
end
end
local AttackAnim = Instance.new("Animation")
AttackAnim.AnimationId = AttackAnimation.AnimationId -- Set the AnimationId from the referenced Animation object
AttackTrack = animator:LoadAnimation(AttackAnim)
AttackTrack:Play()
end
end
local function PlayBlockedAnimation()
local humanoid = Enemy:FindFirstChildOfClass("Humanoid")
if humanoid then
local animator = humanoid:FindFirstChild("Animator")
if not animator then
animator = humanoid:WaitForChild("Animator", 5) -- Timeout after 5 seconds
if not animator then
warn("Animator not found on", Enemy.Name)
return -- Exit if Animator is not found
end
end
local NewAnim = Instance.new("Animation")
NewAnim.AnimationId = BlockAnimation.AnimationId -- Set AnimationId from reference
BlockTrack = animator:LoadAnimation(NewAnim)
BlockTrack:Play()
end
end
local function CanSeeTarget(target) -- The enemy will chase and attack if it can see the target
local enemyRootPart = Enemy:FindFirstChild("HumanoidRootPart")
local targetRootPart = target:FindFirstChild("HumanoidRootPart")
if not enemyRootPart or not targetRootPart then
return false
end
local origin = enemyRootPart.Position
local direction = (targetRootPart.Position - enemyRootPart.Position).unit * 40
local ray = Ray.new(origin, direction)
local hit, pos = workspace:FindPartOnRay(ray, Enemy)
if hit then
if hit:IsDescendantOf(target) then
return true
end
else
return false
end
end
local function findTarget() -- Finds the target
local players = game.Players:GetPlayers()
local nearestTarget
local minDistance = maxDistanceValue
for _, player in pairs(players) do
if player.Character and player.Character:FindFirstChild("HumanoidRootPart") then
local target = player.Character
local distance = (Enemy:FindFirstChild("HumanoidRootPart").Position - target.HumanoidRootPart.Position).Magnitude
if distance < minDistance and CanSeeTarget(target) then
nearestTarget = target
minDistance = distance
end
end
end
return nearestTarget
end
local function getPath(destination)
local pathParams = {
AgentHeight = Height.Value,
AgentRadius = Width.Value,
AgentCanJump = CanJump.Value
}
local path = PathfindingService:CreatePath(pathParams)
path:ComputeAsync(Enemy:FindFirstChild("HumanoidRootPart").Position, destination.Position)
return path
end
local function rotateToFace(target)
local enemyRootPart = Enemy:FindFirstChild("HumanoidRootPart")
local targetPosition = target:FindFirstChild("HumanoidRootPart").Position
local enemyHumanoid = Enemy:FindFirstChild("Humanoid")
if not enemyHumanoid then
return
end
if enemyHumanoid.Health == 0 then
return
end
if enemyRootPart then
local lookAtCFrame = CFrame.lookAt(enemyRootPart.Position, Vector3.new(targetPosition.X, enemyRootPart.Position.Y, targetPosition.Z))
enemyRootPart.CFrame = lookAtCFrame
end
end
-- Enemy Attack Logic
local function attack(target)
local function checkDistance()
local enemyRootPart = Enemy:FindFirstChild("HumanoidRootPart")
local targetRootPart = target:FindFirstChild("HumanoidRootPart")
if not enemyRootPart or not targetRootPart then
return math.huge -- Return large distance if part is missing
end
return (enemyRootPart.Position - targetRootPart.Position).Magnitude
end
local distance = checkDistance()
if distance > Attackrange then -- Move towards target if out of range
local targetRootPart = target:FindFirstChild("HumanoidRootPart")
if targetRootPart then
humanoid:MoveTo(targetRootPart.Position)
end
elseif distance <= Attackrange then -- Attack when in range
wait(0.3)
if target.Humanoid and target.Humanoid.Health > 0 then
coroutine.wrap(PlayAttackAnimation)()
end
local function AttackWait()
while AttackTrack and AttackTrack.IsPlaying do
rotateToFace(target) -- Continue facing target
wait()
end
end
coroutine.wrap(AttackWait)()
distance = checkDistance() -- Re-check distance
if distance <= MaxAttackrange.Value then
if target.Humanoid and target.Humanoid.Health > 0 then
local BlockedAttack = false
local function CheckIfParry()
for _, child in pairs(target:GetChildren()) do
if child:IsA("Tool") then
local Parry = child:FindFirstChild("Parry")
if not Parry then
return
end
wait(AnimationLength)
if Parry.Value == false then
return
end
BlockedAttack = true
end
end
end
CheckIfParry()
local function CheckForSword()
for _, child in pairs(target:GetChildren()) do
if child.ClassName == "Model" then
local Blade = child:FindFirstChild("Blade")
if not Blade then
return
end
local Flash = Blade:FindFirstChild("Flash")
if not Flash then
return
end
local SuccessfulParry = Blade:FindFirstChild("SuccessfulParry")
if SuccessfulParry then
SuccessfulParry:Play()
end
Flash.Enabled = true
wait(1)
Flash.Enabled = false
end
end
end
wait()
if BlockedAttack == false then
distance = checkDistance()
if distance > MaxAttackrange.Value then
return
end
while AttackTrack and AttackTrack.IsPlaying do
wait()
end
target.Humanoid:TakeDamage(DamageAmount) -- Inflict damage
elseif BlockedAttack == true then
distance = checkDistance()
if distance > MaxAttackrange.Value then
return
end
coroutine.wrap(CheckForSword)()
PlayBlockedAnimation()
end
end
wait(AttackCooldown) -- Wait for cooldown
end
end
end
local function walkTo(destination)
local path = getPath(destination)
if path.Status == Enum.PathStatus.Success then
for index, waypoint in pairs(path:GetWaypoints()) do
local target = findTarget()
if target and target.Humanoid and target.Humanoid.Health > 0 then
-- print("TARGET FOUND", target.Name)
attack(target)
else
humanoid:MoveTo(waypoint.Position)
humanoid.MoveToFinished:Wait()
end
end
else
humanoid:MoveTo(destination.Position - (Enemy:FindFirstChild("HumanoidRootPart").CFrame.LookVector * 10))
end
end
local function patrol()
local waypoints = Enemy.Parent:FindFirstChild("waypoints")
if waypoints then
local waypointChildren = waypoints:GetChildren()
local randomNum = math.random(1, #waypointChildren)
walkTo(waypointChildren[randomNum])
else
-- Code not move unless the player is within range. Which then the enemy will attack.
end
end
while wait() do
patrol()
end
end
for _, Enemy in pairs(CollectionService:GetTagged("EnemiesTag")) do
coroutine.wrap(enemyBehavior)(Enemy)
end