--made by epiccooldude and auzer (advanced version with enhanced SelfYeet)
local runService = game:GetService("RunService")
local userInputService = game:GetService("UserInputService")
local camera = workspace.CurrentCamera
local player = game.Players.LocalPlayer
local enabled = false
local c = {}
local playerObjects = {}
local punchTool, yeetTool, selfYeetTool
local leftSwingAnimId = "rbxassetid://7218622714"
local rightSwingAnimId = "rbxassetid://7218624486"
local aggroOffsetMultiplier = 1.5
local defOffsetMultiplier = 10
local attackRange = 66 -- Set the attack range to 66 units
local retreatDistance = 10 -- Distance used for dodging
-- New variables:
local minDistance = 30 -- Bot will not retreat if closer than 30 units
local hitDelay = 0.6 -- Delay (in seconds) from attack animation start to hit
local selfYeetCooldown = 3 -- Reduced cooldown for more frequent selfYeet usage
local lastSelfYeetTime = 0 -- Tracks when we last used selfYeet
local selfYeetMaxDistance = 500 -- Maximum distance to consider using selfYeet
local selfYeetMidFightChance = 0.3 -- 30% chance to selfYeet mid-fight for repositioning
-- Advanced field: how much earlier to lead our attack if enemy is moving fast.
-- This value will be computed adaptively.
local maxLeadTime = 0.3
local playerDataClass = {
char = nil,
humanoidRootPart = nil,
humanoid = nil,
bodyGyro = nil,
alive = false,
player = nil,
attackDirection = "None",
focusPart = nil,
offsetMultiplier = aggroOffsetMultiplier,
c = {},
-- Field to store when an attack animation started:
lastAttackStartTime = nil,
}
local playerData
local targetData
local active = true
local runtime = { states = {} }
local function CopyTable(target, destination, references)
references = references or {}
local copy = destination and CopyTable(destination) or {}
references[target] = copy
for k, v in pairs(target) do
if type(v) == "table" then
copy[k] = references[v] or CopyTable(v, nil, references)
else
copy[k] = v
end
end
return copy
end
function playerDataClass:GetPosition()
return self.humanoidRootPart.Position
end
function playerDataClass:GetCFrame()
return self.humanoidRootPart.CFrame
end
function playerDataClass:GetDistance(point)
return (self:GetPosition() - point).Magnitude
end
-- Always face the enemy.
function playerDataClass:LookAt(point, aimFocus)
self.bodyGyro.CFrame = CFrame.new(aimFocus and self:GetFocusPoint() or self:GetPosition(), point)
end
function playerDataClass:new(player)
self = CopyTable(self)
self.player = player
if player.Character then
self:OnCharacterAdded(player.Character)
end
c[#c + 1] = player.CharacterAdded:Connect(function(char)
self:OnCharacterAdded(char)
end)
playerObjects[player] = self
return self
end
function playerDataClass:remove()
for _, connection in ipairs(self.c) do
connection:Disconnect()
end
if self.bodyGyro then
self.bodyGyro:Destroy()
end
end
function playerDataClass:OnToolAdded(instance)
if not instance:IsA("Tool") then
return
end
if instance.Name == "Yeet" then
yeetTool = instance
elseif instance.Name == "Punch" then
punchTool = instance
elseif instance.Name == "SelfYeet" then
selfYeetTool = instance
end
end
function playerDataClass:UpdateFocus(point)
local bodyParts = {self.leftHand, self.rightHand, self.humanoidRootPart}
local closestData = { distance = math.huge }
for _, part in ipairs(bodyParts) do
local distance = (point - part.Position).Magnitude
if distance < closestData.distance then
closestData.part = part
closestData.distance = distance
end
end
self.focusPart = closestData.part
end
function playerDataClass:UpdateAnimations()
-- Check playing animations and record when an attack started.
for _, instance in ipairs(self.animator:GetPlayingAnimationTracks()) do
if instance.Animation.AnimationId == leftSwingAnimId or instance.Animation.AnimationId == rightSwingAnimId then
self.attackDirection = (instance.Animation.AnimationId == leftSwingAnimId and "Left") or (instance.Animation.AnimationId == rightSwingAnimId and "Right")
self.offsetMultiplier = defOffsetMultiplier
self.lastAttackStartTime = tick() -- Record attack start time
return
end
end
self.attackDirection = "None"
self.offsetMultiplier = aggroOffsetMultiplier
end
function playerDataClass:GetFocusPoint()
return self.focusPart.Position
end
function playerDataClass:OnCharacterAdded(char)
self.char = char
self.humanoidRootPart = self.char:FindFirstChild("HumanoidRootPart")
self.humanoid = self.char:FindFirstChild("Humanoid")
self.animator = self.humanoid:WaitForChild("Animator")
self.leftHand = self.char:WaitForChild("LeftHand")
self.rightHand = self.char:WaitForChild("RightHand")
self.focusPart = self.humanoidRootPart
if self.player == game.Players.LocalPlayer then
self.bodyGyro = Instance.new("BodyGyro")
self.bodyGyro.P = 0
self.bodyGyro.D = 100
self.bodyGyro.MaxTorque = Vector3.new(0, 10000000, 0)
self.bodyGyro.Parent = self.humanoidRootPart
c[#c + 1] = char.ChildAdded:Connect(function(child)
self:OnToolAdded(child)
end)
self:OnToolAdded(self.char:FindFirstChildOfClass("Tool") or workspace)
c[#c + 1] = self.humanoid.Died:Connect(function()
self.alive = false
end)
end
self.alive = true
end
playerData = CopyTable(playerDataClass)
local function errorHandler(err)
warn(err .. "\n" .. debug.traceback())
end
function runtime:DisableState(stateName)
runtime.states[stateName].active = false
while runtime.states[stateName].tasks ~= 0 do
task.wait()
end
end
function runtime:DisableStates()
for stateName, state in pairs(runtime.states) do
if stateName ~= "Default" and state.active then
runtime:DisableState(stateName)
end
end
end
function runtime:EnableState(stateName)
if runtime.states[stateName].active then
return
end
local state = runtime.states[stateName]
local dt = 0
state.tasks = 0
state.active = true
for _, func in pairs(state.functions) do
state.tasks = state.tasks + 1
task.spawn(function()
while active and state.active do
dt = runService.Heartbeat:Wait()
xpcall(function() func(state, dt) end, errorHandler)
end
state.tasks = state.tasks - 1
end)
end
end
function runtime:SwitchState(stateName)
runtime:DisableStates()
runtime:EnableState(stateName)
end
function runtime:NewState(stateName)
local data = { functions = {}, tasks = 0, active = false }
runtime.states[stateName] = data
return data
end
runtime:NewState("Default")
runtime:NewState("Attack")
local function ScanPlayers()
local closestPlayerData = { distance = math.huge }
for _, targetPlayer in ipairs(game.Players:GetChildren()) do
if targetPlayer == game.Players.LocalPlayer then continue end
local targetChar = targetPlayer.Character
if not targetChar then continue end
local humanoidRootPart = targetChar:FindFirstChild("HumanoidRootPart")
local humanoid = targetChar:FindFirstChild("Humanoid")
if not humanoidRootPart or not humanoid or humanoid.Health == 0
or humanoidRootPart.AssemblyMass < playerData.humanoidRootPart.AssemblyMass - 70000 then
continue
end
local distance = playerData:GetDistance(humanoidRootPart.Position)
if distance < closestPlayerData.distance then
closestPlayerData = { distance = distance, player = targetPlayer }
end
end
if closestPlayerData.player then
targetData = playerObjects[closestPlayerData.player]
targetData:UpdateAnimations()
targetData:UpdateFocus(playerData.focusPart.Position)
playerData:UpdateFocus(targetData.humanoidRootPart.Position)
else
if targetData then targetData:remove() end
targetData = nil
end
task.wait(0.1)
end
-- Enhanced SelfYeet function with multiple tactical positions
local function UseSelfYeet(positionType)
if not selfYeetTool or not selfYeetTool.Parent or not targetData then return false end
local currentTime = tick()
if currentTime - lastSelfYeetTime < selfYeetCooldown then return false end
local targetPosition = targetData:GetPosition()
local targetCFrame = targetData:GetCFrame()
local selfYeetPosition
-- Different tactical positions based on the requested type
if positionType == "behind" then
-- Position behind the target
selfYeetPosition = targetPosition - (targetCFrame.LookVector * 6)
elseif positionType == "side" then
-- Position to the side of the target (randomly left or right)
local sideDir = math.random() > 0.5 and targetCFrame.RightVector or -targetCFrame.RightVector
selfYeetPosition = targetPosition + (sideDir * 5)
elseif positionType == "front" then
-- Position in front of the target
selfYeetPosition = targetPosition + (targetCFrame.LookVector * 6)
elseif positionType == "above" then
-- Position slightly above the target
selfYeetPosition = targetPosition + Vector3.new(0, 7, 0)
elseif positionType == "long_distance" then
-- For long distances, aim directly at the target position
selfYeetPosition = targetPosition
else
-- Default to a random tactical position
local positions = {"behind", "side", "front"}
return UseSelfYeet(positions[math.random(1, #positions)])
end
-- Aim camera at the desired selfYeet position (critical, as camera direction determines selfYeet direction)
camera.CFrame = CFrame.new(camera.CFrame.Position, selfYeetPosition)
-- Equip and activate SelfYeet
if selfYeetTool.Parent == player.Backpack then
playerData.humanoid:EquipTool(selfYeetTool)
task.wait(0.1) -- Small delay for equip
end
selfYeetTool:Activate()
lastSelfYeetTime = currentTime
-- Schedule recovery from ragdoll after selfYeet
task.delay(0.5, function()
-- Recovery code to get up after selfYeet
game:GetService("Players").LocalPlayer.PlayerGui.RTGui.codebase.ragdoll.Events.Toggle:FireServer(table.unpack({
[1] = "R",
[2] = false,
}))
end)
return true
end
local function TrackCharacters()
if not targetData or not targetData.alive then
if playerData.bodyGyro then playerData.bodyGyro.P = 0 end
return
end
playerData.bodyGyro.P = 1000000
local playerPos = playerData:GetFocusPoint()
local targetPos = targetData:GetFocusPoint()
local distanceToTarget = (targetPos - playerPos).Magnitude
-- Force facing: always look at enemy's position.
playerData:LookAt(targetPos, true)
-- Long distance SelfYeet (500 studs or farther)
if distanceToTarget >= selfYeetMaxDistance and UseSelfYeet("long_distance") then
return
end
local currentTime = tick()
local xz = Vector3.new(1, 0, 1)
-- Mid-fight random SelfYeet for repositioning
if distanceToTarget < attackRange * 1.5 and distanceToTarget > minDistance and
math.random() < selfYeetMidFightChance and
currentTime - lastSelfYeetTime > selfYeetCooldown then
-- Randomly choose a tactical position (side, behind, or front)
local positions = {"side", "behind", "front"}
-- If enemy is attacking, prioritize getting behind or to the side
if targetData.attackDirection ~= "None" then
positions = {"behind", "side"}
end
if UseSelfYeet(positions[math.random(1, #positions)]) then
return
end
end
-- Advanced dodge: if enemy is attacking (pre-hit phase) and moving fast, dodge more aggressively.
if targetData.lastAttackStartTime and (currentTime - targetData.lastAttackStartTime) < hitDelay then
-- If we're being attacked, consider using SelfYeet to dodge (30% chance)
if math.random() < 0.3 then
if UseSelfYeet("side") then return end
end
-- Standard dodge if SelfYeet not used
local dodgeDir
if targetData.attackDirection == "Left" then
dodgeDir = targetData:GetCFrame().rightVector
elseif targetData.attackDirection == "Right" then
dodgeDir = -targetData:GetCFrame().rightVector
else
dodgeDir = playerData:GetCFrame().rightVector
end
playerData.humanoid:MoveTo(playerPos + dodgeDir * retreatDistance)
return
end
local lookVector = (targetPos - (targetData:GetCFrame() * CFrame.new(1, 0, 0)).p).unit
local vector = (targetPos - playerPos) * xz
local lookDirection = targetData:GetCFrame().lookVector
local movementAngle = math.acos(vector:Dot(lookDirection) / vector.magnitude)
local targetAngle = math.asin(vector:Dot(lookVector) / vector.magnitude)
local rotDirection = 0
if math.deg(movementAngle) >= 1 then
rotDirection = -math.sign(targetAngle) * math.max(1, vector.magnitude / 3)
end
local aggroOffset = playerData.humanoidRootPart.Size.X / 2 + targetData.humanoidRootPart.Size.X / 2
local defOffset = aggroOffset * targetData.offsetMultiplier
local myLookDirection = playerData:GetCFrame().lookVector
local offset = defOffset + (aggroOffset - defOffset) * (myLookDirection:Dot(lookVector) + 1) / 2
local movePoint = (CFrame.new(playerPos * xz, targetPos * xz) * CFrame.new(rotDirection, 0, offset - vector.magnitude)).Position
-- Normal movement logic
if distanceToTarget > attackRange * 1.5 and math.random() < 0.4 then
-- Occasionally use SelfYeet to close distance quickly
UseSelfYeet("front")
elseif distanceToTarget > attackRange then
playerData.humanoid:MoveTo(movePoint)
elseif distanceToTarget < minDistance then
local rightVector = playerData.humanoidRootPart.CFrame.rightVector
local lateralDir = (targetPos - playerPos).Unit
local dot = lateralDir:Dot(rightVector)
local dodgeDirection = (dot >= 0 and rightVector or -rightVector)
playerData.humanoid:MoveTo(playerPos + dodgeDirection * retreatDistance)
elseif distanceToTarget < attackRange then
playerData.humanoid:MoveTo(playerPos - myLookDirection * retreatDistance)
else
playerData.humanoid:MoveTo(movePoint)
end
end
-- SUPER OP Attack function with advanced prediction.
local function Attack()
local currentTime = tick()
local ourPredictedHitTime = currentTime + hitDelay
-- Get enemy's velocity (if available).
local enemyVel = (targetData and targetData.humanoidRootPart.AssemblyLinearVelocity) or Vector3.new(0, 0, 0)
-- Predict enemy's hit time.
local enemyAttackStart = targetData and targetData.lastAttackStartTime or currentTime
local enemyPredictedHitTime = enemyAttackStart + hitDelay
-- Predict enemy's position at hit time.
local enemyPredictedPos = targetData and (targetData.humanoidRootPart.Position + enemyVel * hitDelay) or targetData:GetFocusPoint()
-- Calculate adaptive lead time based on enemy speed.
local enemySpeed = enemyVel.Magnitude
local leadTime = math.clamp(enemySpeed * 0.005, 0, maxLeadTime)
-- Our effective hit time is adjusted by leadTime (attacking earlier).
local ourEffectiveHitTime = ourPredictedHitTime - leadTime
-- Use math to decide: if our effective hit lands before enemy's, attack now.
if ourEffectiveHitTime <= enemyPredictedHitTime then
UseTool(punchTool)
if yeetTool then task.wait(0.5) end
UseTool(yeetTool)
playerData.lastAttackStartTime = currentTime
else
-- Otherwise, schedule our attack so that it lands just before enemy's.
local delayTime = (enemyPredictedHitTime - ourEffectiveHitTime) - 0.05
if delayTime < 0 then delayTime = 0 end
task.delay(delayTime, function()
if active and targetData and targetData.alive then
UseTool(punchTool)
if yeetTool then task.wait(0.5) end
UseTool(yeetTool)
playerData.lastAttackStartTime = tick()
end
end)
end
end
-- Function to automatically equip and use tools
function UseTool(tool)
if not tool or not tool.Parent then return end
if tool.Parent == player.Backpack then
playerData.humanoid:EquipTool(tool)
end
tool:Activate()
end
-- Function to ensure tools are equipped
local function EnsureToolsEquipped()
local backpack = player:WaitForChild("Backpack")
-- Check for and equip all relevant tools if not already equipped
if not yeetTool then
local yeetInBackpack = backpack:FindFirstChild("Yeet")
if yeetInBackpack then
yeetTool = yeetInBackpack
end
end
if not punchTool then
local punchInBackpack = backpack:FindFirstChild("Punch")
if punchInBackpack then
punchTool = punchInBackpack
end
end
if not selfYeetTool then
local selfYeetInBackpack = backpack:FindFirstChild("SelfYeet")
if selfYeetInBackpack then
selfYeetTool = selfYeetInBackpack
end
end
task.wait(1) -- Check periodically
end
local function onToolAdded(instance)
if not instance:IsA("Tool") then return end
if instance.Name == "Yeet" then
yeetTool = instance
elseif instance.Name == "Punch" then
punchTool = instance
elseif instance.Name == "SelfYeet" then
selfYeetTool = instance
end
end
c[#c + 1] = userInputService.InputBegan:Connect(function(input)
if input.KeyCode == Enum.KeyCode.End then
for _, connection in ipairs(c) do
connection:Disconnect()
end
active = false
runtime:DisableStates()
playerData:remove()
elseif input.KeyCode == Enum.KeyCode.X then
if enabled then
enabled = false
runtime:DisableState("Attack")
else
enabled = true
runtime:EnableState("Attack")
end
end
end)
playerData = playerDataClass:new(player)
for _, p in ipairs(game.Players:GetChildren()) do
if p == game.Players.LocalPlayer then continue end
playerDataClass:new(p)
end
c[#c + 1] = game.Players.PlayerAdded:Connect(function(p)
playerDataClass:new(p)
end)
runtime.states.Default.functions[ScanPlayers] = ScanPlayers
runtime.states.Default.functions[EnsureToolsEquipped] = EnsureToolsEquipped
runtime.states.Attack.functions[TrackCharacters] = TrackCharacters
runtime.states.Attack.functions[Attack] = Attack
runtime:EnableState("Default")
print("Enhanced Combat Bot with Advanced SelfYeet loaded! Press X to toggle.") sometimes when selfyeeting, it doesnt have time to selfyeet, before it switches to the other tools, fix that