--[[ NPC INTERACT FARMER — Velocity Executor Fluxo: rota até a FRENTE do NPC -> 5x E -> cooldown individual de 30s naquele NPC - Monitor de velocidade: < 20 studs/s por 2s entra em recuperação - Recuperação: SPACE a cada 0.1s em ciclos de 2s + recálculo de rota em loop - Recuperação ignora validação de waypoint e timeout - Waypoint sempre termina em MoveTo / cooldown por NPC / loop infinito --]] --==================== CONFIG ====================-- local CONFIG = { CONTAINER = "PopulacaoNPC", MAX_INDEX = 45, USE_PHYSICS = true, INFINITE = true, SPEED = 55, FRONT_DISTANCE = 5, FRONT_DIST_MIN = 1.8, ARRIVE_EPS = 0.2, PHYS_EPS = 2.5, WAYPOINT_EPS = 2.5, APPROACH_TIMEOUT = 25, -- monitor de velocidade / recuperação MIN_SPEED = 20, LOW_SPEED_TIME = 2, SPEED_SAMPLE = 0.2, SPACE_INTERVAL = 0.1, -- intervalo do spam de SPACE SPACE_HOLD = 0.03, -- tempo com a tecla pressionada RECOVERY_CYCLE = 2, -- duração de cada janela de spam RECOVERY_PAUSE = 0.3, -- pausa entre janelas RECOVERY_REPATH = 1.0, -- intervalo de recálculo durante recuperação -- validação de waypoint MIN_NORMAL_Y = 0.70, MAX_GROUND_DELTA = 6, PROBE_UP = 3, PROBE_DOWN = 12, PROBE_SIDE = 0.7, WAYPOINT_TIMEOUT = 1.5, STUCK_WINDOW = 0.4, STUCK_MIN_MOVE = 0.6, STUCK_RETRIES = 3, REPATH_DIST = 4, REPATH_COOLDOWN = 0.4, SETTLE_DELAY = 0.05, PRESS_COUNT = 5, PRESS_HOLD = 0.02, PRESS_INTERVAL = 0.05, NPC_COOLDOWN = 0.05, COOLDOWN = 30, COOLDOWN_PAD = 0.5, FAIL_COOLDOWN = 3, MAX_WAIT = 5, OFFSET_Y = 2.5, FRONT_MODE = "npc", } local AGENT_PARAMS = { AgentRadius = 2.5, AgentHeight = 5, AgentCanJump = true, AgentJumpHeight = 7, AgentMaxSlope = 45, WaypointSpacing = 6, } --==================== SERVICES ====================-- local Players = game:GetService("Players") local Workspace = game:GetService("Workspace") local VIM = game:GetService("VirtualInputManager") local UIS = game:GetService("UserInputService") local RunService = game:GetService("RunService") local TweenService = game:GetService("TweenService") local Pathfinding = game:GetService("PathfindingService") local CoreGui = game:GetService("CoreGui") local LP = Players.LocalPlayer local running = false local dragging, dragStart, startPos = false, nil, nil local cooldowns = {} local activeContainer = nil --==================== GUI ====================-- local function getParentGui() local ok, hui = pcall(function() return gethui and gethui() or nil end) if ok and hui then return hui end local ok2, cg = pcall(function() return CoreGui end) if ok2 and cg then return cg end return LP:WaitForChild("PlayerGui") end local gui = Instance.new("ScreenGui") gui.Name = "NPCInteractGui" gui.ResetOnSpawn = false gui.IgnoreGuiInset = true gui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling gui.DisplayOrder = 999 gui.Parent = getParentGui() pcall(function() syn.protect_gui(gui) end) local main = Instance.new("Frame") main.Size = UDim2.new(0, 240, 0, 250) main.Position = UDim2.new(0, 40, 0, 40) main.BackgroundColor3 = Color3.fromRGB(22, 22, 26) main.BorderSizePixel = 0 main.Active = true main.Parent = gui local corner = Instance.new("UICorner") corner.CornerRadius = UDim.new(0, 8) corner.Parent = main local stroke = Instance.new("UIStroke") stroke.Color = Color3.fromRGB(60, 60, 70) stroke.Thickness = 1 stroke.Parent = main local header = Instance.new("Frame") header.Size = UDim2.new(1, 0, 0, 30) header.BackgroundColor3 = Color3.fromRGB(32, 32, 38) header.BorderSizePixel = 0 header.Parent = main local headerCorner = Instance.new("UICorner") headerCorner.CornerRadius = UDim.new(0, 8) headerCorner.Parent = header local title = Instance.new("TextLabel") title.Size = UDim2.new(1, -12, 1, 0) title.Position = UDim2.new(0, 10, 0, 0) title.BackgroundTransparency = 1 title.Font = Enum.Font.GothamBold title.TextSize = 13 title.TextColor3 = Color3.fromRGB(235, 235, 240) title.TextXAlignment = Enum.TextXAlignment.Left title.Text = "NPC Farmer • arraste aqui" title.Parent = header local status = Instance.new("TextLabel") status.Size = UDim2.new(1, -20, 0, 20) status.Position = UDim2.new(0, 10, 0, 36) status.BackgroundTransparency = 1 status.Font = Enum.Font.Gotham status.TextSize = 12 status.TextColor3 = Color3.fromRGB(160, 160, 170) status.TextXAlignment = Enum.TextXAlignment.Left status.Text = "Parado" status.Parent = main local progress = Instance.new("TextLabel") progress.Size = UDim2.new(1, -20, 0, 20) progress.Position = UDim2.new(0, 10, 0, 56) progress.BackgroundTransparency = 1 progress.Font = Enum.Font.GothamMedium progress.TextSize = 12 progress.TextColor3 = Color3.fromRGB(120, 200, 255) progress.TextXAlignment = Enum.TextXAlignment.Left progress.Text = "0 feitos • 0 disponíveis" progress.Parent = main local speedLabel = Instance.new("TextLabel") speedLabel.Size = UDim2.new(0, 70, 0, 24) speedLabel.Position = UDim2.new(0, 10, 0, 82) speedLabel.BackgroundTransparency = 1 speedLabel.Font = Enum.Font.Gotham speedLabel.TextSize = 12 speedLabel.TextColor3 = Color3.fromRGB(170, 170, 180) speedLabel.TextXAlignment = Enum.TextXAlignment.Left speedLabel.Text = "Velocidade" speedLabel.Parent = main local speedBox = Instance.new("TextBox") speedBox.Size = UDim2.new(0, 60, 0, 24) speedBox.Position = UDim2.new(0, 84, 0, 82) speedBox.BackgroundColor3 = Color3.fromRGB(38, 38, 45) speedBox.BorderSizePixel = 0 speedBox.Font = Enum.Font.GothamMedium speedBox.TextSize = 12 speedBox.TextColor3 = Color3.fromRGB(230, 230, 235) speedBox.Text = tostring(CONFIG.SPEED) speedBox.ClearTextOnFocus = false speedBox.Parent = main local sbCorner = Instance.new("UICorner") sbCorner.CornerRadius = UDim.new(0, 5) sbCorner.Parent = speedBox local speedHint = Instance.new("TextLabel") speedHint.Size = UDim2.new(0, 80, 0, 24) speedHint.Position = UDim2.new(0, 150, 0, 82) speedHint.BackgroundTransparency = 1 speedHint.Font = Enum.Font.Gotham speedHint.TextSize = 11 speedHint.TextColor3 = Color3.fromRGB(110, 110, 120) speedHint.TextXAlignment = Enum.TextXAlignment.Left speedHint.Text = "studs/s" speedHint.Parent = main local function applySpeed() local n = tonumber(speedBox.Text) if n then CONFIG.SPEED = math.clamp(n, 16, 500) speedBox.Text = tostring(CONFIG.SPEED) else speedBox.Text = tostring(CONFIG.SPEED) end end speedBox.FocusLost:Connect(applySpeed) local frontBtn = Instance.new("TextButton") frontBtn.Size = UDim2.new(1, -20, 0, 24) frontBtn.Position = UDim2.new(0, 10, 0, 112) frontBtn.BackgroundColor3 = Color3.fromRGB(38, 38, 45) frontBtn.BorderSizePixel = 0 frontBtn.AutoButtonColor = false frontBtn.Font = Enum.Font.GothamMedium frontBtn.TextSize = 11 frontBtn.TextColor3 = Color3.fromRGB(120, 200, 255) frontBtn.Text = "Frente: vetor do NPC" frontBtn.Parent = main local fbCorner = Instance.new("UICorner") fbCorner.CornerRadius = UDim.new(0, 5) fbCorner.Parent = frontBtn local function refreshFrontBtn() if CONFIG.FRONT_MODE == "npc" then frontBtn.Text = "Frente: vetor do NPC" frontBtn.TextColor3 = Color3.fromRGB(120, 200, 255) else frontBtn.Text = "Frente: lado de onde venho" frontBtn.TextColor3 = Color3.fromRGB(200, 200, 210) end end frontBtn.MouseButton1Click:Connect(function() CONFIG.FRONT_MODE = (CONFIG.FRONT_MODE == "npc") and "approach" or "npc" refreshFrontBtn() end) local moveBtn = Instance.new("TextButton") moveBtn.Size = UDim2.new(1, -20, 0, 24) moveBtn.Position = UDim2.new(0, 10, 0, 142) moveBtn.BackgroundColor3 = Color3.fromRGB(38, 38, 45) moveBtn.BorderSizePixel = 0 moveBtn.AutoButtonColor = false moveBtn.Font = Enum.Font.GothamMedium moveBtn.TextSize = 11 moveBtn.TextColor3 = Color3.fromRGB(140, 230, 160) moveBtn.Text = "Movimento: física + rota" moveBtn.Parent = main local mbCorner = Instance.new("UICorner") mbCorner.CornerRadius = UDim.new(0, 5) mbCorner.Parent = moveBtn local function refreshMoveBtn() if CONFIG.USE_PHYSICS then moveBtn.Text = "Movimento: física + rota" moveBtn.TextColor3 = Color3.fromRGB(140, 230, 160) else moveBtn.Text = "Movimento: CFrame (reto)" moveBtn.TextColor3 = Color3.fromRGB(230, 200, 120) end end moveBtn.MouseButton1Click:Connect(function() CONFIG.USE_PHYSICS = not CONFIG.USE_PHYSICS refreshMoveBtn() end) local loopBtn = Instance.new("TextButton") loopBtn.Size = UDim2.new(1, -20, 0, 24) loopBtn.Position = UDim2.new(0, 10, 0, 172) loopBtn.BackgroundColor3 = Color3.fromRGB(38, 38, 45) loopBtn.BorderSizePixel = 0 loopBtn.AutoButtonColor = false loopBtn.Font = Enum.Font.GothamMedium loopBtn.TextSize = 11 loopBtn.TextColor3 = Color3.fromRGB(140, 230, 160) loopBtn.Text = "Loop: infinito" loopBtn.Parent = main local lbCorner = Instance.new("UICorner") lbCorner.CornerRadius = UDim.new(0, 5) lbCorner.Parent = loopBtn local function refreshLoopBtn() if CONFIG.INFINITE then loopBtn.Text = "Loop: infinito" loopBtn.TextColor3 = Color3.fromRGB(140, 230, 160) else loopBtn.Text = "Loop: ciclo único" loopBtn.TextColor3 = Color3.fromRGB(200, 200, 210) end end loopBtn.MouseButton1Click:Connect(function() CONFIG.INFINITE = not CONFIG.INFINITE refreshLoopBtn() end) local button = Instance.new("TextButton") button.Size = UDim2.new(1, -20, 0, 38) button.Position = UDim2.new(0, 10, 1, -48) button.BackgroundColor3 = Color3.fromRGB(45, 140, 75) button.BorderSizePixel = 0 button.AutoButtonColor = false button.Font = Enum.Font.GothamBold button.TextSize = 14 button.TextColor3 = Color3.fromRGB(255, 255, 255) button.Text = "ATIVAR" button.Parent = main local btnCorner = Instance.new("UICorner") btnCorner.CornerRadius = UDim.new(0, 6) btnCorner.Parent = button header.InputBegan:Connect(function(input) if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then dragging = true dragStart = input.Position startPos = main.Position end end) UIS.InputChanged:Connect(function(input) if not dragging then return end if input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch then local delta = input.Position - dragStart main.Position = UDim2.new( startPos.X.Scale, startPos.X.Offset + delta.X, startPos.Y.Scale, startPos.Y.Offset + delta.Y ) end end) UIS.InputEnded:Connect(function(input) if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then dragging = false end end) --==================== HELPERS ====================-- local function setStatus(txt, color) status.Text = txt if color then status.TextColor3 = color end end local function setButton(active) if active then button.Text = "DESATIVAR" button.BackgroundColor3 = Color3.fromRGB(170, 50, 50) else button.Text = "ATIVAR" button.BackgroundColor3 = Color3.fromRGB(45, 140, 75) end end local function getContainer() local direct = Workspace:FindFirstChild(CONFIG.CONTAINER) if direct then return direct end for _, d in ipairs(Workspace:GetDescendants()) do if d.Name == CONFIG.CONTAINER then return d end end return nil end local function getNpcPart(npc) if npc:IsA("BasePart") then return npc end local hrp = npc:FindFirstChild("HumanoidRootPart") if hrp and hrp:IsA("BasePart") then return hrp end if npc.PrimaryPart then return npc.PrimaryPart end for _, d in ipairs(npc:GetDescendants()) do if d:IsA("BasePart") then return d end end return nil end local function isAlive(npc) local hum = npc:FindFirstChildOfClass("Humanoid") if hum and hum.Health <= 0 then return false end return true end local function getHRP() local char = LP.Character return char and char:FindFirstChild("HumanoidRootPart") or nil end local function getHumanoid() local char = LP.Character return char and char:FindFirstChildOfClass("Humanoid") or nil end --==================== COOLDOWN POR NPC ====================-- local function onCooldown(npc) local until_ = cooldowns[npc] if not until_ then return false end if not npc.Parent then cooldowns[npc] = nil return false end if os.clock() >= until_ then cooldowns[npc] = nil return false end return true end local function setCooldown(npc, seconds) cooldowns[npc] = os.clock() + seconds end local function minCooldownWait() local soonest = math.huge for npc, until_ in pairs(cooldowns) do if npc.Parent then local left = until_ - os.clock() if left < soonest then soonest = left end else cooldowns[npc] = nil end end if soonest == math.huge then return 0 end return math.max(soonest, 0) end local function countCooldown() local n = 0 for npc in pairs(cooldowns) do if npc.Parent and onCooldown(npc) then n += 1 end end return n end --==================== ALVOS ====================-- local function isEligible(npc) return isAlive(npc) and getNpcPart(npc) ~= nil and not onCooldown(npc) end local function countRemaining(container) local n = 0 for _, obj in ipairs(container:GetChildren()) do if isEligible(obj) then n += 1 end end return n end local function findNearest(container) local hrp = getHRP() if not hrp then return nil end local myPos = hrp.Position local best, bestDist = nil, math.huge for _, obj in ipairs(container:GetChildren()) do if isEligible(obj) then local part = getNpcPart(obj) if part then local d = (part.Position - myPos).Magnitude if d < bestDist then best, bestDist = obj, d end end end end return best, bestDist end local function boostPrompts(npc) for _, d in ipairs(npc:GetDescendants()) do if d:IsA("ProximityPrompt") then pcall(function() d.MaxActivationDistance = math.max(d.MaxActivationDistance, 80) d.HoldDuration = 0 end) end end end local function newRayParams(ignore) local params = RaycastParams.new() local okFilter = pcall(function() params.FilterType = Enum.RaycastFilterType.Exclude end) if not okFilter then pcall(function() params.FilterType = Enum.RaycastFilterType.Blacklist end) end params.FilterDescendantsInstances = ignore params.IgnoreWater = true pcall(function() params.RespectCanCollide = true end) return params end local function raycastClear(fromPos, toPos, ignore) local res = Workspace:Raycast(fromPos, toPos - fromPos, newRayParams(ignore)) if res then return res.Distance end return nil end local function buildFrontCF(part, npc) local npcPos = part.Position local dir if CONFIG.FRONT_MODE == "npc" then dir = part.CFrame.LookVector else local hrp = getHRP() dir = hrp and (npcPos - hrp.Position) or part.CFrame.LookVector end dir = Vector3.new(dir.X, 0, dir.Z) if dir.Magnitude < 0.05 then dir = Vector3.new(part.CFrame.LookVector.X, 0, part.CFrame.LookVector.Z) end if dir.Magnitude < 0.05 then dir = Vector3.new(0, 0, 1) end dir = dir.Unit local sign = (CONFIG.FRONT_MODE == "npc") and 1 or -1 local baseY = npcPos.Y + CONFIG.OFFSET_Y local dist = CONFIG.FRONT_DISTANCE local ignore = { npc } if LP.Character then ignore[#ignore + 1] = LP.Character end local fromPos = npcPos + Vector3.new(0, 0.5, 0) local hitDist = raycastClear(fromPos, fromPos + dir * sign * (dist + 1), ignore) if hitDist then dist = math.clamp(hitDist - 1.0, CONFIG.FRONT_DIST_MIN, CONFIG.FRONT_DISTANCE) end local standPos = Vector3.new( npcPos.X + dir.X * dist * sign, baseY, npcPos.Z + dir.Z * dist * sign ) local facePos = Vector3.new(npcPos.X, baseY, npcPos.Z) return CFrame.lookAt(standPos, facePos) end local function flatDist(a, b) local dx = a.X - b.X local dz = a.Z - b.Z return math.sqrt(dx * dx + dz * dz) end --==================== TECLA SPACE ====================-- local function tapSpace() task.spawn(function() pcall(function() VIM:SendKeyEvent(true, "Space", false, game) task.wait(CONFIG.SPACE_HOLD) VIM:SendKeyEvent(false, "Space", false, game) end) end) end local function releaseSpace() pcall(function() VIM:SendKeyEvent(false, "Space", false, game) end) end --==================== VALIDAÇÃO DE WAYPOINT ====================-- local function probeGround(pos, ignore) local from = pos + Vector3.new(0, CONFIG.PROBE_UP, 0) local to = pos - Vector3.new(0, CONFIG.PROBE_DOWN, 0) return Workspace:Raycast(from, to - from, newRayParams(ignore)) end local function hitWalkable(hit, refY) if not hit then return false end if hit.Normal.Y < CONFIG.MIN_NORMAL_Y then return false end if math.abs(hit.Position.Y - refY) > CONFIG.MAX_GROUND_DELTA then return false end return true end local function waypointWalkable(wpPos, wpY, fromPos, ignore) local dir = Vector3.new(wpPos.X - fromPos.X, 0, wpPos.Z - fromPos.Z) if dir.Magnitude < 0.1 then dir = Vector3.new(0, 0, 1) end dir = dir.Unit local side = Vector3.new(-dir.Z, 0, dir.X) * CONFIG.PROBE_SIDE if not hitWalkable(probeGround(wpPos, ignore), wpY) then return false end local left = hitWalkable(probeGround(wpPos + side, ignore), wpY) local right = hitWalkable(probeGround(wpPos - side, ignore), wpY) return left or right end --==================== CAMINHADA COM ROTA ====================-- local function walkDynamic(getTarget, eps, timeout, extraIgnore, label) local hrp = getHRP() local hum = getHumanoid() if not hrp or not hum then return false end local ignore = {} if LP.Character then ignore[#ignore + 1] = LP.Character end if activeContainer then ignore[#ignore + 1] = activeContainer end if extraIgnore then for _, v in ipairs(extraIgnore) do ignore[#ignore + 1] = v end end local oldSpeed = hum.WalkSpeed hum.WalkSpeed = CONFIG.SPEED local t0 = os.clock() local arrived = false local lastCompute = 0 local lastTarget = nil local waypoints, wpIndex = nil, 1 local forceRepath = false local blockConn = nil local computing = false local validIdx, validOk = nil, true local wpStartT = os.clock() local progPos = hrp.Position local progT = os.clock() local stuckHits = 0 -- monitor de velocidade / recuperação local recovering = false local lowT = 0 local samplePos = hrp.Position local sampleT = os.clock() local lastSpace = 0 local cycleStart = 0 local function clearPath() if blockConn then pcall(function() blockConn:Disconnect() end) blockConn = nil end waypoints = nil wpIndex = 1 validIdx = nil end local function advanceWaypoint() wpIndex += 1 validIdx = nil wpStartT = os.clock() progPos = hrp.Position progT = os.clock() stuckHits = 0 end local function requestPath(fromPos, toPos) if computing then return end computing = true task.spawn(function() local path = Pathfinding:CreatePath(AGENT_PARAMS) local ok = pcall(function() path:ComputeAsync(fromPos, toPos) end) if ok and running and path.Status == Enum.PathStatus.Success then local wps = path:GetWaypoints() if #wps > 1 then table.remove(wps, 1) end if #wps > 0 then if blockConn then pcall(function() blockConn:Disconnect() end) end blockConn = path.Blocked:Connect(function() forceRepath = true end) waypoints = wps wpIndex = 1 validIdx = nil wpStartT = os.clock() progPos = hrp.Position progT = os.clock() stuckHits = 0 end end computing = false end) end while running do if os.clock() - t0 > timeout then break end local raw = getTarget() if not raw then break end local targetPos = Vector3.new(raw.X, hrp.Position.Y, raw.Z) if flatDist(targetPos, hrp.Position) <= eps then arrived = true break end -- amostragem de velocidade real local now = os.clock() if now - sampleT >= CONFIG.SPEED_SAMPLE then local v = flatDist(hrp.Position, samplePos) / math.max(now - sampleT, 0.001) samplePos = hrp.Position sampleT = now if v < CONFIG.MIN_SPEED then lowT += CONFIG.SPEED_SAMPLE if lowT >= CONFIG.LOW_SPEED_TIME and not recovering then recovering = true cycleStart = os.clock() lastSpace = 0 if label then setStatus("Recuperando: " .. label, Color3.fromRGB(230, 150, 90)) end forceRepath = true lastCompute = 0 end else lowT = 0 end end -- recuperação: SPACE a cada 0.1s em janelas de 2s if recovering then local t = os.clock() if (t - cycleStart) >= (CONFIG.RECOVERY_CYCLE + CONFIG.RECOVERY_PAUSE) then cycleStart = t end if (t - cycleStart) < CONFIG.RECOVERY_CYCLE and (t - lastSpace) >= CONFIG.SPACE_INTERVAL then lastSpace = t tapSpace() end end local needRepath = forceRepath or recovering or (not waypoints) or (lastTarget and flatDist(targetPos, lastTarget) > CONFIG.REPATH_DIST) local repathGap = recovering and CONFIG.RECOVERY_REPATH or CONFIG.REPATH_COOLDOWN if needRepath and not computing and (os.clock() - lastCompute) >= repathGap then forceRepath = false lastCompute = os.clock() lastTarget = targetPos requestPath(hrp.Position, targetPos) end -- seleção: sempre termina em um MoveTo, nunca fica sem alvo local moved = false local guard = 0 local wp = waypoints and waypoints[wpIndex] while wp and guard < 8 do guard += 1 local wpPos = Vector3.new(wp.Position.X, hrp.Position.Y, wp.Position.Z) if flatDist(wpPos, hrp.Position) <= CONFIG.WAYPOINT_EPS then if wp.Action == Enum.PathWaypointAction.Jump then hum.Jump = true end advanceWaypoint() wp = waypoints and waypoints[wpIndex] else if recovering then validOk = true elseif validIdx ~= wpIndex then validOk = waypointWalkable(wpPos, wp.Position.Y, hrp.Position, ignore) validIdx = wpIndex end if validOk then hum:MoveTo(wpPos) moved = true break else advanceWaypoint() wp = waypoints and waypoints[wpIndex] end end end if not moved then hum:MoveTo(targetPos) if waypoints and wpIndex > #waypoints and not computing then forceRepath = true lastCompute = 0 end end local dt = RunService.Heartbeat:Wait() -- timeout por waypoint (desligado durante recuperação) if not recovering and wp and validIdx == wpIndex then local limit = math.max( CONFIG.WAYPOINT_TIMEOUT, (flatDist(wp.Position, hrp.Position) / math.max(CONFIG.SPEED, 1)) * 2.5 ) if os.clock() - wpStartT > limit then advanceWaypoint() if not computing then forceRepath = true lastCompute = 0 end end end -- travamento por janela if os.clock() - progT >= CONFIG.STUCK_WINDOW then local movedDist = flatDist(hrp.Position, progPos) progPos = hrp.Position progT = os.clock() if movedDist < CONFIG.STUCK_MIN_MOVE then stuckHits += 1 hum.Jump = true if stuckHits >= CONFIG.STUCK_RETRIES then stuckHits = 0 advanceWaypoint() if not computing then forceRepath = true lastCompute = 0 end end else stuckHits = 0 end end end releaseSpace() hum:MoveTo(hrp.Position) hum.WalkSpeed = oldSpeed clearPath() return arrived end local function followFrontPhysics(npc, part) return walkDynamic(function() if not npc.Parent then return nil end if not part or not part.Parent then part = getNpcPart(npc) if not part then return nil end end return buildFrontCF(part, npc).Position end, CONFIG.PHYS_EPS, CONFIG.APPROACH_TIMEOUT, { npc }, npc.Name) end --==================== MOVIMENTO CFrame ====================-- local function followFrontCFrame(npc, part, speed) local hrp = getHRP() if not hrp then return false end if not part then return false end local wasAnchored = hrp.Anchored hrp.Anchored = true local cv = Instance.new("CFrameValue") cv.Value = hrp.CFrame local conn = cv:GetPropertyChangedSignal("Value"):Connect(function() local h = getHRP() if h then h.CFrame = cv.Value end end) local tween local function retarget(targetCF, dur) if tween then pcall(function() tween:Cancel() end) end tween = TweenService:Create(cv, TweenInfo.new(dur, Enum.EasingStyle.Linear), { Value = targetCF }) tween:Play() end local t0 = os.clock() local arrived = false local dt = 1 / 60 while running do if os.clock() - t0 > CONFIG.APPROACH_TIMEOUT then break end if not npc.Parent then break end if not part.Parent then part = getNpcPart(npc) if not part then break end end local target = buildFrontCF(part, npc) local dist = (target.Position - hrp.Position).Magnitude if dist <= CONFIG.ARRIVE_EPS then retarget(target, math.max(dist / math.max(speed, 1), 0.03)) local tw = tween while running and tw.PlaybackState == Enum.PlaybackState.Playing do RunService.Heartbeat:Wait() end arrived = true break end retarget(target, math.max(dist / math.max(speed, 1), dt * 1.5)) dt = RunService.Heartbeat:Wait() end if tween then pcall(function() tween:Cancel() end) end conn:Disconnect() cv:Destroy() hrp.Anchored = wasAnchored return arrived end local function followFront(npc, part, speed) if CONFIG.USE_PHYSICS then return followFrontPhysics(npc, part) end return followFrontCFrame(npc, part, speed) end --==================== INTERAÇÃO ====================-- local function pressE() for i = 1, CONFIG.PRESS_COUNT do if not running then break end pcall(function() VIM:SendKeyEvent(true, "E", false, game) task.wait(CONFIG.PRESS_HOLD) VIM:SendKeyEvent(false, "E", false, game) end) if i < CONFIG.PRESS_COUNT then task.wait(CONFIG.PRESS_INTERVAL) end end end --==================== LOOP ====================-- local function stop() running = false releaseSpace() local hrp = getHRP() local hum = getHumanoid() if hrp and hum then pcall(function() hum:MoveTo(hrp.Position) end) end setStatus("Parado", Color3.fromRGB(160, 160, 170)) setButton(false) end local function start() if running then return end applySpeed() local hrp = getHRP() if not hrp then setStatus("Personagem não encontrado", Color3.fromRGB(230, 90, 90)) return end cooldowns = {} running = true setButton(true) setStatus("Procurando NPCs...", Color3.fromRGB(230, 200, 90)) task.spawn(function() local container = getContainer() if not container then setStatus("PopulacaoNPC não encontrado", Color3.fromRGB(230, 90, 90)) stop() return end activeContainer = container local remaining = countRemaining(container) if remaining == 0 then setStatus("Nenhum NPC válido encontrado", Color3.fromRGB(230, 90, 90)) stop() return end setStatus("Rodando...", Color3.fromRGB(120, 230, 140)) local done = 0 while running do if not container or not container.Parent then container = getContainer() if not container then setStatus("PopulacaoNPC não encontrado", Color3.fromRGB(230, 90, 90)) break end activeContainer = container end local npc = findNearest(container) if not npc then if CONFIG.INFINITE and running then local waitSec = math.min(minCooldownWait() + 0.15, CONFIG.MAX_WAIT) setStatus( string.format("Aguardando cooldown (%.1fs)...", waitSec), Color3.fromRGB(200, 200, 120) ) local wt = os.clock() while running and (os.clock() - wt) < waitSec do task.wait(0.1) end continue end break end boostPrompts(npc) setStatus("Indo: " .. npc.Name, Color3.fromRGB(120, 200, 255)) local part = getNpcPart(npc) local ok = false if part then ok = followFront(npc, part, CONFIG.SPEED) end if ok and running then setStatus("Interagindo: " .. npc.Name, Color3.fromRGB(160, 220, 160)) task.wait(CONFIG.SETTLE_DELAY) pressE() setCooldown(npc, CONFIG.COOLDOWN + CONFIG.COOLDOWN_PAD) done += 1 remaining = countRemaining(container) progress.Text = string.format( "%d feitos • %d disponíveis • %d em cooldown", done, remaining, countCooldown() ) elseif running then setStatus("Falhou: " .. npc.Name, Color3.fromRGB(230, 180, 90)) setCooldown(npc, CONFIG.FAIL_COOLDOWN) end task.wait(CONFIG.NPC_COOLDOWN) end setStatus("Concluído", Color3.fromRGB(120, 230, 160)) setButton(false) running = false end) end button.MouseButton1Click:Connect(function() if running then stop() else start() end end) refreshFrontBtn() refreshMoveBtn() refreshLoopBtn() setStatus("Parado", Color3.fromRGB(160, 160, 170))