SHARE
TWEET

Untitled

a guest Feb 16th, 2020 68 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. local Players = game:GetService('Players')
  2.  
  3. local module = {}
  4. local existingNPC = {}
  5.  
  6. function module.new(npc)
  7.     if not existingNPC[npc.Name] then
  8.         return module:create(npc)
  9.     end
  10.     return existingNPC[npc.Name]
  11. end
  12.  
  13. function module:create(npc)
  14.     local newNPC = {}
  15.     setmetatable(newNPC, self)
  16.    
  17.     self.NPC = npc
  18.     self.Name = self.NPC.Name
  19.     self.Root = self.NPC.PrimaryPart
  20.     self.Humanoid = self.NPC:WaitForChild('Humanoid')
  21.     self.Health = self.Humanoid.Health
  22.     self.MaxHealth = self.Humanoid.MaxHealth
  23.     self.TargetChanged = nil
  24.    
  25.     -- Stats (Things you can change)
  26.     self.Visibility = 100 -- How far it can detect enemies
  27.     self.WalkingSpeed = 9 -- When it doesn't have an enemy, it walks randomly
  28.     self.RunningSpeed = 15 -- When it finds an enemy, it runs toward it
  29.     self.ChargingSpeed = 20 -- Desperate mode when it's low health but also very near
  30.     self.ScareFactor = 0.25 -- Determines the health at which it stops attacking in order to heal.
  31.     self.ChargeFactor = 10 -- Distance at which the npc charges if it's at ScareFactor (instead of heal).
  32.    
  33.     -- Info
  34.     self.Target = nil
  35.     self.TargetPoints = {}
  36.     self.Distance = 0
  37.     self.Path = nil
  38.    
  39.     -- Internal
  40.     self.WalkSpeed = self.Humanoid.WalkSpeed
  41.     self.States = {Walking = self.WalkingSpeed, Running = self.RunningSpeed, Charging = self.ChargingSpeed}
  42.     self.State = self.States.Walking
  43.     self.UnusedStateTypes = {
  44.         Enum.HumanoidStateType.Flying,
  45.         Enum.HumanoidStateType.Seated,
  46.         Enum.HumanoidStateType.StrafingNoPhysics,
  47.         Enum.HumanoidStateType.Climbing,
  48.         Enum.HumanoidStateType.Swimming
  49.     }
  50.    
  51.     -- Utility Functions
  52.     function self.MandateType(object, _type)
  53.         if _type == Vector3 then
  54.             if typeof(object) ~= _type then
  55.                 return object.Position
  56.             end
  57.             return object
  58.         end
  59.         assert(typeof(object) ~= _type, 'Object is not the correct type')
  60.     end
  61.    
  62.     function self:create(func)
  63.         self.MandateType(func, 'function')
  64.         coroutine.resume(coroutine.create(func))
  65.     end
  66.    
  67.     -- Main Functions
  68.     function self.CheckDistance(player, object)
  69.         local character = player.Character or player.CharacterAdded:Wait()
  70.         if object then
  71.             object = self.MandateType(object, Vector3)
  72.             return math.abs((object - character.PrimaryPart.Position).Magnitude)
  73.         end
  74.         return math.abs((self.Root.Position - character.PrimaryPart.Position).Magnitude)
  75.     end
  76.    
  77.     function self.GetNearestPlayer(object)
  78.         local closestDistance = self.Visibility
  79.         local closestPlayer = nil
  80.         for _, player in pairs(Players:GetPlayers()) do
  81.             local dist = 0
  82.             if object then
  83.                 dist = self.CheckDistance(player, object)
  84.             else
  85.                 dist = self.CheckDistance(player)
  86.                 self.TargetPoints[player.Name] = dist
  87.             end
  88.             if dist < closestDistance then
  89.                 closestDistance = dist
  90.                 closestPlayer = player
  91.             end
  92.         end
  93.         return closestPlayer, closestDistance
  94.     end
  95.    
  96.     function self.RayCast(thing, direction)
  97.         local ray = nil
  98.         if thing and not direction then
  99.             thing = self.MandateType(thing, Vector3)
  100.             ray = Ray.new(self.Root.Position, (thing - self.Root.Position).Unit * (self.Visibility + 10))
  101.         elseif direction and not thing then
  102.             ray = Ray.new(self.Root.Position, direction * (self.Visibility + 10))
  103.         end
  104.         local part, pos = workspace:FindPartOnRay(ray, self.NPC)
  105.         local distance = math.abs((self.Root.Position - pos).Magnitude)
  106.         return part, pos, distance
  107.     end
  108.    
  109.     function self.AvoidEnemyPosition()
  110.         local targetPos = self.Target.Character.PrimaryPart.Position
  111.         local unit = (targetPos - self.Root.Position).Unit
  112.         local angle = math.atan2(-unit.Z, -unit.X) + math.rad(Random.new():NextNumber(-90, 90))
  113.         local cframe = CFrame.new(self.Root.Position) * CFrame.Angles(0, angle, 0) * CFrame.new(0, 0, -1)
  114.         return (cframe.Position - self.Root.Position).Unit
  115.     end
  116.    
  117.     -- Once it has the random direction to avoid the closest enemy,
  118.     -- it checks if there is another enemy near that direction
  119.     -- if there is, it should make a different random direction.
  120.     function self.CheckNearTargets(direction)
  121.         if not direction then
  122.             direction = self.AvoidEnemyPosition()
  123.         end
  124.         local part = self.RayCast(nil, direction)
  125.         local nearestPlayer, distance = self.GetNearestPlayer(part)
  126.         if distance < self.Visibility * 0.75 then
  127.             -- It is too close, make another random direction.
  128.             self.CheckNearTargets(self.AvoidEnemyPosition())
  129.             return
  130.         end
  131.     end
  132.    
  133.     function self.Logic()
  134.         -- still doing
  135.     end
  136.    
  137.     function self.Init()
  138.         self.Target, self.Distance = self.GetNearestPlayer()
  139.         if not self.TargetChanged then
  140.             repeat wait() until self.TargetChanged
  141.         end
  142.         self.TargetChanged:Fire(self.Target)
  143.     end
  144.    
  145.     function self.Setup()
  146.         self:create(function()
  147.             for _, stateType in pairs(self.UnusedStateTypes) do
  148.                 self.Humanoid:SetStateEnabled(stateType, false)
  149.             end
  150.         end)
  151.            
  152.         if not script:FindFirstChild(self.Name .. 'Event') then
  153.             self.TargetChanged = Instance.new('BindableEvent')
  154.             self.TargetChanged.Name = self.Name .. 'Event'
  155.             self.TargetChanged.Parent = script
  156.         end
  157.        
  158.         -- Update state/walkspeed
  159.         script[self.Name .. 'Event'].Event:Connect(function(target)
  160.             self.State = (not self.Target and self.States.Walking) or self.States.Running
  161.             self.WalkSpeed = self.State
  162.         end)
  163.     end
  164.  
  165.     existingNPC[self.Name] = self
  166.     self.__tostring = self.Name
  167.     self.__index = self
  168.     self.Setup()
  169.     return self
  170. end
  171.  
  172. return module
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
Top