--	local AttackRange, FieldOfView, AggroRange, ChanceOfBoredom, BoredomDuration, 
--		Damage, DamageCooldown

	local configTable = model.Configurations
	local configs = {}
	local function loadConfig(configName, defaultValue)
		if configTable:FindFirstChild(configName) then
			configs[configName] = configTable:FindFirstChild(configName).Value
		else
			configs[configName] = defaultValue
		end
	end

	loadConfig("AttackRange", 3)
	loadConfig("FieldOfView", 180)
	loadConfig("AggroRange", 200)
	loadConfig("ChanceOfBoredom", .5)
	loadConfig("BoredomDuration", 10)
	loadConfig("Damage", 10)
	loadConfig("DamageCooldown", 1)
	
	local StateMachine = require(game.ServerStorage.ROBLOX_StateMachine).new()
	local PathLib = require(game.ServerStorage.ROBLOX_PathfindingLibrary).new()
	local ZombieTarget = nil	
	local ZombieTargetLastLocation = nil
	
	local lastBored = os.time()	
	
	-- STATE DEFINITIONS	
	
	-- IdleState: NPC stays still. Refreshes bored timer when started to
	-- allow for random state change
	local IdleState = StateMachine.NewState()
	IdleState.Name = "Idle"
	IdleState.Action = function()
	end
	IdleState.Init = function()
		lastBored = os.time()
	end	
	
	-- SearchState: NPC wanders randomly increasing chance of spotting
	-- enemy. Refreshed bored timer when started to allow for random state
	-- change
	local SearchState = StateMachine.NewState()
	SearchState.Name = "Search"
	local lastmoved = os.time()
	local searchTarget = nil
	SearchState.Action = function()
		-- move to random spot nearby
		if model then
			if model:FindFirstChild("HumanoidRootPart") then
				local now = os.time()
				if now - lastmoved > 2 then
					lastmoved = now
					local xoff = math.random(5, 10)
					if math.random() > .5 then
						xoff = xoff * -1
					end
					local zoff = math.random(5, 10)
					if math.random() > .5 then
						zoff = zoff * -1
					end
					
					local testtarg = AIUtilities:FindCloseEmptySpace(model)
					--if testtarg then print(testtarg) else print("could not find") end
					searchTarget = Vector3.new(model.HumanoidRootPart.Position.X + xoff,model.HumanoidRootPart.Position.Y,model.HumanoidRootPart.Position.Z + zoff)
					--local target = Vector3.new(model.HumanoidRootPart.Position.X + xoff,model.HumanoidRootPart.Position.Y,model.HumanoidRootPart.Position.Z + zoff)
					--model.Humanoid:MoveTo(target)
					searchTarget = testtarg
				end
				--PathLib:MoveToTarget(model, searchTarget) --Zombie will fall off path when searching 
			end
		end
	end
	SearchState.Init = function()
		lastBored = os.time()
	end
	
	-- PursueState: Enemy has been spotted, need to give chase.
	local PursueState = StateMachine.NewState()
	PursueState.Name = "Pursue"
	PursueState.Action = function()
		-- Double check we still have target
		if ZombieTarget then
			if model:FindFirstChild("HumanoidRootPart") then
				if ZombieTarget:FindFirstChild("HumanoidRootPart") then 
					-- Get distance to target
					local distance = (model.HumanoidRootPart.Position - ZombieTarget.HumanoidRootPart.Position).magnitude
					-- If we're far from target use pathfinding to move. Otherwise just MoveTo
					if distance > configs["AttackRange"] + 5 then
						PathLib:MoveToTarget(model, ZombieTarget.HumanoidRootPart.Position)
					else
						model.Humanoid:MoveTo(ZombieTarget.HumanoidRootPart.Position)
		--				if ZombieTarget.HumanoidRootPart.Position.Y > model.HumanoidRootPart.Position.Y + 2 then
		--					model.Humanoid.Jump = true
		--				end
					end
				end
			end
		end
	end
	PursueState.Init = function()
	end
	
	-- AttackState: Keep moving towards target and play attack animation.
	local AttackState = StateMachine.NewState()
	AttackState.Name = "Attack"
	local lastAttack = os.time()
	local attackTrack = model.Humanoid:LoadAnimation(model.Animations.Attack)
	AttackState.Action = function()
		model.Humanoid:MoveTo(ZombieTarget.HumanoidRootPart.Position)
		local now = os.time()
		if now - lastAttack > 3 then
			lastAttack = now
			attackTrack:Play()
		end
	end
	
	-- HuntState: Can't see target but NPC will move to target's last known location.
	-- Will eventually get bored and switch state.
	local HuntState = StateMachine.NewState()
	HuntState.Name = "Hunt"
	HuntState.Action = function()
		if ZombieTargetLastLocation then
			PathLib:MoveToTarget(model, ZombieTargetLastLocation)
		end
	end
	HuntState.Init = function()
		lastBored = os.time() + configs["BoredomDuration"] / 2
	end
	
	-- CONDITION DEFINITIONS	
	
	-- CanSeeTarget: Determines if a target is visible. Returns true if target is visible and
	-- sets current target. A target is valid if it is nearby, visible, has a HumanoidRootPart and WalkSpeed
	-- greater than 0 (this is to ignore inanimate objects that happen to use humanoids)
	local CanSeeTarget = StateMachine.NewCondition()
	CanSeeTarget.Name = "CanSeeTarget"
	CanSeeTarget.Evaluate = function()
		if model then
			-- Get list of all nearby Zombies and non-Zombie humanoids
			-- Zombie list is used to ignore zombies during later raycast
			local humanoids = HumanoidList:GetCurrent()	
			local zombies = {}
			local characters = {}
			for _, object in pairs(humanoids) do
				if object and object.Parent and object.Parent:FindFirstChild("HumanoidRootPart") and object.Health > 0 and object.WalkSpeed > 0 then				
					local HumanoidRootPart = object.Parent:FindFirstChild("HumanoidRootPart")
					if HumanoidRootPart and model:FindFirstChild("HumanoidRootPart") then
						local distance = (model.HumanoidRootPart.Position - HumanoidRootPart.Position).magnitude
						if distance <= configs["AggroRange"] then
							if object.Parent.Name == "Zombie" then
								table.insert(zombies, object.Parent)
							else
								table.insert(characters, object.Parent)
							end
						end	
					end	
				end
			end	
			
			local target = AIUtilities:GetClosestVisibleTarget(model, characters, zombies, configs["FieldOfView"])
			if target then
				ZombieTarget = target
				return true
			end