local Players = game:GetService("Players") local RunService = game:GetService("RunService") local Workspace = game:GetService("Workspace") local UserInputService = game:GetService("UserInputService") local player = Players.LocalPlayer local character = player.Character or player.CharacterAdded:Wait() local humanoid = character:WaitForChild("Humanoid") local rootPart = character:WaitForChild("HumanoidRootPart") local moveDirection = Vector3.new(math.random() * 2 - 1, 0, math.random() * 2 - 1).Unit local isStanding = false local standStart = 0 local standDuration = 0 local walkDuration = 0 local walkUntil = 0 local checkCooldown = 0 local centerPosition = nil local wanderRadius = 0 local lastPositions = {} local stuckDetected = false local stuckCounter = 0 local maxStuckTime = 3 local obstacleMemory = {} local lastObstacleTime = 0 local obstacleMemoryDuration = 5 local emotionalState = "neutral" local emotionalStateTimer = math.random(50, 150) / 10 local emotionalStateDuration = math.random(50, 150) / 10 local curiosityLevel = 0.5 local socialAwareness = 0.5 local personalityTraits = {} local frameCount = 0 local lastFrameUpdate = 0 local utilityScores = {} local nearbyPlayers = {} local playerDetectionRange = 15 local personalSpace = 4 local imitationChance = 0.3 local curiosityTarget = nil local curiosityTimer = 0 local boredomLevel = 0.3 local npcEnabled = true personalityTraits = { patience = 0.7 + math.random() * 0.25, sociability = 0.2 + math.random() * 0.5, curiosity = 0.3 + math.random() * 0.5, caution = 0.4 + math.random() * 0.5, energy = 0.5 + math.random() * 0.4, consistency = 0.6 + math.random() * 0.35, playfulness = 0.3 + math.random() * 0.5, anxiety = 0.1 + math.random() * 0.4, focus = 0.5 + math.random() * 0.4, adaptability = 0.4 + math.random() * 0.5 } local ScreenGui = Instance.new("ScreenGui") ScreenGui.Name = "NPCControl" ScreenGui.ResetOnSpawn = false ScreenGui.IgnoreGuiInset = true pcall(function() if player.PlayerGui:FindFirstChild("NPCControl") then player.PlayerGui:FindFirstChild("NPCControl"):Destroy() end end) ScreenGui.Parent = player.PlayerGui local function createBorder(parent, zIndex) local left = Instance.new("Frame") left.Size = UDim2.new(0, 1, 1, 0) left.BackgroundColor3 = Color3.fromRGB(50, 50, 50) left.BorderSizePixel = 0 left.ZIndex = zIndex left.Active = false left.Parent = parent local top = Instance.new("Frame") top.Size = UDim2.new(1, 0, 0, 1) top.BackgroundColor3 = Color3.fromRGB(50, 50, 50) top.BorderSizePixel = 0 top.ZIndex = zIndex top.Active = false top.Parent = parent local right = Instance.new("Frame") right.Size = UDim2.new(0, 1, 1, 0) right.Position = UDim2.new(1, -1, 0, 0) right.BackgroundColor3 = Color3.fromRGB(50, 50, 50) right.BorderSizePixel = 0 right.ZIndex = zIndex right.Active = false right.Parent = parent local bottom = Instance.new("Frame") bottom.Size = UDim2.new(1, 0, 0, 1) bottom.Position = UDim2.new(0, 0, 1, -1) bottom.BackgroundColor3 = Color3.fromRGB(50, 50, 50) bottom.BorderSizePixel = 0 bottom.ZIndex = zIndex bottom.Active = false bottom.Parent = parent end local mainFrame = Instance.new("Frame") mainFrame.Size = UDim2.new(0, 240, 0, 50) mainFrame.Position = UDim2.new(0.5, -120, 0, 50) mainFrame.BackgroundColor3 = Color3.fromRGB(12, 12, 12) mainFrame.BorderSizePixel = 0 mainFrame.ZIndex = 100 mainFrame.ClipsDescendants = true mainFrame.Parent = ScreenGui createBorder(mainFrame, 200) local titleBar = Instance.new("Frame") titleBar.Size = UDim2.new(1, -2, 0, 22) titleBar.Position = UDim2.new(0, 1, 0, 1) titleBar.BackgroundColor3 = Color3.fromRGB(12, 12, 12) titleBar.BorderSizePixel = 0 titleBar.ZIndex = 101 titleBar.Parent = mainFrame local titleLabel = Instance.new("TextLabel") titleLabel.Size = UDim2.new(1, -30, 1, 0) titleLabel.Position = UDim2.new(0, 8, 0, 0) titleLabel.BackgroundTransparency = 1 titleLabel.Text = "NPC | Control" titleLabel.TextColor3 = Color3.fromRGB(180, 180, 180) titleLabel.Font = Enum.Font.Code titleLabel.TextSize = 12 titleLabel.TextXAlignment = Enum.TextXAlignment.Left titleLabel.ZIndex = 102 titleLabel.Parent = titleBar local collapseButton = Instance.new("TextButton") collapseButton.Size = UDim2.new(0, 22, 0, 22) collapseButton.Position = UDim2.new(1, -22, 0, 0) collapseButton.BackgroundColor3 = Color3.fromRGB(25, 25, 25) collapseButton.BorderSizePixel = 0 collapseButton.Text = "-" collapseButton.TextColor3 = Color3.fromRGB(200, 200, 200) collapseButton.Font = Enum.Font.Code collapseButton.TextSize = 14 collapseButton.ZIndex = 102 collapseButton.Parent = titleBar createBorder(collapseButton, 103) local titleDivider = Instance.new("Frame") titleDivider.Size = UDim2.new(1, -2, 0, 1) titleDivider.Position = UDim2.new(0, 1, 0, 23) titleDivider.BackgroundColor3 = Color3.fromRGB(50, 50, 50) titleDivider.BorderSizePixel = 0 titleDivider.ZIndex = 200 titleDivider.Parent = mainFrame local contentFrame = Instance.new("Frame") contentFrame.Size = UDim2.new(1, -2, 1, -25) contentFrame.Position = UDim2.new(0, 1, 0, 24) contentFrame.BackgroundTransparency = 1 contentFrame.ZIndex = 101 contentFrame.Parent = mainFrame local isDragging = false local dragStart = nil local frameStartPos = nil local function startDrag(input) isDragging = true dragStart = input.Position frameStartPos = mainFrame.Position end local function stopDrag() isDragging = false end titleBar.InputBegan:Connect(function(input) if input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.MouseButton1 then startDrag(input) end end) titleBar.InputEnded:Connect(function(input) if input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.MouseButton1 then stopDrag() end end) titleLabel.InputBegan:Connect(function(input) if input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.MouseButton1 then startDrag(input) end end) titleLabel.InputEnded:Connect(function(input) if input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.MouseButton1 then stopDrag() end end) UserInputService.InputChanged:Connect(function(input) if isDragging and (input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.MouseMovement) then local delta = input.Position - dragStart mainFrame.Position = UDim2.new( frameStartPos.X.Scale, frameStartPos.X.Offset + delta.X, frameStartPos.Y.Scale, frameStartPos.Y.Offset + delta.Y ) end end) UserInputService.InputEnded:Connect(function(input) if input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.MouseButton1 then stopDrag() end end) collapseButton.MouseButton1Click:Connect(function() if mainFrame.Size.Y.Offset > 24 then mainFrame:TweenSize(UDim2.new(0, 240, 0, 24), "Out", "Quad", 0.2, true) collapseButton.Text = "+" contentFrame.Visible = false else mainFrame:TweenSize(UDim2.new(0, 240, 0, 50), "Out", "Quad", 0.2, true) collapseButton.Text = "-" contentFrame.Visible = true end end) local function createToggle(toggleText, yPosition, default, callback) local button = Instance.new("TextButton") button.Size = UDim2.new(0, 210, 0, 22) button.Position = UDim2.new(0.5, -105, 0, yPosition) button.BackgroundColor3 = default and Color3.fromRGB(25, 45, 25) or Color3.fromRGB(30, 30, 30) button.Text = toggleText .. ": " .. (default and "ON" or "OFF") button.TextColor3 = Color3.fromRGB(200, 200, 200) button.Font = Enum.Font.Code button.TextSize = 11 button.BorderSizePixel = 0 button.ZIndex = 150 button.Active = true button.Selectable = true button.Parent = contentFrame createBorder(button, 149) local state = default local debounce = false local function toggle() if debounce then return end debounce = true state = not state button.Text = toggleText .. ": " .. (state and "ON" or "OFF") button.BackgroundColor3 = state and Color3.fromRGB(25, 45, 25) or Color3.fromRGB(30, 30, 30) callback(state) task.delay(0.3, function() debounce = false end) end button.Activated:Connect(toggle) return button end createToggle("NPC MODE", 2, true, function(state) npcEnabled = state if not npcEnabled and humanoid then humanoid:MoveTo(rootPart.Position) end end) local function isValidCharacter() if not character or not character.Parent then return false end if not humanoid or not humanoid.Parent then return false end if not rootPart or not rootPart.Parent then return false end if humanoid.Health <= 0 then return false end return true end local function findMapCenter() local parts = {} local children = Workspace:GetChildren() for _, child in ipairs(children) do if child:IsA("BasePart") and child.CanCollide and child.Transparency < 0.5 and child.Size.Magnitude > 3 then table.insert(parts, child) end if child:IsA("Model") then for _, descendant in ipairs(child:GetDescendants()) do if descendant:IsA("BasePart") and descendant.CanCollide and descendant.Transparency < 0.5 and descendant.Size.Magnitude > 3 then table.insert(parts, descendant) end end end end if #parts == 0 then return rootPart.Position, 20 end local minX, maxX = math.huge, -math.huge local minZ, maxZ = math.huge, -math.huge for _, part in ipairs(parts) do local pos = part.Position local size = part.Size minX = math.min(minX, pos.X - size.X / 2) maxX = math.max(maxX, pos.X + size.X / 2) minZ = math.min(minZ, pos.Z - size.Z / 2) maxZ = math.max(maxZ, pos.Z + size.Z / 2) end local center = Vector3.new((minX + maxX) / 2, rootPart.Position.Y, (minZ + maxZ) / 2) local radius = math.min((maxX - minX) / 2, (maxZ - minZ) / 2) * 0.6 return center, math.max(radius, 10) end local function getRandomDirection() local angle = math.random() * math.pi * 2 return Vector3.new(math.cos(angle), 0, math.sin(angle)) end local function isBlocked(direction, distance) if not isValidCharacter() then return false end local rayParams = RaycastParams.new() rayParams.FilterType = Enum.RaycastFilterType.Blacklist rayParams.FilterDescendantsInstances = {character} local rayOrigin = rootPart.Position + Vector3.new(0, 1, 0) local rayResult = Workspace:Raycast(rayOrigin, direction * distance, rayParams) if not rayResult then return false end local hit = rayResult.Instance if hit and hit.CanCollide and hit.Transparency < 0.5 and hit.Size.Magnitude > 0.5 then return true end if hit and hit.Parent then local hitPlayer = Players:GetPlayerFromCharacter(hit.Parent) if hitPlayer then return true end end return false end local function isEdgeAhead(direction, distance) if not isValidCharacter() then return false end local checkPosition = rootPart.Position + direction * distance + Vector3.new(0, 0.5, 0) local rayParams = RaycastParams.new() rayParams.FilterType = Enum.RaycastFilterType.Blacklist rayParams.FilterDescendantsInstances = {character} local rayOrigin = checkPosition + Vector3.new(0, 3, 0) local rayResult = Workspace:Raycast(rayOrigin, Vector3.new(0, -15, 0), rayParams) if not rayResult then return true end local hitPart = rayResult.Instance if hitPart and (not hitPart.CanCollide or hitPart.Transparency >= 0.5) then return true end local heightDiff = math.abs(rootPart.Position.Y - rayResult.Position.Y) if heightDiff > 4 then return true end return false end local function getDirectionAwayFromBarrier() if not isValidCharacter() then return -moveDirection end local rayParams = RaycastParams.new() rayParams.FilterType = Enum.RaycastFilterType.Blacklist rayParams.FilterDescendantsInstances = {character} local bestDir = nil local bestDist = 0 for i = 1, 36 do local angle = math.random() * math.pi * 2 local dir = Vector3.new(math.cos(angle), 0, math.sin(angle)) local rayOrigin = rootPart.Position + Vector3.new(0, 1, 0) local rayResult = Workspace:Raycast(rayOrigin, dir * 10, rayParams) local dist = rayResult and (rayResult.Position - rayOrigin).Magnitude or 10 if dist > bestDist and not isEdgeAhead(dir, 5) then bestDist = dist bestDir = dir end end if bestDir then return bestDir end local perpDir = moveDirection:Cross(Vector3.new(0, 1, 0)) if math.random() < 0.5 then perpDir = -perpDir end local newDir = (-moveDirection + perpDir).Unit if isEdgeAhead(newDir, 5) or isBlocked(newDir, 3) then newDir = -moveDirection end return newDir end local function getWanderDirection() if not centerPosition then centerPosition, wanderRadius = findMapCenter() end if not isValidCharacter() then return getRandomDirection() end local toCenter = centerPosition - rootPart.Position toCenter = Vector3.new(toCenter.X, 0, toCenter.Z) local distToCenter = toCenter.Magnitude if distToCenter > wanderRadius then return toCenter.Unit else local baseAngle = math.atan2(toCenter.Z, toCenter.X) local angleVariation = math.random() * math.pi / 3 - math.pi / 6 local directionChoice = math.random() local newAngle if directionChoice < 0.33 then newAngle = baseAngle + math.pi / 2 + angleVariation elseif directionChoice < 0.66 then newAngle = baseAngle - math.pi / 2 + angleVariation else newAngle = baseAngle + angleVariation end local newDir = Vector3.new(math.cos(newAngle), 0, math.sin(newAngle)) if isEdgeAhead(newDir, 5) then newDir = getDirectionAwayFromBarrier() end return newDir end end local function checkStuck() if not isValidCharacter() then return end table.insert(lastPositions, rootPart.Position) if #lastPositions > 30 then table.remove(lastPositions, 1) end if #lastPositions >= 30 then local totalMovement = 0 for i = 2, #lastPositions do totalMovement = totalMovement + (lastPositions[i] - lastPositions[i-1]).Magnitude end if totalMovement < 0.5 then stuckCounter = stuckCounter + 0.1 if stuckCounter >= maxStuckTime then stuckDetected = true stuckCounter = 0 end else stuckCounter = math.max(0, stuckCounter - 0.2) stuckDetected = false end end end local function updateNearbyPlayers() nearbyPlayers = {} if not isValidCharacter() then return end for _, plr in ipairs(Players:GetPlayers()) do if plr ~= player then local char = plr.Character if char then local hrp = char:FindFirstChild("HumanoidRootPart") if hrp then local dist = (rootPart.Position - hrp.Position).Magnitude if dist < playerDetectionRange then table.insert(nearbyPlayers, {player = plr, distance = dist, position = hrp.Position}) end end end end end end local function updateBehaviorTree() utilityScores = {} if not isValidCharacter() then utilityScores.idleStand = 10 return "idleStand" end utilityScores.avoidEdge = (isEdgeAhead(moveDirection, 4) and not isStanding) and 11 or 0 utilityScores.avoidObstacle = (isBlocked(moveDirection, 4) and not isStanding) and 10 or 0 utilityScores.escapeStuck = stuckDetected and 9 or 0 utilityScores.returnToCenter = (centerPosition and (centerPosition - rootPart.Position).Magnitude > wanderRadius) and 8 or 0 utilityScores.wanderRandomly = (not isStanding and not stuckDetected) and 5 or 0 utilityScores.idleStand = isStanding and 3 or 0 utilityScores.explore = (emotionalState == "curious" or emotionalState == "energetic" or emotionalState == "playful") and 6 or 0 utilityScores.socialInteraction = (socialAwareness > 0.5 and #nearbyPlayers > 0) and 4 or 0 utilityScores.imitate = (math.random() < imitationChance and #nearbyPlayers > 0) and 2 or 0 utilityScores.curious = (curiosityLevel > 0.7) and 7 or 0 local highestUtility = 0 local bestBehavior = "idleStand" for behavior, utility in pairs(utilityScores) do if utility > highestUtility then highestUtility = utility bestBehavior = behavior end end return bestBehavior end local function executeBehavior(behavior) if not isValidCharacter() then return end if behavior == "avoidEdge" then moveDirection = getDirectionAwayFromBarrier() isStanding = true standStart = tick() standDuration = 2 elseif behavior == "avoidObstacle" then moveDirection = getDirectionAwayFromBarrier() if math.random() < personalityTraits.caution then isStanding = true standStart = tick() standDuration = 2 end elseif behavior == "escapeStuck" then moveDirection = getDirectionAwayFromBarrier() isStanding = false walkDuration = math.random(20, 35) / 10 walkUntil = tick() + walkDuration stuckDetected = false lastPositions = {} elseif behavior == "returnToCenter" then local toCenter = (centerPosition - rootPart.Position).Unit moveDirection = toCenter isStanding = false walkDuration = math.random(30, 50) / 10 walkUntil = tick() + walkDuration elseif behavior == "wanderRandomly" then if not isStanding then local targetPos = rootPart.Position + moveDirection * 10 humanoid:MoveTo(targetPos) end elseif behavior == "idleStand" then humanoid:MoveTo(rootPart.Position) elseif behavior == "explore" then if not isStanding then local targetPos = rootPart.Position + moveDirection * 15 humanoid:MoveTo(targetPos) end elseif behavior == "socialInteraction" then if #nearbyPlayers > 0 then local target = nearbyPlayers[1] local dirToPlayer = (target.position - rootPart.Position).Unit if target.distance < personalSpace then moveDirection = getDirectionAwayFromBarrier() else moveDirection = dirToPlayer end end elseif behavior == "imitate" then if #nearbyPlayers > 0 and math.random() < 0.3 then local target = nearbyPlayers[math.random(1, #nearbyPlayers)] local targetChar = target.player.Character if targetChar then local targetHRP = targetChar:FindFirstChild("HumanoidRootPart") if targetHRP then local targetVelocity = targetHRP.Velocity if targetVelocity.Magnitude > 1 then moveDirection = targetVelocity.Unit end end end end elseif behavior == "curious" then if not curiosityTarget then local randomAngle = math.random() * math.pi * 2 local randomDist = math.random(10, 30) curiosityTarget = rootPart.Position + Vector3.new(math.cos(randomAngle), 0, math.sin(randomAngle)) * randomDist curiosityTimer = math.random(30, 80) / 10 end if curiosityTarget then moveDirection = (curiosityTarget - rootPart.Position).Unit curiosityTimer = curiosityTimer - 0.1 if curiosityTimer <= 0 then curiosityTarget = nil end end end end local function onHeartbeat() if not isValidCharacter() then if player.Character and player.Character ~= character then character = player.Character humanoid = character:WaitForChild("Humanoid") rootPart = character:WaitForChild("HumanoidRootPart") centerPosition, wanderRadius = findMapCenter() moveDirection = getWanderDirection() isStanding = false walkDuration = math.random(25, 50) / 10 walkUntil = tick() + walkDuration checkCooldown = 0 lastPositions = {} stuckDetected = false stuckCounter = 0 obstacleMemory = {} end return end if not npcEnabled then return end local now = tick() if now - lastFrameUpdate < 0.05 then return end lastFrameUpdate = now checkStuck() updateNearbyPlayers() local bestBehavior = updateBehaviorTree() executeBehavior(bestBehavior) if checkCooldown > now then return end if isStanding then humanoid:MoveTo(rootPart.Position) if now - standStart >= standDuration then isStanding = false moveDirection = getWanderDirection() walkDuration = math.random(25, 50) / 10 walkUntil = now + walkDuration end return end if isEdgeAhead(moveDirection, 4) then moveDirection = getDirectionAwayFromBarrier() isStanding = true standStart = now standDuration = 2 checkCooldown = now + 0.3 return end if isBlocked(moveDirection, 4) then moveDirection = getDirectionAwayFromBarrier() isStanding = true standStart = now standDuration = 2 checkCooldown = now + 0.3 return end humanoid:MoveTo(rootPart.Position + moveDirection * 10) if now >= walkUntil then isStanding = true standStart = now standDuration = math.random(15, 30) / 10 end end centerPosition, wanderRadius = findMapCenter() moveDirection = getWanderDirection() walkDuration = math.random(25, 50) / 10 walkUntil = tick() + walkDuration RunService.Heartbeat:Connect(onHeartbeat) player.CharacterAdded:Connect(function(newCharacter) character = newCharacter humanoid = character:WaitForChild("Humanoid") rootPart = character:WaitForChild("HumanoidRootPart") centerPosition, wanderRadius = findMapCenter() moveDirection = getWanderDirection() isStanding = false walkDuration = math.random(25, 50) / 10 walkUntil = tick() + walkDuration checkCooldown = 0 lastPositions = {} stuckDetected = false stuckCounter = 0 obstacleMemory = {} end)