317 lines
13 KiB
C#
317 lines
13 KiB
C#
using FarseerPhysics;
|
|
using Microsoft.Xna.Framework;
|
|
using System;
|
|
using System.Xml.Linq;
|
|
|
|
namespace Barotrauma.Items.Components
|
|
{
|
|
partial class MotionSensor : ItemComponent
|
|
{
|
|
private float rangeX, rangeY;
|
|
|
|
private Vector2 detectOffset;
|
|
|
|
private float updateTimer;
|
|
|
|
[Flags]
|
|
public enum TargetType
|
|
{
|
|
Human = 1,
|
|
Monster = 2,
|
|
Wall = 4,
|
|
Pet = 8,
|
|
Any = Human | Monster | Wall | Pet,
|
|
}
|
|
|
|
[Serialize(false, IsPropertySaveable.No, description: "Has the item currently detected movement. Intended to be used by StatusEffect conditionals (setting this value in XML has no effect).")]
|
|
public bool MotionDetected { get; set; }
|
|
|
|
[InGameEditable, Serialize(TargetType.Any, IsPropertySaveable.Yes, description: "Which kind of targets can trigger the sensor?", alwaysUseInstanceValues: true)]
|
|
public TargetType Target
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[InGameEditable, Serialize(false, IsPropertySaveable.Yes, description: "Should the sensor ignore the bodies of dead characters?", alwaysUseInstanceValues: true)]
|
|
public bool IgnoreDead
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
|
|
[InGameEditable, Serialize(0.0f, IsPropertySaveable.Yes, description: "Horizontal detection range.", alwaysUseInstanceValues: true)]
|
|
public float RangeX
|
|
{
|
|
get { return rangeX; }
|
|
set
|
|
{
|
|
rangeX = MathHelper.Clamp(value, 0.0f, 1000.0f);
|
|
#if CLIENT
|
|
item.ResetCachedVisibleSize();
|
|
#endif
|
|
}
|
|
}
|
|
[InGameEditable, Serialize(0.0f, IsPropertySaveable.Yes, description: "Vertical movement detection range.", alwaysUseInstanceValues: true)]
|
|
public float RangeY
|
|
{
|
|
get { return rangeY; }
|
|
set
|
|
{
|
|
rangeY = MathHelper.Clamp(value, 0.0f, 1000.0f);
|
|
}
|
|
}
|
|
|
|
[InGameEditable, Serialize("0,0", IsPropertySaveable.Yes, description: "The position to detect the movement at relative to the item. For example, 0,100 would detect movement 100 units above the item.")]
|
|
public Vector2 DetectOffset
|
|
{
|
|
get { return detectOffset; }
|
|
set
|
|
{
|
|
detectOffset = value;
|
|
detectOffset.X = MathHelper.Clamp(value.X, -rangeX, rangeX);
|
|
detectOffset.Y = MathHelper.Clamp(value.Y, -rangeY, rangeY);
|
|
}
|
|
}
|
|
|
|
public Vector2 TransformedDetectOffset
|
|
{
|
|
get
|
|
{
|
|
Vector2 transformedDetectOffset = detectOffset;
|
|
if (item.FlippedX) { transformedDetectOffset.X = -transformedDetectOffset.X; }
|
|
if (item.FlippedY) { transformedDetectOffset.Y = -transformedDetectOffset.Y; }
|
|
return transformedDetectOffset;
|
|
}
|
|
}
|
|
|
|
[Editable(MinValueFloat = 0.1f, MaxValueFloat = 100.0f, DecimalCount = 2), Serialize(0.1f, IsPropertySaveable.Yes, description: "How often the sensor checks if there's something moving near it. Higher values are better for performance.", alwaysUseInstanceValues: true)]
|
|
public float UpdateInterval
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
private int maxOutputLength;
|
|
[Editable, Serialize(200, IsPropertySaveable.No, description: "The maximum length of the output strings. Warning: Large values can lead to large memory usage or networking issues.")]
|
|
public int MaxOutputLength
|
|
{
|
|
get { return maxOutputLength; }
|
|
set
|
|
{
|
|
maxOutputLength = Math.Max(value, 0);
|
|
}
|
|
}
|
|
|
|
private string output;
|
|
[InGameEditable, Serialize("1", IsPropertySaveable.Yes, description: "The signal the item outputs when it has detected movement.", alwaysUseInstanceValues: true)]
|
|
public string Output
|
|
{
|
|
get { return output; }
|
|
set
|
|
{
|
|
if (value == null) { return; }
|
|
output = value;
|
|
if (output.Length > MaxOutputLength && (item.Submarine == null || !item.Submarine.Loading))
|
|
{
|
|
output = output.Substring(0, MaxOutputLength);
|
|
}
|
|
}
|
|
}
|
|
|
|
private string falseOutput;
|
|
[InGameEditable, Serialize("0", IsPropertySaveable.Yes, description: "The signal the item outputs when it has not detected movement.", alwaysUseInstanceValues: true)]
|
|
public string FalseOutput
|
|
{
|
|
get { return falseOutput; }
|
|
set
|
|
{
|
|
if (value == null) { return; }
|
|
falseOutput = value;
|
|
if (falseOutput.Length > MaxOutputLength && (item.Submarine == null || !item.Submarine.Loading))
|
|
{
|
|
falseOutput = falseOutput.Substring(0, MaxOutputLength);
|
|
}
|
|
}
|
|
}
|
|
|
|
[InGameEditable(DecimalCount = 3), Serialize(0.0f, IsPropertySaveable.Yes, description: "How fast the objects within the detector's range have to be moving (in m/s).", alwaysUseInstanceValues: true)]
|
|
public float MinimumVelocity
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[Serialize(true, IsPropertySaveable.Yes, description: "Should the sensor trigger when the item itself moves.")]
|
|
public bool DetectOwnMotion
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public MotionSensor(Item item, ContentXElement element)
|
|
: base(item, element)
|
|
{
|
|
IsActive = true;
|
|
|
|
//backwards compatibility
|
|
if (element.GetAttribute("range") != null)
|
|
{
|
|
rangeX = rangeY = element.GetAttributeFloat("range", 0.0f);
|
|
}
|
|
|
|
//randomize update timer so all sensors aren't updated during the same frame
|
|
updateTimer = Rand.Range(0.0f, UpdateInterval);
|
|
}
|
|
|
|
public override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap, bool isItemSwap)
|
|
{
|
|
base.Load(componentElement, usePrefabValues, idRemap, isItemSwap);
|
|
//backwards compatibility
|
|
if (componentElement.GetAttributeBool("onlyhumans", false))
|
|
{
|
|
Target = TargetType.Human;
|
|
}
|
|
}
|
|
|
|
public override void Update(float deltaTime, Camera cam)
|
|
{
|
|
string signalOut = MotionDetected ? Output : FalseOutput;
|
|
|
|
if (!string.IsNullOrEmpty(signalOut)) { item.SendSignal(new Signal(signalOut, 1), "state_out"); }
|
|
|
|
if (MotionDetected)
|
|
{
|
|
ApplyStatusEffects(ActionType.OnUse, deltaTime);
|
|
}
|
|
|
|
updateTimer -= deltaTime;
|
|
if (updateTimer > 0.0f) { return; }
|
|
|
|
MotionDetected = false;
|
|
updateTimer = UpdateInterval;
|
|
|
|
if (item.body != null && item.body.Enabled && DetectOwnMotion)
|
|
{
|
|
if (Math.Abs(item.body.LinearVelocity.X) > MinimumVelocity || Math.Abs(item.body.LinearVelocity.Y) > MinimumVelocity)
|
|
{
|
|
MotionDetected = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
Vector2 detectPos = item.WorldPosition + TransformedDetectOffset;
|
|
Rectangle detectRect = new Rectangle((int)(detectPos.X - rangeX), (int)(detectPos.Y - rangeY), (int)(rangeX * 2), (int)(rangeY * 2));
|
|
float broadRangeX = Math.Max(rangeX * 2, 500);
|
|
float broadRangeY = Math.Max(rangeY * 2, 500);
|
|
|
|
if (item.CurrentHull == null && item.Submarine != null && Target.HasFlag(TargetType.Wall))
|
|
{
|
|
if (Level.Loaded != null && (Math.Abs(item.Submarine.Velocity.X) > MinimumVelocity || Math.Abs(item.Submarine.Velocity.Y) > MinimumVelocity))
|
|
{
|
|
var cells = Level.Loaded.GetCells(item.WorldPosition, 1);
|
|
foreach (var cell in cells)
|
|
{
|
|
if (cell.IsPointInside(item.WorldPosition))
|
|
{
|
|
MotionDetected = true;
|
|
return;
|
|
}
|
|
foreach (var edge in cell.Edges)
|
|
{
|
|
Vector2 e1 = edge.Point1 + cell.Translation;
|
|
Vector2 e2 = edge.Point2 + cell.Translation;
|
|
if (MathUtils.LineSegmentsIntersect(e1, e2, new Vector2(detectRect.X, detectRect.Y), new Vector2(detectRect.Right, detectRect.Y)) ||
|
|
MathUtils.LineSegmentsIntersect(e1, e2, new Vector2(detectRect.X, detectRect.Bottom), new Vector2(detectRect.Right, detectRect.Bottom)) ||
|
|
MathUtils.LineSegmentsIntersect(e1, e2, new Vector2(detectRect.X, detectRect.Y), new Vector2(detectRect.X, detectRect.Bottom)) ||
|
|
MathUtils.LineSegmentsIntersect(e1, e2, new Vector2(detectRect.Right, detectRect.Y), new Vector2(detectRect.Right, detectRect.Bottom)))
|
|
{
|
|
MotionDetected = true;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
foreach (Submarine sub in Submarine.Loaded)
|
|
{
|
|
if (sub == item.Submarine) { continue; }
|
|
|
|
Vector2 relativeVelocity = item.Submarine.Velocity - sub.Velocity;
|
|
if (Math.Abs(relativeVelocity.X) < MinimumVelocity && Math.Abs(relativeVelocity.Y) < MinimumVelocity) { continue; }
|
|
|
|
Rectangle worldBorders = new Rectangle(
|
|
sub.Borders.X + (int)sub.WorldPosition.X,
|
|
sub.Borders.Y + (int)sub.WorldPosition.Y - sub.Borders.Height,
|
|
sub.Borders.Width,
|
|
sub.Borders.Height);
|
|
|
|
if (worldBorders.Intersects(detectRect))
|
|
{
|
|
MotionDetected = true;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool triggerFromHumans = Target.HasFlag(TargetType.Human);
|
|
bool triggerFromPets = Target.HasFlag(TargetType.Pet);
|
|
bool triggerFromMonsters = Target.HasFlag(TargetType.Monster);
|
|
bool hasTriggers = triggerFromHumans || triggerFromPets || triggerFromMonsters;
|
|
if (!hasTriggers) { return; }
|
|
foreach (Character c in Character.CharacterList)
|
|
{
|
|
if (IgnoreDead && c.IsDead) { continue; }
|
|
|
|
//ignore characters that have spawned a second or less ago
|
|
//makes it possible to detect when a spawned character moves without triggering the detector immediately as the ragdoll spawns and drops to the ground
|
|
if (c.SpawnTime > Timing.TotalTime - 1.0) { continue; }
|
|
if (c.IsHuman)
|
|
{
|
|
if (!triggerFromHumans) { continue; }
|
|
}
|
|
else if (c.IsPet)
|
|
{
|
|
if (!triggerFromPets) { continue; }
|
|
}
|
|
else
|
|
{
|
|
// Not a human or a pet -> monster?
|
|
if (!triggerFromMonsters) { continue; }
|
|
if (CharacterParams.CompareGroup(c.Group, CharacterPrefab.HumanGroup))
|
|
{
|
|
//characters in the "human" group aren't considered monsters (even if they were something like a friendly mudraptor)
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//do a rough check based on the position of the character's collider first
|
|
//before the more accurate limb-based check
|
|
if (Math.Abs(c.WorldPosition.X - detectPos.X) > broadRangeX || Math.Abs(c.WorldPosition.Y - detectPos.Y) > broadRangeY)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach (Limb limb in c.AnimController.Limbs)
|
|
{
|
|
if (limb.IsSevered) { continue; }
|
|
if (limb.LinearVelocity.LengthSquared() < MinimumVelocity * MinimumVelocity) { continue; }
|
|
if (MathUtils.CircleIntersectsRectangle(limb.WorldPosition, ConvertUnits.ToDisplayUnits(limb.body.GetMaxExtent()), detectRect))
|
|
{
|
|
MotionDetected = true;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public override XElement Save(XElement parentElement)
|
|
{
|
|
Vector2 prevDetectOffset = detectOffset;
|
|
XElement element = base.Save(parentElement);
|
|
detectOffset = prevDetectOffset;
|
|
return element;
|
|
}
|
|
}
|
|
}
|