using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Goap.Agent.Core; using Goap.Core; using Debug = UnityEngine.Debug; namespace Goap.Runtime { public abstract class MultiSensorBase : IMultiSensor { public IMultiSensorConfig Config { get; private set; } public Dictionary LocalSensors { get; private set; } = new(); public Dictionary GlobalSensors { get; private set; } = new(); /// /// Sets the configuration for the multi-sensor. /// /// The multi-sensor configuration. public void SetConfig(IMultiSensorConfig config) { this.Config = config; } public Type Key { get; set; } /// /// Called when the sensor is created. Don't use this method to add sensors, use the constructor instead. /// public abstract void Created(); /// /// Called when the sensor should update. Use this for caching or other performance optimizations. /// public abstract void Update(); /// /// Gets the keys of the sensors. /// /// An array of sensor keys. public Type[] GetKeys() { var keys = new List(); foreach (var sensor in this.LocalSensors.Keys) { keys.Add(sensor); } foreach (var sensor in this.GlobalSensors.Keys) { keys.Add(sensor); } return keys.ToArray(); } /// /// Senses the world data using global sensors. /// /// The world data. public void Sense(IWorldData data) { foreach (var sensor in this.GlobalSensors.Values) { sensor.Sense(data); } } /// /// Senses the world data using specific global sensors. /// /// The world data. /// The keys of the sensors to use. public void Sense(IWorldData data, Type[] keys) { foreach (var key in keys) { if (this.GlobalSensors.ContainsKey(key)) { this.GlobalSensors[key].Sense(data); } } } /// /// Senses the world data using local sensors. /// /// The world data. /// The action receiver. /// The component references. public void Sense(IWorldData data, IActionReceiver agent, IComponentReference references) { try { foreach (var sensor in this.LocalSensors.Values) { sensor.Sense(data, agent, references); } } catch (Exception tmp_Exception) { Debug.LogError(tmp_Exception); throw; } } /// /// Senses the world data using specific local sensors. /// /// The world data. /// The action receiver. /// The component references. /// The keys of the sensors to use. public void Sense(IWorldData data, IActionReceiver agent, IComponentReference references, Type[] keys) { foreach (var key in keys) { if (this.LocalSensors.ContainsKey(key)) { this.LocalSensors[key].Sense(data, agent, references); } } } /// /// Adds a local world sensor. /// /// The type of the world key. /// The sense function. /// The sensor timer. public void AddLocalWorldSensor(Func sense, ISensorTimer timer = null) where TKey : IWorldKey { this.ValidateCalledFromConstructor(); timer ??= SensorTimer.Always; this.LocalSensors.Add(typeof(TKey), new LocalSensor { Key = typeof(TKey), SenseMethod = (IWorldData data, IActionReceiver agent, IComponentReference references) => { var state = data.GetWorldState(typeof(TKey)); if (!timer.ShouldSense(state?.Timer)) return; data.SetState(sense(agent, references)); }, }); } public void AddLocalWorldSensor(Type _worldKey, Func sense, ISensorTimer timer = null) { this.ValidateCalledFromConstructor(); timer ??= SensorTimer.Always; this.LocalSensors.Add(_worldKey, new LocalSensor { Key = _worldKey, SenseMethod = (IWorldData data, IActionReceiver agent, IComponentReference references) => { var state = data.GetWorldState(_worldKey); if (!timer.ShouldSense(state?.Timer)) return; data.SetState(_worldKey, sense(agent, references)); //data.SetState(sense(agent, references)); }, }); } /// /// Adds a global world sensor. /// /// The type of the world key. /// The sense function. /// The sensor timer. public void AddGlobalWorldSensor(Func sense, ISensorTimer timer = null) where TKey : IWorldKey { this.ValidateCalledFromConstructor(); timer ??= SensorTimer.Always; this.GlobalSensors.Add(typeof(TKey), new GlobalSensor { Key = typeof(TKey), SenseMethod = (IWorldData data) => { var state = data.GetWorldState(typeof(TKey)); if (!timer.ShouldSense(state?.Timer)) return; data.SetState(sense()); }, }); } internal void AddGlobalWorldSensor(Type _worldKey, Func sense, ISensorTimer timer = null) { this.ValidateCalledFromConstructor(); timer ??= SensorTimer.Always; this.GlobalSensors.Add(_worldKey, new GlobalSensor { Key = _worldKey, SenseMethod = (IWorldData data) => { var state = data.GetWorldState(_worldKey); if (!timer.ShouldSense(state?.Timer)) return; //data.SetState(sense()); data.SetState(_worldKey, sense()); }, }); } public void AddLocalTargetSensor(Func sense, ISensorTimer timer = null) where TKey : ITargetKey { this.ValidateCalledFromConstructor(); timer ??= SensorTimer.Always; this.LocalSensors.Add(typeof(TKey), new LocalSensor { Key = typeof(TKey), SenseMethod = (IWorldData data, IActionReceiver agent, IComponentReference references) => { var state = data.GetTargetState(typeof(TKey)); if (!timer.ShouldSense(state?.Timer)) return; data.SetTarget(sense(agent, references, state?.Value)); }, }); } public void AddLocalTargetSensor(Type _targetKey, Func sense, ISensorTimer timer = null) { this.ValidateCalledFromConstructor(); timer ??= SensorTimer.Always; this.LocalSensors.Add(_targetKey, new LocalSensor { Key = _targetKey, SenseMethod = (IWorldData data, IActionReceiver agent, IComponentReference references) => { var state = data.GetTargetState(_targetKey); if (!timer.ShouldSense(state?.Timer)) return; var tmp_Target = sense(agent, references, state?.Value); data.SetTarget(_targetKey, tmp_Target); }, }); } public void AddGlobalTargetSensor(Func sense, ISensorTimer timer = null) where TKey : ITargetKey { this.ValidateCalledFromConstructor(); timer ??= SensorTimer.Always; this.GlobalSensors.Add(typeof(TKey), new GlobalSensor { Key = typeof(TKey), SenseMethod = (IWorldData data) => { var state = data.GetTargetState(typeof(TKey)); if (!timer.ShouldSense(state?.Timer)) return; data.SetTarget(sense(state?.Value)); }, }); } internal void AddGlobalTargetSensor(Type _targetKey, Func sense, ISensorTimer timer = null) { this.ValidateCalledFromConstructor(); timer ??= SensorTimer.Always; this.GlobalSensors.Add(_targetKey, new GlobalSensor { Key = _targetKey, SenseMethod = (IWorldData data) => { var state = data.GetTargetState(_targetKey); if (!timer.ShouldSense(state?.Timer)) return; //data.SetTarget(sense(state?.Value)); data.SetTarget(_targetKey, sense(state?.Value)); }, }); } /// /// Gets the names of the sensors. /// /// An array of sensor names. public string[] GetSensors() { var sensors = new List(); foreach (var sensor in this.LocalSensors.Keys) { sensors.Add($"{sensor.Name} (local)"); } foreach (var sensor in this.GlobalSensors.Keys) { sensors.Add($"{sensor.Name} (global)"); } return sensors.ToArray(); } private void ValidateCalledFromConstructor() { #if UNITY_EDITOR // var stackTrace = new StackTrace(); // var frames = stackTrace.GetFrames(); // // // Check if any of the frames belong to the constructor of this class // var calledFromConstructor = frames != null && frames.Any(f => // f.GetMethod() is {IsConstructor: true} && // typeof(MultiSensorBase).IsAssignableFrom(f.GetMethod().DeclaringType) // ); // // if (!calledFromConstructor) // UnityEngine.Debug.LogWarning( // "Multi sensor registration must be added from the constructor of the sensor, not the Created method."); #endif } } public class GlobalSensor : IGlobalSensor { public Action SenseMethod; public Type Key { get; set; } public void Created() { } public Type[] GetKeys() => new[] {this.Key}; public void Sense(IWorldData data) => this.SenseMethod(data); } public class LocalSensor : ILocalSensor { public Action SenseMethod; public Type Key { get; set; } public Type[] GetKeys() => new[] {this.Key}; public void Created() { } public void Update() { } public void Sense(IWorldData data, IActionReceiver agent, IComponentReference references) => this.SenseMethod(data, agent, references); } }