using System; using System.Collections.Generic; using Goap.Agent.Core; using Goap.Agent.Runtime; using Goap.Core; using UnityEngine; namespace Goap.Runtime { public class GoapActionProvider : ActionProviderBase, IMonoGoapActionProvider { public LoggerConfig LoggerConfig { get; set; } = new(); public AgentTypeBehaviour AgentTypeBehaviour { get; set; } public float DistanceMultiplier { get; set; } = 1f; private IAgentType agentType; public IAgentType AgentType { get => this.agentType; set { if (this.agentType != null) this.agentType.Unregister(this); this.agentType = value; this.WorldData.SetParent(value.WorldData); this.GoalRequest = null; this.CurrentPlan = null; value.Register(this); this.Events.Bind(this, value.Events); } } public IGoalResult CurrentPlan { get; private set; } = new GoalResult(); public IGoalRequest GoalRequest { get; private set; } private IGoalRequest requestCache = new GoalRequest(); public ILocalWorldData WorldData { get; } = new LocalWorldData(); public IGoapAgentEvents Events { get; } = new GoapAgentEvents(); public ILogger Logger { get; } = new GoapAgentLogger(); public Vector3 Position => this.transform.position; private void Awake() { if (this.AgentTypeBehaviour != null) this.AgentType = this.AgentTypeBehaviour.AgentType; this.Logger.Initialize(this.LoggerConfig, this); } private void Start() { if (this.AgentType == null) throw new GoapException( $"There is no AgentType assigned to the agent '{this.name}'! Please assign one in the inspector or through code in the Awake method."); } private void OnEnable() { if (this.AgentType == null) return; this.AgentType.Register(this); if (this.GoalRequest != null) this.ResolveAction(); } private void OnDisable() { if (this.AgentType == null) return; this.AgentType.Unregister(this); this.Events.Unbind(); } [Obsolete("Use RequestGoal instead.")] public void SetGoal(bool endAction = false) where TGoal : IGoal => this.RequestGoal(endAction); [Obsolete("Use RequestGoal instead.")] public void SetGoal(IGoal goal, bool endAction = false) => this.RequestGoal(goal, endAction); private IGoalRequest GetRequestCache() { if (this.requestCache == null) this.requestCache = new GoalRequest(); this.requestCache.Goals.Clear(); this.requestCache.Key = string.Empty; return this.requestCache; } /// /// Request goals of the specified types. /// /// /// public void RequestGoal(Type[] goalTypes, bool resolve = true) { this.ValidateSetup(); var request = this.GetRequestCache(); request.Goals.Clear(); foreach (var goalType in goalTypes) { request.Goals.Add(this.AgentType.ResolveGoal(goalType)); } this.RequestGoal(request, resolve); } /// /// Request goals of the specified type. /// /// /// public void RequestGoal(Type _goalType, bool _resolve = true) { this.ValidateSetup(); var tmp_Request = this.GetRequestCache(); tmp_Request.Goals.Clear(); tmp_Request.Goals.Add(this.AgentType.ResolveGoal(_goalType)); this.RequestGoal(tmp_Request, _resolve); } /// /// Requests a goal of type TGoal. /// /// The type of the goal. /// Whether to resolve the action after requesting the goal. Defaults to true. public void RequestGoal(bool resolve = true) where TGoal : IGoal { this.RequestGoal(typeof(TGoal), resolve); } public void RequestGoal(bool resolve = true) where TGoal1 : IGoal where TGoal2 : IGoal { this.ValidateSetup(); var request = this.GetRequestCache(); request.Goals.Clear(); request.Goals.Add(this.AgentType.ResolveGoal()); request.Goals.Add(this.AgentType.ResolveGoal()); this.RequestGoal(request, resolve); } internal void RequestGoal(Type _goal1Type, Type _goal2Type, bool resolve = true) { this.ValidateSetup(); var request = this.GetRequestCache(); request.Goals.Clear(); request.Goals.Add(this.AgentType.ResolveGoal(_goal1Type) as IGoal); request.Goals.Add(this.AgentType.ResolveGoal(_goal2Type) as IGoal); this.RequestGoal(request, resolve); } /// /// Requests three goals of types TGoal1, TGoal2, and TGoal3. /// /// The type of the first goal. /// The type of the second goal. /// The type of the third goal. /// Whether to resolve the action after requesting the goals. Defaults to true. public void RequestGoal(bool resolve = true) where TGoal1 : IGoal where TGoal2 : IGoal where TGoal3 : IGoal { this.ValidateSetup(); var request = this.GetRequestCache(); request.Goals.Clear(); request.Goals.Add(this.AgentType.ResolveGoal()); request.Goals.Add(this.AgentType.ResolveGoal()); request.Goals.Add(this.AgentType.ResolveGoal()); this.RequestGoal(request, resolve); } internal void RequestGoal(Type _goal1, Type _goal2, Type _goal3, bool _resolve = true) { this.ValidateSetup(); var request = this.GetRequestCache(); request.Goals.Clear(); request.Goals.Add(this.AgentType.ResolveGoal(_goal1) as IGoal); request.Goals.Add(this.AgentType.ResolveGoal(_goal2) as IGoal); request.Goals.Add(this.AgentType.ResolveGoal(_goal3) as IGoal); this.RequestGoal(request, _resolve); } /// /// Requests four goals of types TGoal1, TGoal2, TGoal3, and TGoal4. /// /// The type of the first goal. /// The type of the second goal. /// The type of the third goal. /// The type of the fourth goal. /// Whether to resolve the action after requesting the goals. Defaults to true. public void RequestGoal(bool resolve = true) where TGoal1 : IGoal where TGoal2 : IGoal where TGoal3 : IGoal where TGoal4 : IGoal { this.ValidateSetup(); var request = this.GetRequestCache(); request.Goals.Clear(); request.Goals.Add(this.AgentType.ResolveGoal()); request.Goals.Add(this.AgentType.ResolveGoal()); request.Goals.Add(this.AgentType.ResolveGoal()); request.Goals.Add(this.AgentType.ResolveGoal()); this.RequestGoal(request, resolve); } internal void RequestGoal(Type _goal1, Type _goal2, Type _goal3, Type _goal4, bool _resolve = true) { this.ValidateSetup(); var request = this.GetRequestCache(); request.Goals.Clear(); request.Goals.Add(this.AgentType.ResolveGoal(_goal1) as IGoal); request.Goals.Add(this.AgentType.ResolveGoal(_goal2) as IGoal); request.Goals.Add(this.AgentType.ResolveGoal(_goal3) as IGoal); request.Goals.Add(this.AgentType.ResolveGoal(_goal4) as IGoal); this.RequestGoal(request, _resolve); } /// /// Requests five goals of types TGoal1, TGoal2, TGoal3, TGoal4, and TGoal5. /// /// The type of the first goal. /// The type of the second goal. /// The type of the third goal. /// The type of the fourth goal. /// The type of the fifth goal. /// Whether to resolve the action after requesting the goals. Defaults to true. public void RequestGoal(bool resolve = true) where TGoal1 : IGoal where TGoal2 : IGoal where TGoal3 : IGoal where TGoal4 : IGoal where TGoal5 : IGoal { this.ValidateSetup(); var request = this.GetRequestCache(); request.Goals.Clear(); request.Goals.Add(this.AgentType.ResolveGoal()); request.Goals.Add(this.AgentType.ResolveGoal()); request.Goals.Add(this.AgentType.ResolveGoal()); request.Goals.Add(this.AgentType.ResolveGoal()); request.Goals.Add(this.AgentType.ResolveGoal()); this.RequestGoal(request, resolve); } internal void RequestGoal(Type _goal1, Type _goal2, Type _goal3, Type _goal4, Type _goal5, bool resolve = true) { this.ValidateSetup(); var request = this.GetRequestCache(); request.Goals.Clear(); request.Goals.Add(this.AgentType.ResolveGoal(_goal1) as IGoal); request.Goals.Add(this.AgentType.ResolveGoal(_goal2) as IGoal); request.Goals.Add(this.AgentType.ResolveGoal(_goal3) as IGoal); request.Goals.Add(this.AgentType.ResolveGoal(_goal4) as IGoal); request.Goals.Add(this.AgentType.ResolveGoal(_goal5) as IGoal); this.RequestGoal(request, resolve); } /// /// Requests a specific goal. /// /// The goal to request. /// Whether to resolve the action after requesting the goal. public void RequestGoal(IGoal goal, bool resolve) { this.ValidateSetup(); var request = this.GetRequestCache(); request.Goals.Clear(); request.Goals.Add(goal); this.RequestGoal(request, resolve); } /// /// Requests a goal based on the provided goal request. This will allow you to request multiple goals at once. /// /// The goal request. /// Whether to resolve the action after requesting the goal. public void RequestGoal(IGoalRequest request, bool resolve = true) { this.ValidateSetup(); if (request == null) return; if (AreEqual(this.GoalRequest?.Goals ?? new List(), request.Goals)) return; this.requestCache = this.GoalRequest; this.GoalRequest = request; if (this.Receiver == null) return; if (resolve) this.ResolveAction(); } public static bool AreEqual(List array1, List array2) where T : class { if (array1.Count != array2.Count) return false; for (var i = 0; i < array1.Count; i++) { if (array1[i] != array2[i]) return false; } return true; } /// /// Sets the action based on the provided goal result. This method is called by the resolver. /// /// The goal result. public void SetAction(IGoalResult result) { if (result == null) return; if (this.Logger.ShouldLog()) this.Logger.Log( $"Setting action '{TypeReSolveHelper.ResolveType(result.Action).GetGenericTypeName()}' for goal '{TypeReSolveHelper.ResolveType(result.Goal).GetGenericTypeName()}'."); var currentGoal = this.CurrentPlan?.Goal; this.CurrentPlan = result; if (this.Receiver == null) return; this.Receiver.Timers.Goal.Touch(); if (currentGoal != result.Goal) this.Events.GoalStart(result.Goal); this.Receiver.SetAction(this, result.Action, this.WorldData.GetTarget(result.Action)); } [Obsolete("Use agent.StopAction() instead")] public void StopAction(bool resolveAction = true) { this.Receiver?.StopAction(resolveAction); } private IActionReceiver receiver; public override IActionReceiver Receiver { get => this.receiver; set { this.receiver = value; this.Events.Bind(value); } } /// /// Will try and resolve for a new action based on the current goal request. /// public override void ResolveAction() { this.ValidateSetup(); this.Events.Resolve(); this.Receiver.Timers.Resolve.Touch(); } /// /// Clears the current goal. /// public void ClearGoal() { this.CurrentPlan = null; } /// /// Sets the distance multiplier for the agent. This is used by the resolver to calculate the cost of distance between /// actions. /// /// The distance multiplier. public void SetDistanceMultiplier(float multiplier) { if (multiplier < 0f) throw new GoapException("The distance multiplier must be >= 0."); this.DistanceMultiplier = multiplier; } /// /// Sets the distance multiplier for the agent based on speed, based on the assumption that speed is in units per /// second and cost is similar to a second. /// This is used by the resolver to calculate the cost of distance between actions. /// /// The distance multiplier. public void SetDistanceMultiplierSpeed(float speed) { if (speed <= 0f) throw new GoapException( "The speed value must be greater than 0. To disable the distance multiplier, use SetDistanceMultiplier(0f)."); this.DistanceMultiplier = 1f / speed; } private void ValidateSetup() { if (this.AgentType == null) throw new GoapException( $"There is no AgentType assigned to the agent '{this.name}'! Please assign one in the inspector or through code in the Awake method."); if (this.Receiver == null) throw new GoapException( $"There is no ActionReceiver assigned to the agent '{this.name}'! You're probably missing the ActionProvider on the AgentBehaviour."); } /// /// Gets the actions of the specified type. /// /// The type of the actions. /// A list of actions of the specified type. public List GetActions() where TAction : IGoapAction => this.AgentType.GetActions(); public List GetActions() => this.AgentType.GetActions(); #region Obsolete Methods [Obsolete("Use CurrentPlan.Goal instead")] public object CurrentGoal { get; set; } [Obsolete("Use AgentType instead")] public object GoapSet { get; set; } #endregion } }