Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local Players = game:GetService('Players')
- local module = {}
- local existingNPC = {}
- function module.new(npc)
- if not existingNPC[npc.Name] then
- return module:create(npc)
- end
- return existingNPC[npc.Name]
- end
- function module:create(npc)
- local newNPC = {}
- setmetatable(newNPC, self)
- self.NPC = npc
- self.Name = self.NPC.Name
- self.Root = self.NPC.PrimaryPart
- self.Humanoid = self.NPC:WaitForChild('Humanoid')
- self.Health = self.Humanoid.Health
- self.MaxHealth = self.Humanoid.MaxHealth
- self.TargetChanged = nil
- -- Stats (Things you can change)
- self.Visibility = 100 -- How far it can detect enemies
- self.WalkingSpeed = 9 -- When it doesn't have an enemy, it walks randomly
- self.RunningSpeed = 15 -- When it finds an enemy, it runs toward it
- self.ChargingSpeed = 20 -- Desperate mode when it's low health but also very near
- self.ScareFactor = 0.25 -- Determines the health at which it stops attacking in order to heal.
- self.ChargeFactor = 10 -- Distance at which the npc charges if it's at ScareFactor (instead of heal).
- -- Info
- self.Target = nil
- self.TargetPoints = {}
- self.Distance = 0
- self.Path = nil
- -- Internal
- self.WalkSpeed = self.Humanoid.WalkSpeed
- self.States = {Walking = self.WalkingSpeed, Running = self.RunningSpeed, Charging = self.ChargingSpeed}
- self.State = self.States.Walking
- self.UnusedStateTypes = {
- Enum.HumanoidStateType.Flying,
- Enum.HumanoidStateType.Seated,
- Enum.HumanoidStateType.StrafingNoPhysics,
- Enum.HumanoidStateType.Climbing,
- Enum.HumanoidStateType.Swimming
- }
- -- Utility Functions
- function self.MandateType(object, _type)
- if _type == Vector3 then
- if typeof(object) ~= _type then
- return object.Position
- end
- return object
- end
- assert(typeof(object) ~= _type, 'Object is not the correct type')
- end
- function self:create(func)
- self.MandateType(func, 'function')
- coroutine.resume(coroutine.create(func))
- end
- -- Main Functions
- function self.CheckDistance(player, object)
- local character = player.Character or player.CharacterAdded:Wait()
- if object then
- object = self.MandateType(object, Vector3)
- return math.abs((object - character.PrimaryPart.Position).Magnitude)
- end
- return math.abs((self.Root.Position - character.PrimaryPart.Position).Magnitude)
- end
- function self.GetNearestPlayer(object)
- local closestDistance = self.Visibility
- local closestPlayer = nil
- for _, player in pairs(Players:GetPlayers()) do
- local dist = 0
- if object then
- dist = self.CheckDistance(player, object)
- else
- dist = self.CheckDistance(player)
- self.TargetPoints[player.Name] = dist
- end
- if dist < closestDistance then
- closestDistance = dist
- closestPlayer = player
- end
- end
- return closestPlayer, closestDistance
- end
- function self.RayCast(thing, direction)
- local ray = nil
- if thing and not direction then
- thing = self.MandateType(thing, Vector3)
- ray = Ray.new(self.Root.Position, (thing - self.Root.Position).Unit * (self.Visibility + 10))
- elseif direction and not thing then
- ray = Ray.new(self.Root.Position, direction * (self.Visibility + 10))
- end
- local part, pos = workspace:FindPartOnRay(ray, self.NPC)
- local distance = math.abs((self.Root.Position - pos).Magnitude)
- return part, pos, distance
- end
- function self.AvoidEnemyPosition()
- local targetPos = self.Target.Character.PrimaryPart.Position
- local unit = (targetPos - self.Root.Position).Unit
- local angle = math.atan2(-unit.Z, -unit.X) + math.rad(Random.new():NextNumber(-90, 90))
- local cframe = CFrame.new(self.Root.Position) * CFrame.Angles(0, angle, 0) * CFrame.new(0, 0, -1)
- return (cframe.Position - self.Root.Position).Unit
- end
- -- Once it has the random direction to avoid the closest enemy,
- -- it checks if there is another enemy near that direction
- -- if there is, it should make a different random direction.
- function self.CheckNearTargets(direction)
- if not direction then
- direction = self.AvoidEnemyPosition()
- end
- local part = self.RayCast(nil, direction)
- local nearestPlayer, distance = self.GetNearestPlayer(part)
- if distance < self.Visibility * 0.75 then
- -- It is too close, make another random direction.
- self.CheckNearTargets(self.AvoidEnemyPosition())
- return
- end
- end
- function self.Logic()
- -- still doing
- end
- function self.Init()
- self.Target, self.Distance = self.GetNearestPlayer()
- if not self.TargetChanged then
- repeat wait() until self.TargetChanged
- end
- self.TargetChanged:Fire(self.Target)
- end
- function self.Setup()
- self:create(function()
- for _, stateType in pairs(self.UnusedStateTypes) do
- self.Humanoid:SetStateEnabled(stateType, false)
- end
- end)
- if not script:FindFirstChild(self.Name .. 'Event') then
- self.TargetChanged = Instance.new('BindableEvent')
- self.TargetChanged.Name = self.Name .. 'Event'
- self.TargetChanged.Parent = script
- end
- -- Update state/walkspeed
- script[self.Name .. 'Event'].Event:Connect(function(target)
- self.State = (not self.Target and self.States.Walking) or self.States.Running
- self.WalkSpeed = self.State
- end)
- end
- existingNPC[self.Name] = self
- self.__tostring = self.Name
- self.__index = self
- self.Setup()
- return self
- end
- return module
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement