local J={}function J.GetCFrame(J)local q=Self:FindFirstChild("\072\117\109\097\110\111\105\100\082\111\111\116\080\097\114\116")if q~=nil and q:IsA("\066\097\115\101\080\097\114\116")then return q.CFrame else return CFrame.new()end end function J.GetMaximumDetectionDistance(J)local q=Settings.MaximumDetectionDistance.Value if q<0 then return math.huge else return q end end function J.SearchForTarget(q)local Y=Info.Players:GetPlayers()local d,Q for q=1,#Y,1 do local G=Y[q]if G.Neutral or G.TeamColor~=Settings.FriendlyTeam.Value then local q=G.Character if q~=nil and(q:FindFirstChild("\072\117\109\097\110\111\105\100")~=nil and q.Humanoid:IsA("\072\117\109\097\110\111\105\100"))then local Y=G:DistanceFromCharacter((J:GetCFrame()).p)if Y<J:GetMaximumDetectionDistance()then if d==nil then d,Q=q,Y else if Q>Y then d,Q=q,Y end end end end end end if d~=nil then Mind.CurrentTargetHumanoid.Value=d.Humanoid end end function J.TryRecomputePath(q)if Data.AutoRecompute or tick()-Data.LastRecomputePath>1/Info.RecomputePathFrequency then J:RecomputePath()end end function J.GetTargetCFrame(q)local Y=Mind.CurrentTargetHumanoid.Value if J:TargetIsValid()then return Y.Torso.CFrame else return CFrame.new()end end function J.IsAlive(J)return Self.Humanoid.Health>0 and Self.Humanoid.Torso~=nil end function J.TargetIsValid(J)local q=Mind.CurrentTargetHumanoid.Value if q~=nil and(q:IsA("\072\117\109\097\110\111\105\100")and(q.Torso~=nil and q.Torso:IsA("\066\097\115\101\080\097\114\116")))then return true else return false end end function J.HasClearLineOfSight(q)local Y,d=(J:GetCFrame()).p,(J:GetTargetCFrame()).p local Q,G=Workspace:FindPartOnRayWithIgnoreList(Ray.new(Y,d-Y),{Self,Mind.CurrentTargetHumanoid.Value.Parent})if Q==nil then return true else return false end end function J.RecomputePath(q)if not Data.Recomputing then if J:IsAlive()and J:TargetIsValid()then if J:HasClearLineOfSight()then Data.AutoRecompute=true Data.PathCoords={(J:GetCFrame()).p;(J:GetTargetCFrame()).p}Data.LastRecomputePath=tick()Data.CurrentNode=nil Data.CurrentNodeIndex=2 else Data.Recomputing=true Data.AutoRecompute=false local q=Info.PathfindingService:ComputeSmoothPathAsync((J:GetCFrame()).p,(J:GetTargetCFrame()).p,500)Data.PathCoords=q:GetPointCoordinates()Data.Recomputing=false Data.LastRecomputePath=tick()Data.CurrentNode=nil Data.CurrentNodeIndex=1 end end end end function J.Update(q)J:ReevaluateTarget()J:SearchForTarget()J:TryRecomputePath()J:TravelPath()end function J.TravelPath(q)local Y,d,Q local G=(J:GetCFrame()).p local P=Data.CurrentNode~=nil and(Data.CurrentNode-G).magnitude<3 for J=Data.CurrentNodeIndex,#Data.PathCoords,1 do local q=Data.PathCoords[J]if not(P and q==Data.CurrentNode)then local P=(q-G).magnitude if Y==nil then Y,d,Q=q,P,J else if P<d then Y,d,Q=q,P,J else break end end end end if Y~=nil then Data.CurrentNode=Y Data.CurrentNodeIndex=Q local q=Self:FindFirstChild("\072\117\109\097\110\111\105\100")if q~=nil and q:IsA("\072\117\109\097\110\111\105\100")then q:MoveTo(Y)end if J:IsAlive()and J:TargetIsValid()then J:TryJumpCheck()J:TryAttack()end if Q==#Data.PathCoords then Data.AutoRecompute=true end end end function J.TryJumpCheck(q)if tick()-Data.LastJumpCheck>1/Info.JumpCheckFrequency then J:JumpCheck()end end function J.TryAttack(q)if tick()-Data.LastAttack>1/Settings.AttackFrequency.Value then J:Attack()end end function J.Attack(q)local Y,d=(J:GetCFrame()).p,(J:GetTargetCFrame()).p if(Y-d).magnitude<=Settings.AttackRange.Value then Mind.CurrentTargetHumanoid.Value:TakeDamage(Settings.AttackDamage.Value)Data.LastAttack=tick()Data.AttackTrack:Play()end end function J.JumpCheck(q)local Y=J:GetCFrame()local d=((J:GetTargetCFrame()).p-Y.p).unit*2 local Q,G=Workspace:FindPartOnRay(Ray.new(Y.p+Vector3.new(0,-2.4,0),d),Self)if Q~=nil and not Q:IsDescendantOf(Mind.CurrentTargetHumanoid.Value.Parent)then local J,q=Workspace:FindPartOnRay(Ray.new(Y.p+Vector3.new(0,-2.3,0),d),Self)if J==Q then if((q-G)*Vector3.new(1,0,1)).magnitude<.05 then Self.Humanoid.Jump=true end end end Data.LastJumpCheck=tick()end function J.Connect(q)Mind.CurrentTargetHumanoid.Changed:connect(function(q)if q~=nil then assert(q:IsA("\072\117\109\097\110\111\105\100"),"\077\111\110\115\116\101\114 \116\097\114\103\101\116 \109\117\115\116 \098\101 \097 \104\117\109\097\110\111\105\100")J:RecomputePath()end end)Self.Respawn.OnInvoke=function(q)J:Respawn(q)end end function J.Initialize(q)J:Connect()if Settings.AutoDetectSpawnPoint.Value then Settings.SpawnPoint.Value=(J:GetCFrame()).p end end function J.Respawn(Y,q)local d=q or Settings.SpawnPoint.Value for J,q in next,(Data.BaseMonster:Clone()):GetChildren()do if q.Name=="\067\111\110\102\105\103\117\114\097\116\105\111\110\115"or q.Name=="\077\105\110\100"or q.Name=="\082\101\115\112\097\119\110\101\100"or q.Name=="\068\105\101\100"or q.Name=="\077\111\110\115\116\101\114\083\099\114\105\112\116"or q.Name=="\082\101\115\112\097\119\110"then q:Destroy()else Self[q.Name]:Destroy()q.Parent=Self end end J:InitializeUnique()Self.Parent=Workspace Self.HumanoidRootPart.CFrame=CFrame.new(d)Settings.SpawnPoint.Value=d Self.Respawned:Fire()end function J.InitializeUnique(J)Data.AttackTrack=Self.Humanoid:LoadAnimation(script.Attack)end function J.ReevaluateTarget(q)local Y=Mind.CurrentTargetHumanoid.Value if Y~=nil and Y:IsA("\072\117\109\097\110\111\105\100")then local q=Y.Parent if q~=nil then local J=Info.Players:GetPlayerFromCharacter(q)if J~=nil then if not J.Neutral and J.TeamColor==Settings.FriendlyTeam.Value then Mind.CurrentTargetHumanoid.Value=nil end end end if Y==Mind.CurrentTargetHumanoid.Value then local q=Y.Torso if q~=nil and q:IsA("\066\097\115\101\080\097\114\116")then if Settings.CanGiveUp.Value and(q.Position-(J:GetCFrame()).p).magnitude>J:GetMaximumDetectionDistance()then Mind.CurrentTargetHumanoid.Value=nil end end end end end

local Monster = {} -- Create the monster class


function Monster:GetCFrame()
	-- Returns the CFrame of the monster's humanoidrootpart

	local humanoidRootPart = Self:FindFirstChild('HumanoidRootPart')

	if humanoidRootPart ~= nil and humanoidRootPart:IsA('BasePart') then
		return humanoidRootPart.CFrame
	else
		return CFrame.new()
	end
end

function Monster:GetMaximumDetectionDistance()
	-- Returns the maximum detection distance
	
	local setting = Settings.MaximumDetectionDistance.Value

	if setting < 0 then
		return math.huge
	else
		return setting
	end
end

function Monster:SearchForTarget()
	-- Finds the closest player and sets the target

	local players = Info.Players:GetPlayers()
	local closestCharacter, closestCharacterDistance

	for i=1, #players do
		local player = players[i]
		
		if player.Neutral or player.TeamColor ~= Settings.FriendlyTeam.Value then
			local character = player.Character
	
			if character ~= nil and character:FindFirstChild('Humanoid') ~= nil and character.Humanoid:IsA('Humanoid') then
				local distance = player:DistanceFromCharacter(Monster:GetCFrame().p)
	
				if distance < Monster:GetMaximumDetectionDistance() then
					if closestCharacter == nil then
						closestCharacter, closestCharacterDistance = character, distance
					else
						if closestCharacterDistance > distance then
							closestCharacter, closestCharacterDistance = character, distance
						end
					end
				end
			end
		end
	end


	if closestCharacter ~= nil then
		Mind.CurrentTargetHumanoid.Value = closestCharacter.Humanoid
	end
end

function Monster:TryRecomputePath()
	if Data.AutoRecompute or tick() - Data.LastRecomputePath > 1/Info.RecomputePathFrequency then
		Monster:RecomputePath()
	end
end

function Monster:GetTargetCFrame()
	local targetHumanoid = Mind.CurrentTargetHumanoid.Value
	
	if Monster:TargetIsValid() then
		return targetHumanoid.Torso.CFrame
	else
		return CFrame.new()
	end
end

function Monster:IsAlive()
	return Self.Humanoid.Health > 0 and Self.Humanoid.Torso ~= nil
end

function Monster:TargetIsValid()
	local targetHumanoid = Mind.CurrentTargetHumanoid.Value
	
	if targetHumanoid ~= nil and targetHumanoid:IsA 'Humanoid' and targetHumanoid.Torso ~= nil and targetHumanoid.Torso:IsA 'BasePart' then
		return true
	else
		return false
	end
end

function Monster:HasClearLineOfSight()
	-- Going to cast a ray to see if I can just see my target
	local myPos, targetPos = Monster:GetCFrame().p, Monster:GetTargetCFrame().p
	
	local hit, pos = Workspace:FindPartOnRayWithIgnoreList(
		Ray.new(
			myPos,
			targetPos - myPos
		),
		{
			Self,
			Mind.CurrentTargetHumanoid.Value.Parent
		}
	)
	
	
	if hit == nil then
		return true
	else
		return false
	end
end

function Monster:RecomputePath()
	if not Data.Recomputing then
		if Monster:IsAlive() and Monster:TargetIsValid() then
			if Monster:HasClearLineOfSight() then
				Data.AutoRecompute = true
				Data.PathCoords = {
					Monster:GetCFrame().p,
					Monster:GetTargetCFrame().p
				}
				
				Data.LastRecomputePath = tick()
				Data.CurrentNode = nil
				Data.CurrentNodeIndex = 2 -- Starts chasing the target without evaluating its current position
			else
				-- Do pathfinding since you can't walk straight
				Data.Recomputing = true -- Basically a debounce.
				Data.AutoRecompute = false
				
				
				local path = Info.PathfindingService:ComputeSmoothPathAsync(
					Monster:GetCFrame().p,
					Monster:GetTargetCFrame().p,
					500
				)
				Data.PathCoords = path:GetPointCoordinates()
				
				
				Data.Recomputing = false
				Data.LastRecomputePath = tick()
				Data.CurrentNode = nil
				Data.CurrentNodeIndex = 1
			end
		end
	end
end

function Monster:Update()
	Monster:ReevaluateTarget()
	Monster:SearchForTarget()
	Monster:TryRecomputePath()
	Monster:TravelPath()
end

function Monster:TravelPath()
	local closest, closestDistance, closestIndex
	local myPosition = Monster:GetCFrame().p
	local skipCurrentNode = Data.CurrentNode ~= nil and (Data.CurrentNode - myPosition).magnitude < 3
	
	for i=Data.CurrentNodeIndex, #Data.PathCoords do
		local coord = Data.PathCoords[i]
		if not (skipCurrentNode and coord == Data.CurrentNode) then
			local distance = (coord - myPosition).magnitude
			
			if closest == nil then
				closest, closestDistance, closestIndex = coord, distance, i
			else
				if distance < closestDistance then
					closest, closestDistance, closestIndex = coord, distance, i
				else
					break
				end
			end
		end
	end
	
	
	--
	if closest ~= nil then
		Data.CurrentNode = closest
		Data.CurrentNodeIndex = closestIndex
		
		local humanoid = Self:FindFirstChild 'Humanoid'
		
		if humanoid ~= nil and humanoid:IsA'Humanoid' then
			humanoid:MoveTo(closest)
		end
		
		if Monster:IsAlive() and Monster:TargetIsValid() then
			Monster:TryJumpCheck()
			Monster:TryAttack()
		end
		
		if closestIndex == #Data.PathCoords then
			-- Reached the end of the path, force a new check
			Data.AutoRecompute = true
		end
	end
end

function Monster:TryJumpCheck()
	if tick() - Data.LastJumpCheck > 1/Info.JumpCheckFrequency then
		Monster:JumpCheck()
	end
end

function Monster:TryAttack()
	if tick() - Data.LastAttack > 1/Settings.AttackFrequency.Value then
		Monster:Attack()
	end
end

function Monster:Attack()
	local myPos, targetPos = Monster:GetCFrame().p, Monster:GetTargetCFrame().p
	
	if (myPos - targetPos).magnitude <= Settings.AttackRange.Value then
		Mind.CurrentTargetHumanoid.Value:TakeDamage(Settings.AttackDamage.Value)
		Data.LastAttack = tick()
		Data.AttackTrack:Play()
	end
end

function Monster:JumpCheck()
	-- Do a raycast to check if we need to jump
	local myCFrame = Monster:GetCFrame()
	local checkVector = (Monster:GetTargetCFrame().p - myCFrame.p).unit*2
	
	local hit, pos = Workspace:FindPartOnRay(
		Ray.new(
			myCFrame.p + Vector3.new(0, -2.4, 0),
			checkVector
		),
		Self
	)
	
	if hit ~= nil and not hit:IsDescendantOf(Mind.CurrentTargetHumanoid.Value.Parent) then
		-- Do a slope check to make sure we're not walking up a ramp
		
		local hit2, pos2 = Workspace:FindPartOnRay(
			Ray.new(
				myCFrame.p + Vector3.new(0, -2.3, 0),
				checkVector
			),
			Self
		)
		
		if hit2 == hit then
			if ((pos2 - pos)*Vector3.new(1,0,1)).magnitude < 0.05 then -- Will pass for any ramp with <2 slope
				Self.Humanoid.Jump = true
			end
		end
	end
	
	Data.LastJumpCheck = tick()
end

function Monster:Connect()
	Mind.CurrentTargetHumanoid.Changed:connect(function(humanoid)
		if humanoid ~= nil then
			assert(humanoid:IsA'Humanoid', 'Monster target must be a humanoid')
			
			Monster:RecomputePath()
		end
	end)
	
	Self.Respawn.OnInvoke = function(point)
		Monster:Respawn(point)
	end
end

function Monster:Initialize()
	Monster:Connect()
	
	if Settings.AutoDetectSpawnPoint.Value then
		Settings.SpawnPoint.Value = Monster:GetCFrame().p
	end
end

function Monster:Respawn(point)
	local point = point or Settings.SpawnPoint.Value
	
	for index, obj in next, Data.BaseMonster:Clone():GetChildren() do
		if obj.Name == 'Configurations' or obj.Name == 'Mind' or obj.Name == 'Respawned' or obj.Name == 'Died' or obj.Name == 'MonsterScript' or obj.Name == 'Respawn' then
			obj:Destroy()
		else
			Self[obj.Name]:Destroy()
			obj.Parent = Self
		end
	end
	
	Monster:InitializeUnique()
	
	Self.Parent = Workspace
	
	Self.HumanoidRootPart.CFrame = CFrame.new(point)
	Settings.SpawnPoint.Value = point
	Self.Respawned:Fire()
end

function Monster:InitializeUnique()
	Data.AttackTrack = Self.Humanoid:LoadAnimation(script.Attack)
end

function Monster:ReevaluateTarget()
	local currentTarget = Mind.CurrentTargetHumanoid.Value
	
	if currentTarget ~= nil and currentTarget:IsA'Humanoid' then
		local character = currentTarget.Parent
		
		if character ~= nil then
			local player = Info.Players:GetPlayerFromCharacter(character)
			
			if player ~= nil then
				if not player.Neutral and player.TeamColor == Settings.FriendlyTeam.Value then
					Mind.CurrentTargetHumanoid.Value = nil
				end
			end
		end
		
		
		if currentTarget == Mind.CurrentTargetHumanoid.Value then
			local torso = currentTarget.Torso
			
			if torso ~= nil and torso:IsA 'BasePart' then
				if Settings.CanGiveUp.Value and (torso.Position - Monster:GetCFrame().p).magnitude > Monster:GetMaximumDetectionDistance() then
					Mind.CurrentTargetHumanoid.Value = nil
				end
			end
		end
	end
end
