local I=game:GetService("\083\101\114\118\101\114\083\099\114\105\112\116\083\101\114\118\105\099\101")local A=I:WaitForChild("\065\103\101\110\116\115",2)local e=require(A:WaitForChild("\080\097\116\104\102\105\110\100\105\110\103\065\105\066\097\115\101",2))local X=game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")local a=require(X:WaitForChild("\078\101\118\101\114\109\111\114\101"))local S=a("\072\117\109\097\110\111\105\100\070\105\110\100\101\114")local J=(a("\083\116\100")).rquery local W=a("\112\101\114\099\101\112\116\105\111\110")local g=a("\083\116\097\116\101\077\097\099\104\105\110\101\077\097\099\104\105\110\101")local N={_configs={};CurrentTarget=nil,SoldatModel=nil,SoldatTorso=nil}local P={__index=N}function N.PushStates(I)local A=I:GetIdleState()local e=I.SoldatTorso.CFrame local X=0 local a=0 local g=I.SoldatTorso.BodyGyro local N=math.random(5,10)I.CurrentTarget=game.Workspace:FindFirstChild("\090\111\109\098\105\101")local P=I:GetIdleState()P.Action=function()local A=os.time()if A-a>N then X=math.random(-60,60)a=A N=math.random(5,10)end local S=I.SoldatTorso.CFrame.lookVector local J=math.acos(S:Dot(e.lookVector))g.cframe=e*CFrame.fromEulerAnglesXYZ(0,math.rad(X),0)end P.Init=function()g.maxTorque=Vector3.new(0,10000,0)end local V=0 local z=I.StateMachine.NewState("\065\116\116\097\099\107")local x=I.SoldatModel.Humanoid:LoadAnimation(I.SoldatModel.Animations.Aim)local b=false z.Action=function()if I.CurrentTarget then if not b then b=true x:Play()end local A=J.PersonageTorsoOrEquivalent(I.CurrentTarget)I.SoldatModel:SetPrimaryPartCFrame(CFrame.new(I.SoldatTorso.Position,Vector3.new(A.Position.X,I.SoldatTorso.Position.Y,A.Position.Z))*CFrame.Angles(0,-math.pi/3,0))local e=os.time()if e-V>I:GetConfigValue("\065\116\116\097\099\107\067\111\111\108\100\111\119\110")then V=e local X=A.Position-I.SoldatTorso.Position local a=-X.magnitude/I:GetConfigValue("\065\103\103\114\111\082\097\110\103\101")+1 if math.random()<a then I.CurrentTarget.Humanoid.Health=I.CurrentTarget.Humanoid.Health-I:GetConfigValue("\068\097\109\097\103\101")else local A=((X:Cross(Vector3.new(0,1,0))).unit*5)*math.random(-1,1)X=X+A local e=Ray.new(I.SoldatTorso.Position,X.unit*1000)local a={}table.insert(a,I.SoldatModel)local S,J=game.Workspace:FindPartOnRayWithIgnoreList(e,a)if S then if S and(S.Parent and(S.Parent:FindFirstChild("\072\117\109\097\110\111\105\100")and S.Parent~="\083\111\108\100\105\101\114"))then if game.Players:GetPlayerFromCharacter(S.Parent)and not I:GetConfigValue("\067\097\110\068\097\109\097\103\101\080\108\097\121\101\114")then return end local A=S.Parent:FindFirstChild("\072\117\109\097\110\111\105\100")A.Health=A.Health-I:GetConfigValue("\068\097\109\097\103\101")end end end local S=Instance.new("\080\097\114\116",game.Workspace)S.FormFactor=Enum.FormFactor.Custom S.Size=Vector3.new(.1,.1,X.magnitude)S.Anchored=true S.CanCollide=false S.CFrame=CFrame.new(I.SoldatTorso.Position+X/2,I.SoldatTorso.Position)S.BrickColor=BrickColor.Yellow()game.Debris:AddItem(S,.1)end end end z.Init=function()g.maxTorque=Vector3.new(0,0,0)end local j=I.StateMachine.NewCondition()j.Name="\067\097\110\083\101\101\084\097\114\103\101\116"j.Evaluate=function()local A=S:GetCurrent()local e={}local X={}for A,a in pairs(A)do if a and(a.Parent and(not game.Players:GetPlayerFromCharacter(a.Parent)and(a.Health>0 and(a.WalkSpeed>0 and a.Parent:FindFirstChild("\084\111\114\115\111")))))then local A=J.PersonageTorsoOrEquivalent(a.Parent)local S=(I.SoldatTorso.Position-A.Position).magnitude if S<=I.GetConfigValue("\065\103\103\114\111\082\097\110\103\101")then if a.Parent.Name=="\083\111\108\100\105\101\114"then table.insert(e,a.Parent)else table.insert(X,a.Parent)end end end end local a=W:GetClosestVisibleTarget(I.SoldatModel,X,e,I:GetConfigValue("\070\105\101\108\100\079\102\086\105\101\119"))if a then I.CurrentTarget=a return true end return false end j.TransitionState=z local Z=I.StateMachine.NewCondition()Z.Name="\084\097\114\103\101\116\068\101\097\100"Z.Evaluate=function()if I.CurrentTarget and I.CurrentTarget:FindFirstChild("\072\117\109\097\110\111\105\100")then return I.CurrentTarget.Humanoid.Health<=0 end return true end Z.TransitionState=P table.insert(P.Conditions,j)table.insert(z.Conditions,Z)I.StateMachine.SwitchState(P)I.Stop=function()I.StateMachine.SwitchState(nil)end end function N.LoadConfigsFromModel(A,I)local e=I:FindFirstChild("\067\111\110\102\105\103\117\114\097\116\105\111\110\115")A:LoadConfig(e,"\065\103\103\114\111\082\097\110\103\101",100)A:LoadConfig(e,"\070\105\101\108\100\079\102\086\105\101\119",180)A:LoadConfig(e,"\068\097\109\097\103\101",50)A:LoadConfig(e,"\065\116\116\097\099\107\067\111\111\108\100\111\119\110",1)A:LoadConfig(e,"\067\097\110\068\097\109\097\103\101\080\108\097\121\101\114",false)end function N.Init(A,I)A:LoadConfigsFromModel(I)A.SoldatModel=I A.SoldatTorso=J.PersonageTorsoOrEquivalent(A.SoldatModel)A.StateMachine=g.NewStateMachine()A:PushStates()end local V={}function V.new(I,A)local X=setmetatable({},P)local a=setmetatable(X,e.new())a:Init(I)return a end return V
local ServerScriptService = game:GetService("ServerScriptService")
local agentsFolder = ServerScriptService:WaitForChild("Agents", 2)

local aiBase = require(agentsFolder:WaitForChild("PathfindingAiBase", 2))

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local require = require(ReplicatedStorage:WaitForChild("Nevermore"))
local HumanoidList = require("HumanoidFinder")

local rq = require("Std").rquery
local perception = require("perception")
local StateMachineMachine = require("StateMachineMachine")

local soldatAiProto = {
	_configs = {},
	CurrentTarget = nil,
	SoldatModel = nil,
	SoldatTorso = nil
}

local soldatBrainMeta = { __index = soldatAiProto }

function soldatAiProto:PushStates()

	local IdleState = self:GetIdleState()

	local defaultFaceCFrame = self.SoldatTorso.CFrame
	local desiredFaceAngle = 0
	local lastTurned = 0
	local turningGyro = self.SoldatTorso.BodyGyro
	local turnWait = math.random(5,10)

	self.CurrentTarget = game.Workspace:FindFirstChild("Zombie") -- ***<--- FIX THIS TO BE DYNAMIC

	local IdleState = self:GetIdleState()
	IdleState.Action = function()
		local now = os.time()
		if now - lastTurned > turnWait then
			desiredFaceAngle = math.random(-60, 60)
			lastTurned = now
			turnWait = math.random(5,10)
		end
		local currentFaceDirection = self.SoldatTorso.CFrame.lookVector
		local angle = math.acos(currentFaceDirection:Dot(defaultFaceCFrame.lookVector))
		turningGyro.cframe = defaultFaceCFrame * CFrame.fromEulerAnglesXYZ(0, math.rad(desiredFaceAngle),0)
	end
	IdleState.Init = function()
		turningGyro.maxTorque = Vector3.new(0, 10000, 0)
	end
	
	local lastAttack = 0
	local AttackState = self.StateMachine.NewState("Attack")
	local AimTrack = self.SoldatModel.Humanoid:LoadAnimation(self.SoldatModel.Animations.Aim)
	local aiming = false
	AttackState.Action = function()
		if self.CurrentTarget then
			-- go into aiming animation
			if not aiming then
				aiming = true
				AimTrack:Play()	
			end
			
			-- face the target
			local currentTargetTorso = rq.PersonageTorsoOrEquivalent(self.CurrentTarget)
			self.SoldatModel:SetPrimaryPartCFrame(CFrame.new(
				self.SoldatTorso.Position,
				Vector3.new(currentTargetTorso.Position.X, 
					self.SoldatTorso.Position.Y, 
					currentTargetTorso.Position.Z))*CFrame.Angles(0,-math.pi/3,0))

			local now = os.time()
			if now - lastAttack > self:GetConfigValue("AttackCooldown") then
				lastAttack = now
				
				-- render shot
				local toTarget = currentTargetTorso.Position - self.SoldatTorso.Position
				local hit = -toTarget.magnitude/self:GetConfigValue("AggroRange") + 1
				if math.random() < hit then
					self.CurrentTarget.Humanoid.Health = 
						self.CurrentTarget.Humanoid.Health - self:GetConfigValue("Damage")
				else
					-- missed!
					local hOffset = toTarget:Cross(Vector3.new(0,1,0)).unit * 5 * math.random(-1, 1)
					toTarget = toTarget + hOffset
					local missRay = Ray.new(self.SoldatTorso.Position, toTarget.unit * 1000)
					local ignoreList = {}
					table.insert(ignoreList, self.SoldatModel)
					local part, position = game.Workspace:FindPartOnRayWithIgnoreList(missRay, ignoreList)
					if part then
						if part and part.Parent and part.Parent:FindFirstChild("Humanoid") and 
								part.Parent ~= "Soldier" then
							if game.Players:GetPlayerFromCharacter(part.Parent) and not self:GetConfigValue("CanDamagePlayer") then 
								return
							end
							local otherHumanoid = part.Parent:FindFirstChild("Humanoid")
							otherHumanoid.Health = otherHumanoid.Health - self:GetConfigValue("Damage")
						end
					end
				end	
				
				local shot = Instance.new("Part", game.Workspace)
				shot.FormFactor = Enum.FormFactor.Custom
				shot.Size = Vector3.new(.1,.1,toTarget.magnitude)
				shot.Anchored = true
				shot.CanCollide = false
				--shot.Position = soldier.Torso.Position + toTarget / 2
				shot.CFrame = CFrame.new(self.SoldatTorso.Position + toTarget / 2, self.SoldatTorso.Position)
				shot.BrickColor = BrickColor.Yellow()
				game.Debris:AddItem(shot, .1)
			end
		end
	end
	AttackState.Init = function()
		turningGyro.maxTorque = Vector3.new(0, 0, 0)
	end
	
	-- CONDITIONS	
	local CanSeeTarget = self.StateMachine.NewCondition()
	CanSeeTarget.Name = "CanSeeTarget"
	CanSeeTarget.Evaluate = function()
		local humanoids = HumanoidList:GetCurrent()
		local soldiers = {}
		local enemies = {}
		for _, object in pairs(humanoids) do
			if object 
				and object.Parent 
				and not game.Players:GetPlayerFromCharacter(object.Parent) and object.Health > 0
					and object.WalkSpeed > 0 and object.Parent:FindFirstChild("Torso") then
				local torso = rq.PersonageTorsoOrEquivalent(object.Parent)
				local distance = (self.SoldatTorso.Position - torso.Position).magnitude
				if distance <= self.GetConfigValue("AggroRange") then
					if object.Parent.Name == "Soldier" then
						table.insert(soldiers, object.Parent)
					else
						table.insert(enemies, object.Parent)
					end
				end			
			end
		end
		
		local target = perception:GetClosestVisibleTarget(
			self.SoldatModel, enemies, soldiers, self:GetConfigValue("FieldOfView"))
		if target then
			self.CurrentTarget = target
			return true
		end
		return false
	end
	CanSeeTarget.TransitionState = AttackState
	
	local TargetDead = self.StateMachine.NewCondition()
	TargetDead.Name = "TargetDead"
	TargetDead.Evaluate = function()
		if self.CurrentTarget and self.CurrentTarget:FindFirstChild("Humanoid") then
			return self.CurrentTarget.Humanoid.Health <= 0
		end
		return true
	end
	TargetDead.TransitionState = IdleState	
	
	
	table.insert(IdleState.Conditions, CanSeeTarget)
	table.insert(AttackState.Conditions, TargetDead)	
	
	self.StateMachine.SwitchState(IdleState)
	
	self.Stop = function()
		self.StateMachine.SwitchState(nil)
	end
	
end

function soldatAiProto:LoadConfigsFromModel(soldatModel)
	local configTable = soldatModel:FindFirstChild("Configurations")
	self:LoadConfig(configTable, "AggroRange", 100)
	self:LoadConfig(configTable, "FieldOfView", 180)
	self:LoadConfig(configTable, "Damage", 50)
	self:LoadConfig(configTable, "AttackCooldown", 1)
	self:LoadConfig(configTable, "CanDamagePlayer", false)
end

-- ***************************************
-- ***************************************
-- {48bfcbf7-e9c5-488b-96ab-9158850d2055}
-- Create metatable for this
-- ***************************************
-- ***************************************
function soldatAiProto:Init(soldatModel)
	self:LoadConfigsFromModel(soldatModel)
	self.SoldatModel = soldatModel
	self.SoldatTorso = rq.PersonageTorsoOrEquivalent(self.SoldatModel)
	self.StateMachine = StateMachineMachine.NewStateMachine()
	self:PushStates()
end


local soldatAiModule = {}

function soldatAiModule.new(soldatModel, targetNames)
	local aiInstanceMeta = setmetatable({}, soldatBrainMeta)
	local aiInstance = setmetatable(aiInstanceMeta, aiBase.new())

	aiInstance:Init(soldatModel)

	return aiInstance
end

return soldatAiModule