-- Name: UltraInstinct_Dodge_AI_V3 -- Parent: StarterPlayer -> StarterCharacterScripts local RunService = game:GetService("RunService") local Workspace = game:GetService("Workspace") local Players = game:GetService("Players") local Debris = game:GetService("Debris") local TweenService = game:GetService("TweenService") local player = Players.LocalPlayer local character = player.Character or player.CharacterAdded:Wait() local rootPart = character:WaitForChild("HumanoidRootPart") local humanoid = character:WaitForChild("Humanoid") -- // CONFIGURATION // local CONFIG = { -- Detection ScanRadius = 18, -- Area to check for threats ProjectileSpeedMin = 25, -- Minimum speed to consider a part a "projectile" -- Movement DodgeDist = 14, -- How far to teleport WallBuffer = 2.5, -- Distance to keep from walls -- Cooldowns & Stress BaseCooldown = 1.3, OverdriveCooldown = 0.2, -- Spam dodge speed StressThreshold = 3, StressDecay = 0.6, } -- // STATE VARIABLES // local stressLevel = 0 local lastDodgeTime = 0 local isDodging = false -- Raycast params for environment (Walls/Floors) local envParams = RaycastParams.new() envParams.FilterType = Enum.RaycastFilterType.Exclude -- Raycast params for incoming threats (Projectiles) local threatParams = RaycastParams.new() threatParams.FilterType = Enum.RaycastFilterType.Exclude -- // INITIALIZATION // local function updateFilters() local ignoreList = {character, Workspace.CurrentCamera} envParams.FilterDescendantsInstances = ignoreList -- For projectile prediction, we want to see if it hits US threatParams.FilterDescendantsInstances = {Workspace.CurrentCamera} -- Don't ignore character, we need to know if we get hit end character.DescendantAdded:Connect(updateFilters) updateFilters() -- // VISUAL FX // local function playEffects(mode, pos) local isPanic = mode == "Panic" local color = isPanic and Color3.fromRGB(255, 40, 40) or Color3.fromRGB(80, 255, 200) -- Red vs Cyan -- 1. Sound local sound = Instance.new("Sound", rootPart) sound.SoundId = "rbxassetid://130768133" sound.PlaybackSpeed = isPanic and 1.4 or 1.0 sound.Volume = 0.6 sound:Play() Debris:AddItem(sound, 1) -- 2. Afterimage (Ghost) coroutine.wrap(function() for _, v in pairs(character:GetChildren()) do if v:IsA("BasePart") and v.Transparency < 1 then local g = v:Clone() g.Parent = Workspace g.Anchored = true g.CanCollide = false g.CFrame = v.CFrame g.Material = Enum.Material.ForceField g.Color = color g.Transparency = 0.2 -- Remove scripts/joints for _, c in pairs(g:GetChildren()) do c:Destroy() end Debris:AddItem(g, 0.4) -- Quick fade local tween = TweenService:Create(g, TweenInfo.new(0.4), {Transparency = 1}) tween:Play() end end end)() end -- // PHYSICS CALCULATIONS // -- Checks if a position is valid (Not inside a wall, not over a void) local function getSafePosition(origin, direction, distance) -- 1. Wall Check local ray = Workspace:Raycast(origin, direction * distance, envParams) local targetPos if ray then -- Wall detected: Slide logic local hitNormal = ray.Normal -- Calculate sliding vector: remove the component of movement going INTO the wall local slideDir = direction - (direction:Dot(hitNormal) * hitNormal) -- If slide vector is too small (cornered), just stop before wall if slideDir.Magnitude < 0.1 then targetPos = origin + (direction * (ray.Distance - CONFIG.WallBuffer)) else -- Slide along the wall targetPos = origin + (slideDir.Unit * (distance * 0.8)) end else targetPos = origin + (direction * distance) end -- 2. Void Check (Floor Check) -- Raycast down from the proposed target position local floorRay = Workspace:Raycast(targetPos + Vector3.new(0, 4, 0), Vector3.new(0, -20, 0), envParams) if not floorRay then -- If dodging leads to death (void), cancel movement or reduce distance drastically return origin -- Stay put if the alternative is falling end return targetPos end -- // SCANNER SYSTEM // local function scanForThreats() local scanParams = OverlapParams.new() scanParams.FilterDescendantsInstances = {character} scanParams.FilterType = Enum.RaycastFilterType.Exclude -- Get everything nearby local parts = Workspace:GetPartBoundsInRadius(rootPart.Position, CONFIG.ScanRadius, scanParams) local chosenThreat = nil local threatType = "None" -- "Melee" or "Projectile" local threatVector = Vector3.new(0,0,0) -- Direction available to dodge away from for _, part in pairs(parts) do -- >> CHECK 1: FAST MOVING PARTS (Projectiles/Flying Objects) << if not part.Anchored and part.AssemblyLinearVelocity.Magnitude > CONFIG.ProjectileSpeedMin then local vel = part.AssemblyLinearVelocity local toMe = (rootPart.Position - part.Position) -- Is it moving towards me? if vel.Unit:Dot(toMe.Unit) > 0.5 then -- PREDICTION: Raycast forward from the part to see if it will actually hit us within 0.2s local predictRay = Workspace:Raycast(part.Position, vel * 0.2, threatParams) if predictRay and predictRay.Instance:IsDescendantOf(character) then chosenThreat = part threatType = "Projectile" threatVector = vel.Unit -- Movement direction of projectile break -- Priority: Dodge bullet immediately end end end -- >> CHECK 2: HUMANOIDS (Melee/Players) << -- Works on invisible players because we check HumanoidRootPart existence, not transparency local model = part.Parent if model and model:FindFirstChild("Humanoid") and model ~= character then local enemyRoot = model:FindFirstChild("HumanoidRootPart") if enemyRoot then local eHum = model.Humanoid if eHum.Health > 0 then local toPlayer = (rootPart.Position - enemyRoot.Position) local dist = toPlayer.Magnitude -- Are they looking at me? local facing = enemyRoot.CFrame.LookVector:Dot(-toPlayer.Unit) -- -1 to 1 local speed = enemyRoot.AssemblyLinearVelocity.Magnitude -- Logic: Close & Facing me OR Close & Moving Fast if dist < 10 and (facing > 0.6 or speed > 10) then chosenThreat = enemyRoot threatType = "Melee" threatVector = (rootPart.Position - enemyRoot.Position).Unit -- Direction away from enemy end end end end end return chosenThreat, threatType, threatVector end -- // MAIN BRAIN // RunService.Heartbeat:Connect(function(dt) local now = tick() -- Stress Decay if stressLevel > 0 then stressLevel = math.max(0, stressLevel - (CONFIG.StressDecay * dt)) end -- Determine Cooldown based on Stress local currentCooldown = (stressLevel > CONFIG.StressThreshold) and CONFIG.OverdriveCooldown or CONFIG.BaseCooldown if isDodging or (now - lastDodgeTime < currentCooldown) then return end -- -> RUN SCAN local threat, type, dirVec = scanForThreats() if threat then isDodging = true local mode = (stressLevel > CONFIG.StressThreshold) and "Panic" or "Normal" -- // DODGE IRECTION LOGIC // local dodgeDir = Vector3.new(0,0,0) if type == "Projectile" then -- If bullet coming, dodge PERPENDICULAR (Sideways usually) local left = dirVec:Cross(Vector3.new(0, 1, 0)) dodgeDir = (math.random() > 0.5) and left or -left stressLevel = stressLevel + 2 -- Projectiles are scary, high stress gain elseif type == "Melee" then -- If Melee, calculate strategy if mode == "Panic" then -- Panic behavior: Teleport BEHIND enemy (dirVec is currently Away, so reverse it) dodgeDir = -dirVec + (Vector3.new(math.random()-0.5, 0, math.random()-0.5)) -- Behind + Jitter else -- Normal behavior: Back up or Circle local left = dirVec:Cross(Vector3.new(0, 1, 0)) dodgeDir = dirVec + (left * 0.5) -- Angle backwards end stressLevel = stressLevel + 1 end -- Normalize dodgeDir = Vector3.new(dodgeDir.X, 0, dodgeDir.Z).Unit -- // EXECUTE MOVEMENT // local finalPos = getSafePosition(rootPart.Position, dodgeDir, CONFIG.DodgeDist) -- Only move if significant distance if (finalPos - rootPart.Position).Magnitude > 2 then -- 1. Reset Velocity (Stop momentum so we don't slide) rootPart.AssemblyLinearVelocity = Vector3.zero rootPart.AssemblyAngularVelocity = Vector3.zero -- 2. Teleport local lookTarget = (type == "Projectile") and (rootPart.Position + dodgeDir) or (threat.Position) rootPart.CFrame = CFrame.new(finalPos, Vector3.new(lookTarget.X, finalPos.Y, lookTarget.Z)) lastDodgeTime = now playEffects(mode, finalPos) end task.wait(0.05) isDodging = false end end)