make the script only run while the user is holding x and only run if there is a player within 25 studs

local Funcs = {}

-- Ensure 'Filter' folder exists in the workspace
local Filter = workspace:FindFirstChild("Filter")
if not Filter then
    Filter = Instance.new("Folder")
    Filter.Name = "Filter"
    Filter.Parent = workspace
end

-- Function to get all player characters
local function GetPlayerCharacters()
    local charList = {}
    for _, player in pairs(game.Players:GetPlayers()) do
        if player.Character then
            table.insert(charList, player.Character)
        end
    end
    return charList
end

-- Functions for the bot's operations
Funcs.GetDist = function(Model, Position)
    local rightArm = Model:FindFirstChild("Right Arm") or Model:FindFirstChild("RightHand")
    if rightArm then
        return (rightArm.Position - Position).Magnitude
    else
        return math.huge
    end
end

Funcs.GetClosestEnemy = function(Model)
    local ClosestEnemy = nil
    local ClosestDistance = math.huge
    for _, player in pairs(game.Players:GetPlayers()) do 
        if player.Character and player.Character ~= Model and player.Character:FindFirstChildOfClass("Humanoid") and player.Character:FindFirstChildOfClass("Humanoid").Health > 0 then
            local v = player.Character
            if v.PrimaryPart and Model.PrimaryPart then
                local Dist = (v.PrimaryPart.Position - Model.PrimaryPart.Position).Magnitude
                if Dist < ClosestDistance then
                    ClosestDistance = Dist
                    ClosestEnemy = v
                end
            end
        end
    end
    return ClosestEnemy
end

Funcs.GetDistance = function(Model, OtherModel)
    if Model.PrimaryPart and OtherModel.PrimaryPart then
        return (Model.PrimaryPart.Position - OtherModel.PrimaryPart.Position).Magnitude
    else
        return math.huge
    end
end

Funcs.GetClosestPart = function(Position, OtherModel)
    local ClosestPart = nil
    local ClosestDistance = math.huge
    for _, v in pairs(OtherModel:GetChildren()) do 
        if v:IsA("BasePart") then 
            local Offset = Vector3.new(0, 0, 0)
            if v.Name == "Right Arm" or v.Name == "RightHand" then 
                Offset = OtherModel.PrimaryPart.CFrame.LookVector * 3
            end
            local Dist = (Position - (v.Position + Offset)).Magnitude
            if Dist < ClosestDistance then
                ClosestDistance = Dist
                ClosestPart = v
            end
        end
    end
    return ClosestPart
end

Funcs.GetRetreatDirection = function(Model, ClosestEnemy, Dist)
    local Difference = (Model.PrimaryPart.Position - ClosestEnemy.PrimaryPart.Position)
    local Params = RaycastParams.new()
    Params.FilterType = Enum.RaycastFilterType.Blacklist
    Params.FilterDescendantsInstances = GetPlayerCharacters()
    local Angle = -135
    local BestDist = 0
    local BestPosition = nil
    local Raycasts = 10
    for _ = 1, Raycasts do
        local RayDirection = (CFrame.lookAt(Model.PrimaryPart.Position, Model.PrimaryPart.Position + Difference) * CFrame.Angles(0, math.rad(Angle), 0)).LookVector * Dist
        local Raycast = workspace:Raycast(Model.PrimaryPart.Position, RayDirection, Params)

        if Raycast then 
            local NewDist = (Raycast.Position - ClosestEnemy.PrimaryPart.Position).Magnitude
            if NewDist > BestDist then 
                BestPosition = Raycast.Position
                BestDist = NewDist
            end
        end
        Angle += 270 / Raycasts
    end
    return BestPosition
end

Funcs.SideCast = function(ClosestEnemy)
    local Params = RaycastParams.new()
    Params.FilterType = Enum.RaycastFilterType.Blacklist
    Params.FilterDescendantsInstances = GetPlayerCharacters()
    local Angle = 0
    local Raycasts = 10
    for _ = 1, Raycasts do 
        local RayDirection = (ClosestEnemy.PrimaryPart.CFrame * CFrame.Angles(0, math.rad(Angle), 0)).LookVector * 8
        local Raycast = workspace:Raycast(ClosestEnemy.PrimaryPart.Position, RayDirection, Params)
        Angle += 360 / Raycasts
        if Raycast then 
            return Raycast
        end
    end
    return nil
end

Funcs.FindBestAngle = function(Model, Enemy, EnemyTool)
    local Params = RaycastParams.new()
    Params.FilterType = Enum.RaycastFilterType.Blacklist
    local FilterInstances = {Model, Filter, EnemyTool}
    -- Add Enemy's accessories to filter
    if Enemy:FindFirstChild("Humanoid") then
        for _, accessory in pairs(Enemy.Humanoid:GetAccessories()) do
            table.insert(FilterInstances, accessory)
        end
    end
    Params.FilterDescendantsInstances = FilterInstances
    local Raycasts = {}
    local rightArm = Model:FindFirstChild("Right Arm") or Model:FindFirstChild("RightHand")
    if not rightArm then return 0 end
    table.insert(Raycasts, {workspace:Raycast(rightArm.Position, Model.PrimaryPart.CFrame.LookVector * 10, Params), 0})
    table.insert(Raycasts, {workspace:Raycast(rightArm.Position, (Model.PrimaryPart.CFrame * CFrame.Angles(0, -math.rad(1), 0)).LookVector.Unit * 10, Params), -1})
    table.insert(Raycasts, {workspace:Raycast(rightArm.Position, (Model.PrimaryPart.CFrame * CFrame.Angles(0, math.rad(1), 0)).LookVector.Unit * 10, Params), 1})
    table.insert(Raycasts, {workspace:Raycast(rightArm.Position, (Model.PrimaryPart.CFrame * CFrame.Angles(0, math.rad(2), 0)).LookVector.Unit * 10, Params), 2})
    table.insert(Raycasts, {workspace:Raycast(rightArm.Position, (Model.PrimaryPart.CFrame * CFrame.Angles(0, -math.rad(2), 0)).LookVector.Unit * 10, Params), -2})
    Filter:ClearAllChildren()
    local ClosestDistance = math.huge
    local ClosestAngle = 0
    for _, v in pairs(Raycasts) do 
        local RaycastResult = v[1]
        local Angle = v[2]
        if RaycastResult then
            local Dist = Funcs.GetDist(Model, RaycastResult.Position)
            if Dist < ClosestDistance then
                ClosestDistance = Dist
                ClosestAngle = Angle
            end
        end
    end
    return ClosestAngle
end

-- Additional utility functions
local GetXDist = function(Pos1, Pos2)
    return math.sqrt((Pos1.X - Pos2.X) ^ 2 + (Pos1.Z - Pos2.Z) ^ 2)
end

local FairLookAt = function(Pos1, Pos2)
    return CFrame.lookAt(Pos1, Pos2 - Vector3.new(0, Pos2.Y - Pos1.Y, 0)) * CFrame.Angles(0, math.rad(17.5), 0)
end

local Grips = {
    Up = CFrame.new(0, 0, -1.70000005, 0, 0, 1, 1, 0, 0, 0, 1, 0),
    Out = CFrame.new(0, 0, -1.70000005, 0, 1, 0, 1, -0, 0, 0, 0, -1)
}

-- Main bot function
local function SwordFightBot(Model)
    local Humanoid = Model:FindFirstChildOfClass("Humanoid")
    local PrimaryPart = Model.PrimaryPart
    local Tool = Model:FindFirstChildOfClass("Tool")
    if not Tool then 
        repeat 
            Tool = Model:FindFirstChildOfClass("Tool") 
            wait()
        until Tool
    end
    local Tick = 0
    local LastAttack = 0
    local LastOpponentAttack = 0
    local EnemyExtended = false

    while Model.PrimaryPart and Humanoid and Humanoid.Health > 0 and Model.Parent == workspace do  
        Tick += game:GetService("RunService").RenderStepped:Wait()
        local ClosestEnemy = Funcs.GetClosestEnemy(Model)

        if ClosestEnemy and ClosestEnemy.PrimaryPart then 
            local ClosestPart = Funcs.GetClosestPart(Tool.Handle.Position, ClosestEnemy)
            local EnemyTool = ClosestEnemy:FindFirstChildOfClass("Tool")

            local LungeCooldown = false
            if EnemyTool then 
                if EnemyTool.Grip == Grips.Out then
                    if not EnemyExtended then 
                        LastOpponentAttack = Tick
                    end
                    EnemyExtended = true
                end
            end
            if Tool.Grip ~= Grips.Out then
                LungeCooldown = true
            end

            local HigherHp = Humanoid.Health > ClosestEnemy.Humanoid.Health
            local LowerHp = Humanoid.Health < ClosestEnemy.Humanoid.Health
            local XDist = GetXDist(PrimaryPart.Position, ClosestEnemy.PrimaryPart.Position)

            local Distance = Funcs.GetDistance(Model, ClosestEnemy)
            local HeightDifference = math.abs(ClosestEnemy.PrimaryPart.Position.Y - PrimaryPart.Position.Y)
            local SameHeight = HeightDifference < 3

            local ClosestPos = ClosestEnemy.PrimaryPart.Position

            if EnemyExtended and LungeCooldown and SameHeight and Distance > 6 and EnemyTool then 
                Model:SetPrimaryPartCFrame(FairLookAt(PrimaryPart.Position, EnemyTool.Handle.Position + ClosestEnemy.PrimaryPart.CFrame.LookVector * EnemyTool.Handle.Size.Y * 2.2))
            else 
                if Tool.Parent ~= Model then
                    Tool.Parent = Model
                end

                Model:SetPrimaryPartCFrame(CFrame.lookAt(PrimaryPart.Position, (ClosestPart.Position - Vector3.new(0, ClosestPart.Position.Y - PrimaryPart.Position.Y, 0))) * CFrame.Angles(0, math.rad(17.5), 0))
                local Angle = 0
                local LastAngle = Angle
                -- Points sword towards the closest position on the part
                for _ = 1, 10 do
                    Angle = Angle + Funcs.FindBestAngle(Model, ClosestEnemy, EnemyTool)
                    if LastAngle == Angle then 
                        -- The wiggle to trigger 'Touched' events
                        if HigherHp then
                            Angle += math.random(-10, 10)
                        else 
                            Angle += math.random(-2, 2)
                        end
                        Model:SetPrimaryPartCFrame(CFrame.lookAt(PrimaryPart.Position, (ClosestPart.Position - Vector3.new(0, ClosestPart.Position.Y - PrimaryPart.Position.Y, 0))) * CFrame.Angles(0, math.rad(17.5 + Angle), 0))
                        break
                    end
                    LastAngle = Angle
                    Model:SetPrimaryPartCFrame(CFrame.lookAt(PrimaryPart.Position, (ClosestPart.Position - Vector3.new(0, ClosestPart.Position.Y - PrimaryPart.Position.Y, 0))) * CFrame.Angles(0, math.rad(17.5 + Angle), 0))
                end
            end 

            local EnemyToModelVector = (PrimaryPart.Position - ClosestEnemy.PrimaryPart.Position).Unit
            local MoveDirection = ClosestEnemy.Humanoid.MoveDirection
            local EnemyIsMovingTowardsModel = (EnemyToModelVector - MoveDirection).Magnitude < 1
            -- Goes aggressive when enemy is on cooldown
            local IdealDistance = EnemyExtended and 8 or 7
            -- Backs off
            IdealDistance += EnemyIsMovingTowardsModel and 2 or 0

            -- Detects when last lunged
            if Tool.Enabled and Tool.Grip == Grips.Up then
                LastAttack = Tick
            end

            -- Pushes when enemy is about to run out of lunge
            if EnemyTool and not EnemyTool.Enabled and (Tick - LastOpponentAttack > 0.1) and (Tick - LastOpponentAttack > 0.2) and  (Tick - LastAttack > 0.05) then 
                IdealDistance -= 2
            end

            -- Backs off when about to stop lunging
            if (Tick - LastAttack > 0.5) then
                IdealDistance += 2
            end

            if XDist < 15 then
                Tool:Activate()
            end

            if Distance > 8 then
                if LowerHp then 
                    IdealDistance += 10
                end
            end

            local Offset = -ClosestEnemy.PrimaryPart.CFrame.RightVector
            local SideCastResult = Funcs.SideCast(ClosestEnemy)

            if HigherHp and SameHeight then 
                Offset *= 3
                IdealDistance = 2
            end

            if SideCastResult or Tool.Grip == Grips.Up then 
                Offset = Vector3.new(0, 0, 0)
            end

            -- Favors the side without the sword
            local SpacedPosition = (ClosestPos - (ClosestPos - PrimaryPart.Position).Unit * IdealDistance) + Offset
            if HigherHp then 
                Offset *= 3
            end

            if Distance < IdealDistance then 
                local RetreatPos = Funcs.GetRetreatDirection(Model, ClosestEnemy, 10)
                if RetreatPos and EnemyIsMovingTowardsModel then 
                    RetreatPos = Funcs.GetRetreatDirection(Model, ClosestEnemy, 100)
                    Humanoid:MoveTo(RetreatPos)
                else
                    Humanoid:MoveTo(SpacedPosition)
                end
            else 
                Humanoid:MoveTo(SpacedPosition)
            end
        end
    end
end

-- Start the bot for the local player
local player = game.Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
SwordFightBot(character)