So here’s my issue. When my npc dies from the module it always gives it a new name once respawned which I don’t want.
Here’s what I need for the name generation:
Random.new():NextInteger(Settings.Trf.Min, Settings.Trf.Max)
Heres my main code:
local NPCModule = {}
local NPCModule_mt = {__index = NPCModule}
NPCModule.AIList = {}
--// Services
local players = game:GetService("Players")
local ts = game:GetService('TweenService')
local chatService = game:GetService("Chat")
local runService = game:GetService("RunService")
local pathfindingService = game:GetService("PathfindingService")
local textChatService = game:GetService("TextChatService")
--// Variables
local noobPath = require(script:FindFirstChild("NoobPath"))
local Settings = require(script:WaitForChild("Setting"))
local Pathfind = require(script:WaitForChild("PathFindingModule"))
local GoogleTranslator = require(script:WaitForChild("GoogleTranslator"))
local NPCSpawner = require(script:WaitForChild("NPCSpawner"))
local FilterMod = require(script:WaitForChild("FilteringModule"))
local chatf = game:GetService("ReplicatedStorage"):WaitForChild("ChatFunction")
local generatedNames = {}
local agentRadius = 4
local agentHeight = 5
local threads = {}
local function newAI(self: AI, character)
local newAI = NPCModule.new(character, { useSimplePath = self.useSimplePath, Inventory = self.Inventory })
newAI.Name = self.Name
newAI.chatColor = self.chatColor
newAI.canChat = self.canChat
newAI.Language = self.Language
newAI.active = false
newAI.Team = self.Team
print(self.Inventory)
newAI:Update()
end
local function activateToolAI(self: AI, tool)
-- R15 Support by TheDavidsProject
local npc = tool.Parent
local Humanoid = npc:FindFirstChildWhichIsA("Humanoid")
local brickcolor = nil
if npc:FindFirstChild("NPCTeam") then
brickcolor = npc.NPCTeam.Value
end
--The following function was written by Ajedi32.
---------------------------------------------------------
local function findNearestTorso(pos, maxDistance, randomSelect) -- Also targets AIs
local list = workspace:GetChildren()
local torsos = {}
for x=1, #list do
if (list[x].className == "Model") and (list[x] ~= npc) then
local torso = list[x]:findFirstChild("HumanoidRootPart")
local human = list[x]:findFirstChildWhichIsA("Humanoid")
if torso ~= nil and human ~= nil and human.ClassName == "Humanoid" and human.Health > 0 then
for i=1,#torsos do
if (torso.Position - pos).magnitude < torsos[i][2] then
table.insert(torsos, i, {torso, (torso.Position - pos).magnitude})
break
end
if i == #torsos and (maxDistance == nil or (torso.Position - pos).magnitude < maxDistance) then
table.insert(torsos, {torso, (torso.Position - pos).magnitude})
end
end
if torsos[1] == nil and (maxDistance == nil or (torso.Position - pos).magnitude < maxDistance) then
torsos[1] = {torso, (torso.Position - pos).magnitude}
end
end
end
end
if randomSelect == nil then
return torsos[1][1], torsos[1][2]
else
if randomSelect > #torsos then randomSelect = #torsos end
--
if randomSelect ~= 0 then
local selection = math.random(1, randomSelect)
return torsos[selection][1], torsos[selection][2]
end
end
end
---------------------------------------------------------
--Everything below here was written by me though ;)
if tool:FindFirstChild("HarmsEnemies") ~= nil and tool:FindFirstChild("HarmsEnemies").Value == true then
--The NPC should use the tool to harm the nearest humanoid.
while wait(.5) or tool:FindFirstChild("HarmsEnemies") ~= nil and tool:FindFirstChild("HarmsEnemies").Value == true do --This is really too often but it doesn't make the NPC stop every second, should fix.
local torso = findNearestTorso(npc.HumanoidRootPart.Position, math.huge, 1) --Was originally higher than 512 but once pathfinding was added that became pointless.
if torso ~= nil then
if torso.Parent ~= npc then--Was using that for testing ;) Might want to use it again.
if brickcolor ~= nil then
if torso.Parent:FindFirstChild("NPCTeam") ~= nil then
othercolor = torso.Parent:FindFirstChild("NPCTeam").Value
if othercolor ~= brickcolor then
self.path:Run(torso)
--local path = game:GetService("PathfindingService"):FindPathAsync(npc.HumanoidRootPart.Position, torso.Position):GetPointCoordinates()
--if path ~= {} then
-- if pcall(function() Humanoid:MoveTo(path[2]) end) then
-- elseif pcall(function() Humanoid:MoveTo(path[1]) end)then
-- Humanoid:MoveTo(path[1])
-- end
--end
else--Do nothing.
end
else--The other NPC has no team, kill them. NOTE: Add neutral option.
self.path:Run(torso)
end
elseif brickcolor == nil then--Neither NPC has a team associated with it. Kill the other one!
self.path:Run(torso)
end
Humanoid.MoveToFinished:connect(function()
if (npc.HumanoidRootPart.Position-torso.Position).magnitude <= 10 and brickcolor == nil then
tool:Activate()
elseif (npc.HumanoidRootPart.Position-torso.Position).magnitude <= 10 and brickcolor ~= nil then
tool:Activate()
end
end)
end
end
end
else
--The NPC should use the tool randomly.
while true do
wait(math.random(4, 16))
tool:Activate()
end
end
end
local function activatePickUpAI(self: AI)
local debounce = false
for i, npcpart in pairs(self.AI:GetChildren()) do
if npcpart:IsA("BasePart") then
npcpart.Touched:connect(function(otherpart)
if otherpart.Parent ~= nil and otherpart.Parent:IsA("Tool") and otherpart.Parent:FindFirstChildWhichIsA("Humanoid") == nil and debounce == false then --No support for hopperbins, but they are deprecated anyway.
debounce = true
--They have just touched a tool. Time to equip it.
local tool = otherpart.Parent:Clone()
tool.Parent = otherpart.Parent
local Humanoid = npcpart.Parent:FindFirstChildWhichIsA("Humanoid")
if script:GetAttribute("EnableInventory") then
warn("Inventory Enabled")
otherpart.Parent = nil
local OldTool = self.AI:FindFirstChildWhichIsA("Tool")
if OldTool then
if OldTool == nil then
table.remove(self.Inventory, table.find(self.Inventory, OldTool))
end
warn(`{self.AI.Name} Current Inventory:`, self.Inventory)
table.insert(self.Inventory, OldTool)
end
warn(`{self.AI.Name} Current Inventory:`, self.Inventory)
table.insert(self.Inventory, OldTool)
else
warn("Normal")
Humanoid:EquipTool(tool)
otherpart.Parent:Destroy() --Removes it, just like a real tool!
end
local currentTool = tool:Clone()
currentTool.Parent = self.inventoryFolder
table.insert(self.Inventory, currentTool)
Humanoid:EquipTool(tool)
activateToolAI(self, tool)
wait() --Long wait for this debounce!
debounce = false
end
end)
end
end
end
local function RandomValue(array)
return array[math.random(1,#array)]
end
local function IsAlive(hum: Humanoid)
return hum.Health > 0
end
local function InLineOfSight(self, target)
if self.settings.InLineOfSightUseRay == false then return true end
local origin = self.RootSightPart.Position
local direction = (target.Position - self.RootSightPart.Position).unit * self.settings.MaxDistance
local ray = Ray.new(origin, direction)
local ignorelist = self.tempIgnore
local hit, pos = workspace:FindPartOnRayWithIgnoreList(ray, ignorelist, true, true)
if hit then
if hit:IsDescendantOf(target.Parent) then
return true
elseif hit:IsA("BasePart") and hit.Transparency > 0 and not table.find(self.tempIgnore, hit) then
table.insert(self.tempIgnore, hit)
end
end
return false
end
local function GetCharacterHeight(character: Model)
return character:GetExtentsSize().Y --return select(2, character:GetBoundingBox()).Y
end
local function MoveUntilFinished(Humanoid, TargetPosition, Timeout)
if not Humanoid.Parent then
return
end
local Done = Instance.new("BindableEvent"); Done.Event:Connect(function() Humanoid:Move(Vector3.new()) end)
local SteppedConnection = nil
local DebugPart = nil
local Debug = true
if Debug then
local Torso = Humanoid.Parent:FindFirstChild("Torso")
if Torso then
--Torso.Transparency = 1
end
DebugPart = Instance.new("Part")
DebugPart.Position = TargetPosition
DebugPart.Size = Vector3.new(0.25,0.25,0.25)
DebugPart.Material = Enum.Material.Neon
DebugPart.BrickColor = BrickColor.new("Lime green")
DebugPart.CanCollide = false
DebugPart.CanQuery = false
DebugPart.CanTouch = false
DebugPart.Anchored = true
DebugPart.Parent = game.Workspace
end
local HRP = Humanoid.RootPart
local MoveToYAdjusted = Vector3.new(0,0,0)
--local LastY = (HRP ~= nil) and HRP.Position.Y or math.huge
--local LastDistance = math.huge
Humanoid:Move(MoveToYAdjusted)
SteppedConnection = game:GetService("RunService").Stepped:Connect(function()
if HRP then
MoveToYAdjusted = Vector3.new(TargetPosition.X, HRP.Position.Y, TargetPosition.Z)
local Distance = (HRP.Position - MoveToYAdjusted).Magnitude
if Distance <= 1.33 then
Done:Fire()
else
Humanoid:Move((MoveToYAdjusted - HRP.Position).Unit)
end
--LastDistance = Distance
else
Done:Fire()
end
end)
--[[Humanoid.MoveToFinished:Connect(function()
Done:Fire()
end)]]
task.delay(Timeout, function()
Done:Fire()
end)
Done.Event:Wait()
if SteppedConnection ~= nil then
SteppedConnection:Disconnect()
end
if DebugPart then
DebugPart:Destroy()
end
end
local function FixedToMove(Humanoid, TargetPosition, Timeout)
if not Timeout then Timeout = 8 end
MoveUntilFinished(Humanoid, TargetPosition, Timeout)
end
local function LookAtPlayer(self, player)
self.looking = true
self.stopWandering = true
--self.humanoid.AutoRotate = false
local targetPosition = player.Character.HumanoidRootPart.Position
local currentPosition = self.AI.HumanoidRootPart.Position
local adjustedTargetPosition = Vector3.new(targetPosition.X, currentPosition.Y, targetPosition.Z)
if self.wandering then
task.wait(.25)
end
local tween = ts:Create(self.AI.HumanoidRootPart, TweenInfo.new(Random.new():NextNumber(self.settings.TurnSpeeds.Min, self.settings.TurnSpeeds.Max), Enum.EasingStyle.Sine), {CFrame = CFrame.lookAt(currentPosition, adjustedTargetPosition)})
tween:Play()
local x = math.random(0,100)
if x <= self.settings.Chat2Chance and self.lookCanChat == false then
self.stopWandering = true
task.wait(tween.TweenInfo.Time)
if self.lookCanChat == false then
self.lookCanChat = true
if #self.settings.Chat2Messages ~= 0 then
self:Chat(RandomValue(self.settings.Chat2Messages))
end
task.wait(Random.new():NextNumber(self.settings.Chat2Delays.Min, self.settings.Chat2Delays.Max))
self.lookCanChat = false
end
end
end
local function LookAtDirection(self, position)
self.looking = true
self.stopWandering = true
--self.humanoid.AutoRotate = false
local targetPosition = position
local currentPosition = self.AI.HumanoidRootPart.Position
local adjustedTargetPosition = Vector3.new(targetPosition.X, currentPosition.Y, targetPosition.Z)
if self.wandering then
task.wait(.25)
end
local tween = ts:Create(self.AI.HumanoidRootPart, TweenInfo.new(Random.new():NextNumber(self.settings.TurnSpeeds.Min, self.settings.TurnSpeeds.Max), Enum.EasingStyle.Sine), {CFrame = CFrame.lookAt(currentPosition, adjustedTargetPosition)})
tween:Play()
local x = math.random(0,100)
if x <= self.settings.Chat2Chance and self.lookCanChat == false then
self.stopWandering = true
task.wait(tween.TweenInfo.Time)
if self.lookCanChat == false then
self.lookCanChat = true
if #self.settings.Chat2Messages ~= 0 then
self:Chat(RandomValue(self.settings.Chat2Messages))
end
task.wait(Random.new():NextNumber(self.settings.Chat2Delays.Min, self.settings.Chat2Delays.Max))
self.lookCanChat = false
end
end
end
local function GetRandomAvailableTeam()
local Teams = game.Teams:GetChildren()
local AvailableTeams = {}
for _,Team in pairs(Teams) do
if Team:IsA("Team") and Team.AutoAssignable == true then
table.insert(AvailableTeams, Team)
end
end
local Team = #AvailableTeams > 0 and AvailableTeams[math.random(1, #AvailableTeams)] or nil
return Team
end
local function GetTeamFromBrickColor(LookForBrickColor)
for _, Team in pairs(game.Teams:GetChildren()) do
if Team.TeamColor == LookForBrickColor then
return Team
end
end
end
function NPCModule.new(AI: Model, options: {
Inventory: {Tool},
}, id:number?)
local BOT = {}
setmetatable(BOT, NPCModule_mt)
print(options.Inventory)
local Params = Pathfind.PathFindParams.new()
Params.AgentRadius = agentRadius
Params.AgentHeight = agentHeight
Params.AgentCanJump = true
Params.AgentCanClimb = true
Params.WaypointSpacing = 2
Params.PathSettings = { SupportPartialPath = true }
Params.Costs = {
Water = 100,
}
BOT.AI = AI
BOT.Name = players:GetNameFromUserIdAsync(Random.new():NextInteger(Settings.Trf.Min, Settings.Trf.Max))
BOT.humanoid = AI:FindFirstChildOfClass("Humanoid")
BOT.settings = Settings
BOT.chatColor = RandomValue(BOT.settings.ChatColors)
BOT.wandering = false
BOT.chatting = false
BOT.stopWandering = false
BOT.looking = false
BOT.lookCanChat = false
BOT.useSimplePath = options.useSimplePath or false
--self.isMobile = math.random(1,2) == 2
BOT.active = false
BOT.path = noobPath.Humanoid(BOT.AI, Params)
BOT.tempIgnore = {AI}
BOT.Team = GetRandomAvailableTeam()
--// Extra Settings
BOT.canChat = math.random(1,2) == 2
BOT.canRun = true
BOT.canLookAround = false
BOT.RootSightPart = AI.PrimaryPart
BOT.Language = math.random(1,2) == 2 and "English" or "Spanish"
BOT.inventoryFolder = nil
BOT.Inventory = options.Inventory or {}
BOT.agentParams = Params
if not game:GetService("ReplicatedStorage"):FindFirstChild(`{BOT.Name} Inventory`) then
local botInventory = Instance.new("Folder", game.ReplicatedStorage)
botInventory.Name = `{BOT.Name} Inventory`
BOT.inventoryFolder = botInventory
end
if not BOT.AI:GetAttribute("ID") or id then
BOT.AI:SetAttribute("ID", id)
end
return BOT
end
-- Helper function to send chat message
function NPCModule:Chat(message)
local self: AI = self
if not self.canChat then return end
if self.settings.DontChatIfCurrentlyChatting and self.chatting then return end
if self.chatting then repeat task.wait() until self.chatting == false end
if self.wandering and not self.stopWandering then return else end
self.chatting = true
if not self.active then
self.chatting = false -- Stop if AI is removed
return
end
if self.settings.WaitMessageLength then
task.wait(#message / self.settings.WaitMessageLengthDivider)
end
print(message)
local chatColor = self.chatColor
local team: Team = self.Team
local currentMessage = GoogleTranslator.translate(GoogleTranslator.getLanguageAbreviation(self.Language), message)
if self.settings.FilterMessages then
local players = game:GetService("Players"):GetPlayers()
if #players > 0 then
currentMessage = FilterMod.Filter(players[math.random(1,#players)], currentMessage) -- game:GetService("TextService"):FilterStringAsync(currentMessage, players[math.random(1, #players)].UserId, Enum.TextFilterContext.PublicChat):GetNonChatStringForBroadcastAsync()
end
end
local formattedMessage
if team then
if textChatService.ChatVersion == Enum.ChatVersion.TextChatService then
formattedMessage = string.format(
"<font color=\"rgb(%s, %s, %s)\">%s:</font> %s",
math.floor(team.TeamColor.Color.R * 255),
math.floor(team.TeamColor.Color.G * 255),
math.floor(team.TeamColor.Color.B * 255),
self.AI.Name,
currentMessage
)
chatf:FireAllClients(self.AI.Head, formattedMessage, currentMessage)
else
end
else
if textChatService.ChatVersion == Enum.ChatVersion.TextChatService then
formattedMessage = string.format(
"<font color=\"rgb(%s, %s, %s)\">%s:</font> %s",
math.floor(chatColor.R * 255),
math.floor(chatColor.G * 255),
math.floor(chatColor.B * 255),
self.AI.Name,
currentMessage
)
print(formattedMessage)
chatf:FireAllClients(self.AI.Head, formattedMessage, currentMessage)
else
end
end
-- chatService:Chat(self.AI.Head, currentMessage, Enum.ChatColor.White) end
self.chatting = false
end
-- Check if the target is within line of sight
function NPCModule:InLineOfSight(target)
if not self.settings.InLineOfSightUseRay then return true end
local origin = self.settings.RootSightPart.Position
local direction = (target.Position - origin).Unit * self.settings.MaxDistance
local ray = Ray.new(origin, direction)
local hit, pos = workspace:FindPartOnRayWithIgnoreList(ray, self.tempIgnore, true, true)
if hit and hit:IsDescendantOf(target.Parent) then
return true
elseif hit and hit.Transparency > 0 and not table.find(self.tempIgnore, hit) then
table.insert(self.tempIgnore, hit)
end
return false
end
--function NPCModule:InsertToolsToInventory(Tools: {Tool})
-- if Tools then
-- local Inventory = self.Inventory
-- for index=1, #Tools do
-- local Tool = Tools[index]
-- if not table.find(Inventory, Tool) then
-- table.insert(Inventory, Tool)
-- end
-- end
-- end
--end
function NPCModule:EquipTool()
local self: AI = self
local AI = self.AI
local humanoid = self.humanoid
local Inventory: {Tool} = self.Inventory
local UseTool = script:WaitForChild("UseTool"):Clone()
if Inventory ~= {} or #Inventory ~= 0 then
local Tool = RandomValue(Inventory):Clone()
if not AI:FindFirstChildOfClass("Tool") then
humanoid:EquipTool(Tool)
activateToolAI(self, AI:FindFirstChildOfClass("Tool"))
else
if AI:FindFirstChildOfClass("Tool") then
humanoid:UnequipTools()
end
end
end
end
function NPCModule:EquipToolByName(name)
local self: AI = self
local AI = self.AI
local humanoid = self.humanoid
local Inventory = self.Inventory
local UseTool = script:WaitForChild("UseTool"):Clone()
for _, Tool in ipairs(Inventory) do
if Tool.Name == name then
if not AI:FindFirstChildOfClass("Tool") then
humanoid:EquipTool(Tool)
activateToolAI(self, AI:FindFirstChildOfClass("Tool"))
else
if AI:FindFirstChildOfClass("Tool") then
humanoid:UnequipTools()
end
end
end
end
end
-- Patrol around a defined area
function NPCModule:Wander()
if not IsAlive(self.humanoid) then return end
if self.stopWandering then return end
--print(GetCharacterHeight(self.humanoid.Parent)/2.5)
local Path = self.path
local winfo = {
minx = self.settings.Patrol.AxisX[1];
maxx = self.settings.Patrol.AxisX[2];
minz = self.settings.Patrol.AxisZ[1];
maxz = self.settings.Patrol.AxisZ[2];
}
local Goal = self.AI.HumanoidRootPart.Position + Vector3.new(math.random(winfo.minx, winfo.maxx), 0, math.random(winfo.minz, winfo.maxz))
local Reached = false
local CanRun = true
self.wandering = true
self.canRun = CanRun
--if Chasing then Path:Destroy() end
Path:Run(Goal)
Reached = false
repeat task.wait() until Reached == true or self.stopWandering == true -- or Chasing
CanRun = false
self.wandering = false
self.canRun = CanRun
--if Path and Path.Status == SimplePath.StatusType.Active then Path:Stop() end
end
function NPCModule:Move(Position)
if not IsAlive(self.humanoid) then return end
if self.stopWandering then return end
--print(GetCharacterHeight(self.humanoid.Parent)/2.5)
local Path = self.path
local Goal = Position
local Reached = false
local CanRun = true
self.wandering = true
self.canRun = CanRun
--if Chasing then Path:Destroy() end
Path:Run(Position)
Reached = true
repeat task.wait() until Reached == true or self.stopWandering == true -- or Chasing
CanRun = false
self.wandering = false
self.canRun = CanRun
--if Path and Path.Status == SimplePath.StatusType.Active then Path:Stop() end
end
-- Handle player chat events
function NPCModule:HandlePlayerChat(player, message)
local distance = (self.AI.HumanoidRootPart.Position - player.Character.HumanoidRootPart.Position).Magnitude
if distance <= self.settings.PlayerResponseRange and self:InLineOfSight(player.Character.HumanoidRootPart) then
self:Chat("Responding to: " .. message)
end
end
-- Main update function
function NPCModule:Update()
local self: AI = self
self.settings.RootSightPart = self.AI:FindFirstChild("Head")
if self.Team then
if not self.AI:FindFirstChild("NPCTeam") then
local NPCTeam = Instance.new("BrickColorValue", self.AI)
NPCTeam.Value = self.Team.TeamColor
NPCTeam.Name = "NPCTeam"
end
end
if not self.AI:GetAttribute(self.Language) then
self.AI:SetAttribute("Language", self.Language)
end
local respawnreserve = self.AI:Clone()
if respawnreserve:FindFirstChildOfClass("ForceField") then
respawnreserve:FindFirstChildOfClass("ForceField"):Destroy()
end
self.humanoid.Died:Connect(function()
spawn(function()
if self.settings.CanRespawn then
task.wait(self.settings.RespawnDelay)
respawnreserve.Parent = workspace
self.AI:Destroy()
newAI(self, respawnreserve)
self:Remove()
else
respawnreserve:Destroy()
end
end)
task.wait(self.settings.DieMessageDelay)
if self.humanoid:FindFirstChild("creator") then
local killer = self.humanoid:FindFirstChild("creator").Value
if killer and killer.Character and killer.Character:FindFirstChild("HumanoidRootPart") then
local dist = (self.humanoid.RootPart.Position - killer.Character:FindFirstChild("HumanoidRootPart").Position).Magnitude
if dist >= self.settings.DieMessagesFarRange then
if #self.settings.DieMessages.KillerFar ~= 0 then
self:Chat(RandomValue(self.settings.DieMessages.KillerFar))
end
else
if #self.settings.DieMessages.Killer ~= 0 then
self:Chat(RandomValue(self.settings.DieMessages.Killer))
end
end
end
else
if #self.settings.DieMessages.NoKiller ~= 0 then
self:Chat(RandomValue(self.settings.DieMessages.NoKiller))
end
end
end)
local loaded = false
self.AI.Name = self.Name
spawn(function()
activatePickUpAI(self)
end)
-- Spawning
if self.settings.StartOnRandomSpawns then
if self.active then
NPCSpawner:SpawnNPC(self.AI, self.Team)
self.humanoid.RootPart.Anchored = true
self.humanoid.RootPart.Anchored = false
wait()
self.humanoid.RootPart.Anchored = true
task.wait(math.random(4,16))
self.humanoid.RootPart.Anchored = false
loaded = true
else
NPCSpawner:SpawnNPC(self.AI, self.Team)
loaded = true
end
--local f = game.Workspace:GetDescendants()
--if self.settings.RandomSpawnFolder.Enabled == true then
-- f = self.settings.RandomSpawnFolder.Folder:GetDescendants() --:GetChildren()
--end
--local spawns = {}
--for _, v in f do
-- if v:IsA("SpawnLocation") then
-- table.insert(spawns, v)
-- end
--end
--local randomspawn = spawns[math.random(1, #spawns)]
--self.AI:SetPrimaryPartCFrame(randomspawn.CFrame * self.settings.SpawnOffset)
--end)
end
if loaded then
self.humanoid.Touched:Connect(function(Hit)
if Hit:IsA("SpawnLocation") and self.Team ~= GetTeamFromBrickColor(Hit.TeamColor) and Hit.AllowTeamChangeOnTouch == true then
self.Team = GetTeamFromBrickColor(Hit.TeamColor)
end
end)
--while self.humanoid.Health > 0 do
task.wait(0.1)
local hastransformed = true
-- Wandering
spawn(function() -- Chatting
while IsAlive(self.humanoid) do
task.wait(Random.new():NextNumber(self.settings.Chat1Time.Min, self.settings.Chat1Time.Max))
if not self.looking then
self:Chat(RandomValue(self.settings.Chat1Messages))
end
end
end)
spawn(function() -- Main Wandering
while IsAlive(self.humanoid) and self.settings.CanPatrol do
if not self.settings.CanPatrol then break end
task.wait(Random.new():NextNumber(self.settings.PatrolTimes.Min, self.settings.PatrolTimes.Max))
self:Wander()
end
end)
spawn(function()
while IsAlive(self.humanoid) do
task.wait(0.1)
local closestPlayer, closestDistance = nil, self.settings.LookAtRange
local currentTween
for _, player in pairs(game.Players:GetPlayers()) do
if player.Character and player.Character:FindFirstChild("HumanoidRootPart") then
local distance = (player.Character.HumanoidRootPart.Position - self.AI.HumanoidRootPart.Position).Magnitude
if distance < closestDistance and InLineOfSight(self, player.Character.HumanoidRootPart) then
closestPlayer, closestDistance = player, distance
end
end
end
if closestPlayer and not self.looking then
LookAtPlayer(self, closestPlayer)
else
self.looking = false
--self.humanoid.AutoRotate = true
end
end
end)
spawn(function()
while IsAlive(self.humanoid) do
task.wait(0.1)
local winfo = {
minx = self.settings.Patrol.AxisX[1];
maxx = self.settings.Patrol.AxisX[2];
minz = self.settings.Patrol.AxisZ[1];
maxz = self.settings.Patrol.AxisZ[2];
}
if self.canLookAround then
if math.random(1,2) == 2 then
task.wait(math.random(winfo.minx, winfo.maxx))
LookAtDirection(self, self.humanoid.RootPart.Position+Vector3.new(math.random(winfo.minx, winfo.maxx), 0, math.random(winfo.minz, winfo.maxz)))
end
end
end
end)
spawn(function()
while wait(math.random(1,60)) do
self:EquipTool()
self.stopWandering = false
end
end)
local function bindchat(ply: Player)
ply.Chatted:Connect(function(message)
if self.chatting then return end
if ply.Character and ply.Character:FindFirstChild("HumanoidRootPart") then
local dist = (self.humanoid.RootPart.Position-ply.Character:FindFirstChild("Humanoid").RootPart.Position).Magnitude
if dist <= self.settings.PlayerResponseRange then
if self.settings.ResponseNeedToBeInSight and not InLineOfSight(self, ply.Character:FindFirstChild("HumanoidRootPart")) then return end
local lowered = string.lower(message)
local question_patterns = {
"%?%s*$", -- Ends with a question mark
"can ", "could ", "will ", "would ", "should ", "do ", "does ",
"did ", "is ", "are ", "was ", "were ", "have ", "has ", "had ",
"what", "when", "where", "why", "who", "how", "which",
"wanna", "want"
}
local greeting_patterns = {
"hello", "hi", "hey", "greetings", "yo", "what's up", "howdy", "whats up"
}
local function is_question(msg)
for _, pattern in ipairs(question_patterns) do
if string.match(msg, pattern) then
return true
end
end
return false
end
local function is_greeting(msg)
for _, pattern in ipairs(greeting_patterns) do
if string.match(msg, pattern) then
return true
end
end
return false
end
local function requires_yesorno(msg)
return string.match(msg, "wanna") or string.match(msg, "want") or string.match(msg, "yes")
end
if is_greeting(lowered) then
if string.match(lowered, "what's up") then
self:Chat(RandomValue(self.settings.PlayerResponses.Greeting))
elseif is_question(lowered) then
if requires_yesorno(lowered) then
self:Chat(RandomValue(self.settings.PlayerResponses.Question.yesorno))
else
self:Chat(RandomValue(self.settings.PlayerResponses.Question.general))
end
else
self:Chat(RandomValue(self.settings.PlayerResponses.Greeting))
end
elseif is_question(lowered) then
if requires_yesorno(lowered) then
self:Chat(RandomValue(self.settings.PlayerResponses.Question.yesorno))
else
self:Chat(RandomValue(self.settings.PlayerResponses.Question.general))
end
else
if not self.settings.CustomResponses then
self:Chat(RandomValue(self.settings.PlayerResponses.Other))
else
local x = math.random(0,100)
if not (x <= self.settings.CustomReponseData.Chance) then return end
if not ply.Character or not ply.Character:FindFirstChild("HumanoidRootPart") then return end
local dist = (ply.Character:FindFirstChild("HumanoidRootPart").Position - self.humanoid.RootPart.Position).Magnitude
if dist <= self.settings.CustomReponseData.DistanceRequired then
for _, v in self.settings.CustomResponseMessages do
if string.match(lowered, v.TargetMessage) or lowered == v.TargetMessage then
self:Chat(RandomValue(v.Contents))
return
end
end
end
end
end
end
end
end)
end
task.spawn(function()
for _, ply in players:GetPlayers() do
bindchat(ply)
end
players.PlayerAdded:Connect(function(ply)
bindchat(ply)
end)
end)
--if self.settings.CanRespawn then
-- if self.AI:GetAttribute("AlreadyTransformed") == false then
-- repeat wait() until hastransformed == true
-- respawnreserve = self.AI:Clone()
-- local rrscript = respawnreserve:FindFirstChild(script.Name)
-- rrscript:SetAttribute("AlreadyTransformed", true)
-- rrscript.ChatColor.Value = script.ChatColor.Value
-- end
--end
--end
end
end
-- Start NPC behavior
function NPCModule:Start()
self.active = true
--self.AI.HumanoidRootPart.Anchored = true
--wait(math.random(4,16))
--self.AI.HumanoidRootPart.Anchored = false
self:Update()
end
-- Stop and cleanup NPC behavior
function NPCModule:Remove()
self.active = false
self.stopWandering = false
self.chatting = false
-- Clean up any other tasks or tweens
if self.path then
end
print(self.AI.Name .. " NPC AI stopped and cleaned up.")
self.AI = nil
end
function NPCModule:GetAIFromModelID(ID)
return NPCModule.AIList[ID]
end
type AI = typeof(NPCModule.new(..., ...))
return NPCModule
I don’t know how to do it