319 lines
10 KiB
C#
319 lines
10 KiB
C#
using Microsoft.Xna.Framework;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Xml.Linq;
|
|
|
|
namespace Barotrauma
|
|
{
|
|
partial class AITarget
|
|
{
|
|
public static List<AITarget> List = new List<AITarget>();
|
|
|
|
private Entity entity;
|
|
public Entity Entity
|
|
{
|
|
get
|
|
{
|
|
if (entity != null && entity.Removed) { return null; }
|
|
return entity;
|
|
}
|
|
}
|
|
|
|
private float soundRange;
|
|
private float sightRange;
|
|
|
|
/// <summary>
|
|
/// How long does it take for the ai target to fade out if not kept alive.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// A multiplier for the sound range for the purposes of displaying the target on sonar.
|
|
/// E.g. a value of 10 would mean the sonar can detect the target from x10 further than monsters.
|
|
/// </summary>
|
|
public float SoundRangeOnSonarMultiplier { get; private set; } = 1.0f;
|
|
|
|
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;
|
|
|
|
/// <summary>
|
|
/// Should be reset to false each frame and kept indetectable by e.g. a status effect.
|
|
/// </summary>
|
|
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;
|
|
|
|
/// <summary>
|
|
/// Does the AI target do something that requires Update() to be called (e.g. static targets don't need to be updated)
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is some condition met (e.g. entity null, indetectable, outside level) that prevents anyone from detecting the target?
|
|
/// </summary>
|
|
public bool ShouldBeIgnored()
|
|
{
|
|
if (InDetectable) { return true; }
|
|
if (Entity == null) { return true; }
|
|
if (Level.IsPositionAboveLevel(WorldPosition))
|
|
{
|
|
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);
|
|
SoundRangeOnSonarMultiplier = element.GetAttributeFloat(nameof(SoundRangeOnSonarMultiplier), 1.0f);
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|