local I=game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")local A=require(I:WaitForChild("\078\101\118\101\114\109\111\114\101"))local e=A("\112\097\116\104\102\105\110\100\101\114")local X=A("\083\116\097\116\101\077\097\099\104\105\110\101\077\097\099\104\105\110\101")local a={_configs={};StateMachine=nil,MAX_FORCE=75,Personage=nil,CancelPathRequested=false}local S={__index=a}function a.RequestPathCancellation(I)I.CancelPathRequested=true end function a.GetOnWaypointReachedDelegate(A,I)local e=function(e)local X=I.CurrentWaypointIndex local a=I.Waypoints if a~=nil then local S=a[X]if S~=nil then if S.Position~=nil then if e and X<#a then if not A.CancelPathRequested then I.CurrentWaypointIndex=X+1;(A.Personage:FindFirstChild("\072\117\109\097\110\111\105\100")):MoveTo(S.Position)end end end end end end return e end function a.GetOnPathBlockedDelegate(X,I,A,e)local a=function(a)if a>I.CurrentWaypointIndex then I.PathBlockedEventConnection:Disconnect()I.Path:destroy()if not X.CancelPathRequested then wait(.2)X:MoveTo(A,e)end end end return a end function a.MoveTo(X,I,A)X.CancelPathRequested=false local a=e.GetPathForPersonage(X.Personage,I)local S=a.Path.Blocked:Connect(X:GetOnPathBlockedDelegate(a,I,A))a.PathBlockedEventConnection=S if a~=nil and not X.CancelPathRequested then if A then e.DisplayPathWaypoints(a)end a=e.MovePersonageOnPath(X.Personage,a,X:GetOnWaypointReachedDelegate(a))end return a end function a.GetRepulsionVector(X,I,A,e)if e==nil or e==0 then e=X.MAX_FORCE end local a=Vector3.new(0,0,0)local S=0 for A,e in pairs(A)do local X=I-e X=(X.unit*1000)/math.pow(X.magnitude+1,2)a=a+X end return a*e end function a.GetNewState(A,I)return A.StateMachine.NewState(I)end function a.GetIdleState(I)local A=I.StateMachine.NewState("\073\100\108\101")A.Action=function() end A.Init=function() end return A end function a.LoadConfig(X,I,A,e)if I:FindFirstChild(A)then X._configs[A]=(I:FindFirstChild(A)).Value else X._configs[A]=e end end function a.GetConfigValue(A,I)if A._configs[I]~=nil then return A._configs[I]else warn("\078\111 \099\111\110\102\105\103 \102\111\117\110\100 \102\111\114 \107\101\121\058 "..I)return nil end end local J={}function J.new(I)local A=setmetatable({},S)A.StateMachine=X.NewStateMachine()A.Personage=I return A end return J
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local require = require(ReplicatedStorage:WaitForChild("Nevermore"))
local pathfinder = require("pathfinder")
local StateMachineMachine = require("StateMachineMachine")

local pathfindingAiProto = {
	_configs = {},
	StateMachine = nil,
	MAX_FORCE =  75,
	Personage = nil,
	CancelPathRequested = false
}

local pathfindingAiMeta = { __index = pathfindingAiProto }

function pathfindingAiProto:RequestPathCancellation()
	self.CancelPathRequested = true
end

function pathfindingAiProto:GetOnWaypointReachedDelegate(pathProgressData)
	local delegateHandler = function(reached)

		local currentWaypointIndex = pathProgressData.CurrentWaypointIndex
		local waypoints = pathProgressData.Waypoints
		if waypoints ~= nil then

			local movingTo = waypoints[currentWaypointIndex]

			if movingTo ~= nil then
				if movingTo["Position"] ~= nil then
					--print("MOVING TO " .. tostring(waypoints[movingTo].Position))		
					if reached and currentWaypointIndex < #waypoints then
						if not self.CancelPathRequested then
							pathProgressData.CurrentWaypointIndex = currentWaypointIndex + 1
							self.Personage:FindFirstChild("Humanoid"):MoveTo(
								movingTo.Position)
						end
					end
				end
			end
		end
	end
	return delegateHandler
end

function pathfindingAiProto:GetOnPathBlockedDelegate(pathProgressData, destinationPart, displayWaypointMarkers)
	local delegateHandler = function (blockedWaypointIndex)
		if blockedWaypointIndex > pathProgressData.CurrentWaypointIndex then
			pathProgressData.PathBlockedEventConnection:Disconnect()
			pathProgressData.Path:destroy()
			if not self.CancelPathRequested then
				wait(0.2)
				self:MoveTo( destinationPart, displayWaypointMarkers )
			end
		end
	end

	return delegateHandler
end

function pathfindingAiProto:MoveTo( destinationPart, displayWaypointMarkers )
	self.CancelPathRequested = false
	local pathProgressData = pathfinder.GetPathForPersonage(
		self.Personage, destinationPart)
	
	local pathBlockedEventConnection = pathProgressData.Path.Blocked:Connect(
			self:GetOnPathBlockedDelegate(
				pathProgressData,
				destinationPart, 
				displayWaypointMarkers)
			)
	pathProgressData.PathBlockedEventConnection = pathBlockedEventConnection

	if pathProgressData ~= nil and not self.CancelPathRequested then
		if displayWaypointMarkers then
			pathfinder.DisplayPathWaypoints(pathProgressData)
		end
		pathProgressData = pathfinder.MovePersonageOnPath(self.Personage,
			pathProgressData,
			self:GetOnWaypointReachedDelegate(pathProgressData))
	end
	return pathProgressData
end

function pathfindingAiProto:GetRepulsionVector(unitPosition, otherUnitsPositions, maxForce)
    if maxForce == nil or maxForce == 0 then
		maxForce = self.MAX_FORCE
    end

    local repulsionVector = Vector3.new(0,0,0)
	local count = 0
	for _, other in pairs(otherUnitsPositions) do
		local fromOther = unitPosition - other 
		--fromOther = fromOther.unit * ((-maxForce / 5) * math.pow(fromOther.magnitude,2) + maxForce)
		fromOther = fromOther.unit * 1000 / math.pow((fromOther.magnitude + 1), 2)
		repulsionVector = repulsionVector + fromOther
	end
	return repulsionVector * maxForce
end


function pathfindingAiProto:GetNewState(stateName)
	return self.StateMachine.NewState(stateName)
end

function pathfindingAiProto:GetIdleState()
	local idleState = self.StateMachine.NewState("Idle")
	idleState.Action = function() end
	idleState.Init = function() end
	return idleState
end

function pathfindingAiProto:LoadConfig(configSource, configName, defaultValue)
	if configSource:FindFirstChild(configName) then
		self._configs[configName] = configSource:FindFirstChild(configName).Value
	else
		self._configs[configName] = defaultValue
	end
end

function pathfindingAiProto:GetConfigValue(configName)
	if self._configs[configName] ~= nil then
		return self._configs[configName]
	else
		warn("No config found for key: " ..  configName)
		return nil
	end
end

local aiBaseModule = {}

function aiBaseModule.new(personage)
	local npcAiInstance = setmetatable({}, pathfindingAiMeta)
	npcAiInstance.StateMachine = StateMachineMachine.NewStateMachine()
	npcAiInstance.Personage = personage
    return npcAiInstance
end

return aiBaseModule