using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Xml.Linq; namespace Barotrauma { partial class AITarget { public static List List = new List(); private Entity entity; public Entity Entity { get { if (entity != null && entity.Removed) { return null; } return entity; } } private float soundRange; private float sightRange; /// /// How long does it take for the ai target to fade out if not kept alive. /// public float FadeOutTime { get; private set; } = 2; public bool Static { get; private set; } public bool StaticSound { get; private set; } public bool StaticSight { get; private set; } public float SoundRange { get { return soundRange; } set { if (float.IsNaN(value)) { DebugConsole.ThrowError("Attempted to set the SoundRange of an AITarget to NaN.\n" + Environment.StackTrace.CleanupStackTrace()); return; } soundRange = MathHelper.Clamp(value, MinSoundRange, MaxSoundRange); if (soundRange > 0.0f && !Static && FadeOutTime > 0.0f) { NeedsUpdate = true; } } } public float SightRange { get { return sightRange; } set { if (float.IsNaN(value)) { DebugConsole.ThrowError("Attempted to set the SightRange of an AITarget to NaN.\n" + Environment.StackTrace.CleanupStackTrace()); return; } sightRange = MathHelper.Clamp(value, MinSightRange, MaxSightRange); if (sightRange > 0 && !Static && FadeOutTime > 0.0f) { NeedsUpdate = true; } } } private float sectorRad = MathHelper.TwoPi; public float SectorDegrees { get { return MathHelper.ToDegrees(sectorRad); } set { sectorRad = MathHelper.ToRadians(value); } } private Vector2 sectorDir; public Vector2 SectorDir { get { return sectorDir; } set { if (!MathUtils.IsValid(value)) { string errorMsg = "Invalid AITarget sector direction (" + value + ")\n" + Environment.StackTrace.CleanupStackTrace(); DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce("AITarget.SectorDir:" + entity?.ToString(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return; } sectorDir = value; } } public float SonarDisruption { get; set; } public LocalizedString SonarLabel; public Identifier SonarIconIdentifier; private bool inDetectable; public double InDetectableSetTime; /// /// Should be reset to false each frame and kept indetectable by e.g. a status effect. /// public bool InDetectable { get { return inDetectable || (SoundRange <= 0 && SightRange <= 0); } set { inDetectable = value; if (inDetectable) { InDetectableSetTime = Timing.TotalTime; NeedsUpdate = true; } } } public float MinSoundRange, MinSightRange; public float MaxSoundRange = 100000, MaxSightRange = 100000; /// /// Does the AI target do something that requires Update() to be called (e.g. static targets don't need to be updated) /// public bool NeedsUpdate { get; private set; } = true; public TargetType Type { get; private set; } public enum TargetType { Any, HumanOnly, EnemyOnly } public Vector2 WorldPosition { get { if (entity == null || entity.Removed) { #if DEBUG DebugConsole.ThrowError("Attempted to access a removed AITarget\n" + Environment.StackTrace.CleanupStackTrace()); #endif GameAnalyticsManager.AddErrorEventOnce("AITarget.WorldPosition:EntityRemoved", GameAnalyticsManager.ErrorSeverity.Error, "Attempted to access a removed AITarget\n" + Environment.StackTrace.CleanupStackTrace()); return Vector2.Zero; } return entity.WorldPosition; } } public Vector2 SimPosition { get { if (entity == null || entity.Removed) { #if DEBUG DebugConsole.ThrowError("Attempted to access a removed AITarget\n" + Environment.StackTrace.CleanupStackTrace()); #endif GameAnalyticsManager.AddErrorEventOnce("AITarget.WorldPosition:EntityRemoved", GameAnalyticsManager.ErrorSeverity.Error, "Attempted to access a removed AITarget\n" + Environment.StackTrace.CleanupStackTrace()); return Vector2.Zero; } return entity.SimPosition; } } /// /// Is some condition met (e.g. entity null, indetectable, outside level) that prevents anyone from detecting the target? /// public bool ShouldBeIgnored() { if (InDetectable) { return true; } if (Entity == null) { return true; } if (Level.Loaded != null && WorldPosition.Y > Level.Loaded.Size.Y) { return true; } return false; } public AITarget(Entity e, XElement element) : this(e) { SightRange = element.GetAttributeFloat("sightrange", 0.0f); SoundRange = element.GetAttributeFloat("soundrange", 0.0f); MinSightRange = element.GetAttributeFloat("minsightrange", 0f); MinSoundRange = element.GetAttributeFloat("minsoundrange", 0f); MaxSightRange = element.GetAttributeFloat("maxsightrange", SightRange); MaxSoundRange = element.GetAttributeFloat("maxsoundrange", SoundRange); FadeOutTime = element.GetAttributeFloat("fadeouttime", FadeOutTime); Static = element.GetAttributeBool("static", Static); StaticSight = element.GetAttributeBool("staticsight", StaticSight); StaticSound = element.GetAttributeBool("staticsound", StaticSound); if (Static) { StaticSound = true; StaticSight = true; } SonarDisruption = element.GetAttributeFloat("sonardisruption", 0.0f); string label = element.GetAttributeString("sonarlabel", ""); SonarLabel = TextManager.Get(label).Fallback(label); SonarIconIdentifier = element.GetAttributeIdentifier("sonaricon", Identifier.Empty); Type = element.GetAttributeEnum("type", TargetType.Any); Reset(); } public AITarget(Entity e) { entity = e; List.Add(this); } public void Update(float deltaTime) { InDetectable = false; if (!Static && FadeOutTime > 0) { // The aitarget goes silent/invisible if the components don't keep it active if (!StaticSight && sightRange > 0) { DecreaseSightRange(deltaTime); } if (!StaticSound && soundRange > 0) { DecreaseSoundRange(deltaTime); } if (sightRange <= 0 && soundRange <= 0) { NeedsUpdate = false; } } else { NeedsUpdate = false; } } public void IncreaseSoundRange(float deltaTime, float speed = 1) { SoundRange += speed * deltaTime * (MaxSoundRange / FadeOutTime); } public void IncreaseSightRange(float deltaTime, float speed = 1) { SightRange += speed * deltaTime * (MaxSightRange / FadeOutTime); } public void DecreaseSoundRange(float deltaTime, float speed = 1) { SoundRange -= speed * deltaTime * (MaxSoundRange / FadeOutTime); } public void DecreaseSightRange(float deltaTime, float speed = 1) { SightRange -= speed * deltaTime * (MaxSightRange / FadeOutTime); } public bool HasSector() { return sectorRad < MathHelper.TwoPi; } public bool IsWithinSector(Vector2 worldPosition) { if (!HasSector()) { return true; } Vector2 diff = worldPosition - WorldPosition; return Math.Abs(MathUtils.GetShortestAngle(MathUtils.VectorToAngle(diff), MathUtils.VectorToAngle(sectorDir))) <= sectorRad * 0.5f; } public void Remove() { List.Remove(this); entity = null; } public void Reset() { if (Static) { SightRange = MaxSightRange; SoundRange = MaxSoundRange; } else { // Non-static ai targets must be kept alive by a custom logic (e.g. item components) SightRange = StaticSight ? MaxSightRange : MinSightRange; SoundRange = StaticSound ? MaxSoundRange : MinSoundRange; } } } }