2 using FarseerPhysics.Dynamics;
3 using FarseerPhysics.Dynamics.Contacts;
4 using FarseerPhysics.Dynamics.Joints;
5 using Microsoft.Xna.Framework;
8 using System.Collections.Generic;
10 using System.Xml.Linq;
21 LeftLeg,
RightLeg,
LeftFoot,
RightFoot,
Head,
Torso,
Tail,
Legs,
RightThigh,
LeftThigh,
Waist,
Jaw
41 set =>
Joint.Enabled = value;
50 get =>
Joint.WorldAnchorA;
51 set =>
Joint.WorldAnchorA = value;
56 get =>
Joint.WorldAnchorB;
57 set =>
Joint.WorldAnchorB = value;
148 MaxMotorTorque = 0.25f
151 Joint.CollideConnected =
false;
163 if (
float.IsNaN(
Params.LowerLimit))
167 if (
float.IsNaN(
Params.UpperLimit))
225 private FixedMouseJoint pullJoint;
229 private bool ignoreCollisions;
232 get {
return ignoreCollisions; }
235 ignoreCollisions = value;
238 if (ignoreCollisions)
247 body.
CollidesWith = Physics.CollisionAll & ~Physics.CollisionCharacter & ~Physics.CollisionItem & ~Physics.CollisionItemBlocking;
253 private bool isSevered;
254 private float severedFadeOutTimer;
256 private Vector2? mouthPos;
261 if (!mouthPos.HasValue)
263 mouthPos =
Params.MouthPos;
265 return mouthPos.Value;
274 public List<DamageModifier>
DamageModifiers {
get;
private set; } =
new List<DamageModifier>();
355 get {
return isSevered; }
358 if (isSevered == value) {
return; }
363 SeveredFadeOutTime = Math.Max(
Params.SeveredFadeOutTime, connectedLimbs.Any() ? connectedLimbs.Max(l => l.SeveredFadeOutTime) : 0);
376 severedFadeOutTimer = 0.0f;
381 damageOverlayStrength = 1.0f;
392 set =>
Params.Hide = value;
402 get {
return ConvertUnits.ToDisplayUnits(
body?.
SimPosition ?? Vector2.Zero); }
412 DebugConsole.ThrowError(
"Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace());
414 GameAnalyticsManager.AddErrorEventOnce(
"Limb.LinearVelocity:SimPosition", GameAnalyticsManager.ErrorSeverity.Error,
415 "Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace());
430 DebugConsole.ThrowError(
"Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace());
432 GameAnalyticsManager.AddErrorEventOnce(
"Limb.LinearVelocity:DrawPosition", GameAnalyticsManager.ErrorSeverity.Error,
433 "Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace());
447 DebugConsole.ThrowError(
"Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace());
449 GameAnalyticsManager.AddErrorEventOnce(
"Limb.LinearVelocity:SimPosition", GameAnalyticsManager.ErrorSeverity.Error,
450 "Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace());
467 DebugConsole.ThrowError(
"Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace());
469 GameAnalyticsManager.AddErrorEventOnce(
"Limb.Mass:AccessRemoved", GameAnalyticsManager.ErrorSeverity.Error,
470 "Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace());
486 DebugConsole.ThrowError(
"Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace());
488 GameAnalyticsManager.AddErrorEventOnce(
"Limb.LinearVelocity:AccessRemoved", GameAnalyticsManager.ErrorSeverity.Error,
489 "Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace());
498 get {
return (dir ==
Direction.Left) ? -1.0f : 1.0f; }
509 private float _alpha = 1.0f;
518 _alpha = MathHelper.Clamp(value, 0.0f, 1.0f);
524 public readonly List<WearableSprite>
WearingItems =
new List<WearableSprite>();
526 public readonly List<WearableSprite>
OtherWearables =
new List<WearableSprite>();
530 get {
return pullJoint.Enabled; }
531 set { pullJoint.Enabled = value; }
536 get {
return pullJoint.MaxForce; }
537 set { pullJoint.MaxForce = value; }
542 get {
return pullJoint.WorldAnchorA; }
545 if (!MathUtils.IsValid(value))
547 string errorMsg =
"Attempted to set the anchor A of a limb's pull joint to an invalid value (" + value +
")\n" + Environment.StackTrace.CleanupStackTrace();
548 GameAnalyticsManager.AddErrorEventOnce(
"Limb.SetPullJointAnchorA:InvalidValue", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
550 DebugConsole.ThrowError(errorMsg);
555 if (Vector2.DistanceSquared(
SimPosition, value) > 50.0f * 50.0f)
558 string errorMsg =
"Attempted to move the anchor A of a limb's pull joint extremely far from the limb (diff: " + diff +
561 + Environment.StackTrace.CleanupStackTrace();
562 GameAnalyticsManager.AddErrorEventOnce(
"Limb.SetPullJointAnchorA:ExcessiveValue", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
564 DebugConsole.ThrowError(errorMsg);
569 pullJoint.WorldAnchorA = value;
575 get {
return pullJoint.WorldAnchorB; }
578 if (!MathUtils.IsValid(value))
580 string errorMsg =
"Attempted to set the anchor B of a limb's pull joint to an invalid value (" + value +
")\n" + Environment.StackTrace.CleanupStackTrace();
581 GameAnalyticsManager.AddErrorEventOnce(
"Limb.SetPullJointAnchorB:InvalidValue", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
583 DebugConsole.ThrowError(errorMsg);
588 if (Vector2.DistanceSquared(pullJoint.WorldAnchorA, value) > 50.0f * 50.0f)
590 Vector2 diff = value - pullJoint.WorldAnchorA;
591 string errorMsg =
"Attempted to move the anchor B of a limb's pull joint extremely far from the limb (diff: " + diff +
594 + Environment.StackTrace.CleanupStackTrace();
595 GameAnalyticsManager.AddErrorEventOnce(
"Limb.SetPullJointAnchorB:ExcessiveValue", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
597 DebugConsole.ThrowError(errorMsg);
602 pullJoint.WorldAnchorB = value;
608 get {
return pullJoint.LocalAnchorA; }
638 bool canBeSevered =
Params.CanBeSeveredAlive;
664 private readonly Dictionary<ActionType, List<StatusEffect>> statusEffects =
new Dictionary<ActionType, List<StatusEffect>>();
666 public Dictionary<ActionType, List<StatusEffect>>
StatusEffects {
get {
return statusEffects; } }
672 this.Params = limbParams;
675 type = limbParams.Type;
678 pullJoint =
new FixedMouseJoint(
body.
FarseerBody, ConvertUnits.ToSimUnits(limbParams.PullPos *
Scale))
683 MaxForce = 1000 *
Mass
688 var element = limbParams.Element;
692 foreach (var subElement
in element.Elements())
694 switch (subElement.Name.ToString().ToLowerInvariant())
714 if (
character is { VariantOf.IsEmpty:
false })
717 if (attackElement !=
null)
725 case "damagemodifier":
730 if (statusEffect !=
null)
732 if (!statusEffects.ContainsKey(statusEffect.type))
734 statusEffects.Add(statusEffect.type,
new List<StatusEffect>());
736 statusEffects[statusEffect.type].Add(statusEffect);
744 InitProjSpecific(element);
748 public void MoveToPos(Vector2 pos,
float force,
bool pullFromCenter =
false)
753 pullPos = pullJoint.WorldAnchorA;
763 pullJoint.LocalAnchorA =
new Vector2(-pullJoint.LocalAnchorA.X, pullJoint.LocalAnchorA.Y);
766 public AttackResult AddDamage(Vector2 simPosition,
float damage,
float bleedingDamage,
float burnDamage,
bool playSound)
768 List<Affliction> afflictions =
new List<Affliction>();
773 return AddDamage(simPosition, afflictions, playSound);
776 private readonly List<DamageModifier> appliedDamageModifiers =
new List<DamageModifier>();
777 private readonly List<DamageModifier> tempModifiers =
new List<DamageModifier>();
778 private readonly List<Affliction> afflictionsCopy =
new List<Affliction>();
779 public AttackResult AddDamage(Vector2 simPosition, IEnumerable<Affliction> afflictions,
bool playSound,
float damageMultiplier = 1,
float penetration = 0f,
Character attacker =
null)
781 appliedDamageModifiers.Clear();
782 afflictionsCopy.Clear();
783 foreach (var affliction
in afflictions)
785 tempModifiers.Clear();
786 var newAffliction = affliction;
787 float random = Rand.Value(Rand.RandSync.Unsynced);
788 bool foundMatchingModifier =
false;
789 bool applyAffliction =
true;
793 foundMatchingModifier =
true;
796 applyAffliction =
false;
801 tempModifiers.Add(damageModifier);
809 foundMatchingModifier =
true;
812 applyAffliction =
false;
817 tempModifiers.Add(damageModifier);
821 if (!foundMatchingModifier && random > affliction.Probability) {
continue; }
822 float finalDamageModifier = damageMultiplier;
839 damageModifierValue = MathHelper.Lerp(damageModifierValue, 1f, penetration);
841 finalDamageModifier *= damageModifierValue;
843 if (affliction.MultiplyByMaxVitality)
847 if (!MathUtils.NearlyEqual(finalDamageModifier, 1.0f))
849 newAffliction = affliction.CreateMultiplied(finalDamageModifier, affliction);
853 newAffliction.SetStrength(affliction.NonClampedStrength);
855 if (attacker !=
null)
858 attacker.CheckTalents(
AbilityEffectType.OnAddDamageAffliction, abilityAfflictionCharacter);
859 newAffliction = abilityAfflictionCharacter.Affliction;
863 afflictionsCopy.Add(newAffliction);
864 newAffliction.Source ??= attacker;
866 appliedDamageModifiers.AddRange(tempModifiers);
868 var result =
new AttackResult(afflictionsCopy,
this, appliedDamageModifiers);
869 if (result.Afflictions.None())
873 AddDamageProjSpecific(playSound, result);
875 float bleedingDamage = 0;
878 foreach (var affliction
in result.Afflictions)
885 if (bleedingDamage > 0)
887 float bloodDecalSize = MathHelper.Clamp(bleedingDamage / 5, 0.1f, 1.0f);
898 partial
void AddDamageProjSpecific(
bool playSound,
AttackResult result);
900 public bool SectorHit(Vector2 armorSector, Vector2 simPosition)
902 if (armorSector == Vector2.Zero) {
return false; }
904 if (Math.Abs(armorSector.Y - armorSector.X) >= MathHelper.TwoPi) {
return true; }
906 float offset = (MathHelper.PiOver2 - MathUtils.GetMidAngle(armorSector.X, armorSector.Y)) *
Dir;
907 float hitAngle = VectorExtensions.Angle(VectorExtensions.Forward(rotation + offset),
SimPosition - simPosition);
909 return hitAngle < sectorSize / 2;
914 return Math.Abs(armorSector.X - armorSector.Y);
919 UpdateProjSpecific(deltaTime);
930 severedFadeOutTimer += deltaTime;
942 if (
Params.BlinkFrequency > 0)
953 if (reEnableTimer > 0)
955 reEnableTimer -= deltaTime;
957 else if (reEnableTimer > -1)
966 private bool temporarilyDisabled;
967 private float reEnableTimer = -1;
968 private bool originalIgnoreCollisions;
972 temporarilyDisabled =
true;
979 reEnableTimer = duration;
991 if (!temporarilyDisabled) {
return; }
998 partial
void UpdateProjSpecific(
float deltaTime);
1000 private readonly List<Body> contactBodies =
new List<Body>();
1006 attackResult =
default;
1008 float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(simPos, attackSimPos));
1029 bool wasHit =
false;
1030 Body structureBody =
null;
1031 if (damageTarget !=
null)
1038 Vector2 rayStart = simPos;
1039 Vector2 rayEnd = attackSimPos;
1042 rayStart -= spatialEntity.Submarine.SimPosition;
1043 rayEnd -= spatialEntity.Submarine.SimPosition;
1046 if (damageTarget is
Item i && i.GetComponent<Items.
Components.Door>() !=
null)
1052 else if (damageTarget is
Structure wall && structureBody !=
null &&
1061 wasHit = structureBody ==
null;
1066 contactBodies.Clear();
1067 if (damageTarget is
Character targetCharacter)
1069 foreach (
Limb limb
in targetCharacter.AnimController.Limbs)
1074 else if (damageTarget is
Structure targetStructure)
1078 contactBodies.Add(targetStructure.Submarine.PhysicsBody.FarseerBody);
1082 contactBodies.AddRange(targetStructure.Bodies);
1085 else if (damageTarget is
Item)
1087 Item targetItem = damageTarget as
Item;
1091 while (contactEdge !=
null)
1093 if (contactEdge.Contact !=
null &&
1094 contactEdge.Contact.IsTouching &&
1095 contactBodies.Any(b => b == contactEdge.Contact.FixtureA?.Body || b == contactEdge.Contact.FixtureB?.Body))
1097 structureBody = contactBodies.LastOrDefault();
1101 contactEdge = contactEdge.Next;
1109 wasHit = damageTarget !=
null;
1120 attackLimb:
this, targetEntity: damageTarget, targetLimb: targetLimb,
1121 targetSimPos: attackSimPos));
1138 if (diff == Vector2.Zero) {
continue; }
1140 Vector2 forcePos = limb.pullJoint ==
null ? limb.
body.
SimPosition : limb.pullJoint.WorldAnchorA;
1144 else if (diff != Vector2.Zero)
1147 Vector2 forcePos = pullJoint ==
null ?
body.
SimPosition : pullJoint.WorldAnchorA;
1167 bool playSound =
false;
1175 if (damageTarget is
Character targetCharacter && targetLimb !=
null)
1203 private WeldJoint attachJoint;
1204 private WeldJoint colliderJoint;
1211 private void StickTo(Body target, Vector2 from, Vector2 to)
1213 if (attachJoint !=
null)
1216 if (attachJoint.BodyB == target) {
return; }
1227 mainLimbLocalFront.X = -mainLimbLocalFront.X;
1229 Vector2 mainLimbFront = mainLimbBody.
FarseerBody.GetWorldPoint(mainLimbLocalFront);
1232 colliderJoint =
new WeldJoint(colliderBody, mainLimbBody.
FarseerBody, mainLimbFront, mainLimbFront,
true)
1234 KinematicBodyB =
true,
1235 CollideConnected =
false
1243 DampingRatio = 0.5f,
1244 KinematicBodyB =
true,
1245 CollideConnected =
false
1255 if (colliderJoint !=
null)
1258 colliderJoint =
null;
1262 private readonly List<ISerializableEntity> targets =
new List<ISerializableEntity>();
1265 if (!statusEffects.TryGetValue(actionType, out var statusEffectList)) {
return; }
1266 foreach (
StatusEffect statusEffect
in statusEffectList)
1291 foreach (var limbType
in statusEffect.
targetLimbs)
1298 if (limb.IsSevered) {
continue; }
1299 if (limb.type == limbType)
1301 ApplyToLimb(actionType, deltaTime, statusEffect,
character, limb);
1311 ApplyToLimb(actionType, deltaTime, statusEffect,
character, limb);
1317 Limb limb =
ragdoll.
Limbs.LastOrDefault(l => l.type == limbType && !l.IsSevered && !l.Hidden);
1320 ApplyToLimb(actionType, deltaTime, statusEffect,
character, limb);
1330 if (limb.IsSevered) {
continue; }
1331 ApplyToLimb(actionType, deltaTime, statusEffect,
character, limb);
1340 ApplyToLimb(actionType, deltaTime, statusEffect,
character, limb:
this);
1346 statusEffect.
Apply(actionType, deltaTime, entity:
character, target: limb);
1355 private float TotalBlinkDurationOut =>
Params.BlinkDurationOut +
Params.BlinkHoldTime;
1373 float t = ToolBox.GetEasing(
Params.BlinkTransitionIn, MathUtils.InverseLerp(1, 0,
BlinkPhase /
Params.BlinkDurationIn));
1375 if (
Params.UseTextureOffsetForBlinking)
1393 float t = ToolBox.GetEasing(
Params.BlinkTransitionOut, MathUtils.InverseLerp(0, 1, (-
BlinkPhase -
Params.BlinkHoldTime) /
Params.BlinkDurationOut));
1395 if (
Params.UseTextureOffsetForBlinking)
1420 var connectedLimbs =
new HashSet<Limb>();
1424 foreach (
LimbJoint connectedJoint
in connectedJoints)
1426 if (otherJoints.Contains(connectedJoint))
1428 connectedLimbs.Add(limb);
1432 return connectedLimbs;
1439 if (pullJoint !=
null)
1448 RemoveProjSpecific();
1452 partial
void RemoveProjSpecific();
1456 pullJoint.LocalAnchorA = ConvertUnits.ToSimUnits(
Params.PullPos *
Scale);
1457 LoadParamsProjSpecific();
1460 partial
void LoadParamsProjSpecific();
AbilityAfflictionCharacter(Affliction affliction, Character character)
AbilityReduceAffliction(Character character, float value)
A special affliction type that increases the character's Bloodloss affliction with a rate relative to...
AfflictionPrefab is a prefab that defines a type of affliction that can be applied to a character....
Affliction Instantiate(float strength, Character source=null)
static AfflictionPrefab InternalDamage
static AfflictionPrefab Burn
static AfflictionPrefab Bleeding
static readonly Identifier ParalysisType
static readonly Identifier EMPType
static readonly Identifier PoisonType
AnimationParams? CurrentAnimationParams
Attacks are used to deal damage to characters, structures and items. They can be defined in the weapo...
AttackResult DoDamageToLimb(Character attacker, Limb targetLimb, Vector2 worldPosition, float deltaTime, bool playSound=true, PhysicsBody sourceBody=null, Limb sourceLimb=null)
void UpdateCoolDown(float deltaTime)
float RangeMultiplier
Used for multiplying all the ranges.
void SetCoolDown(bool applyRandom)
TransitionMode RootTransitionEasing
readonly List< int > ForceOnLimbIndices
Vector2 CalculateAttackPhase(TransitionMode easing=TransitionMode.Linear)
float ImpactMultiplier
Used for multiplying the physics forces.
void UpdateAttackTimer(float deltaTime, Character character)
float DamageMultiplier
Used for multiplying all the damage.
AttackResult DoDamage(Character attacker, IDamageable target, Vector2 worldPosition, float deltaTime, bool playSound=true, PhysicsBody sourceBody=null, Limb sourceLimb=null)
HitDetection HitDetectionType
readonly CharacterParams Params
CharacterHealth CharacterHealth
virtual AIController AIController
override Vector2? SimPosition
void Kill(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool isNetworkMessage=false, bool log=true)
static Character? Controlled
readonly AnimController AnimController
float PoisonVulnerability
bool MatchesAffliction(string identifier, string type)
Returns true if the type or the identifier matches the defined types/identifiers.
Vector2 ArmorSectorInRadians
float ProbabilityMultiplier
static NetworkMember NetworkMember
Decal AddDecal(UInt32 decalId, Vector2 worldPosition, float scale, bool isNetworkEvent, int? spriteIndex=null)
static Hull FindHull(Vector2 position, Hull guess=null, bool useWorldCoordinates=true, bool inclusive=true)
Returns the hull which contains the point (or null if it isn't inside any)
List< ItemComponent > Components
AttackResult AddDamage(Vector2 simPosition, float damage, float bleedingDamage, float burnDamage, bool playSound)
readonly Ragdoll ragdoll
Note that during the limb initialization, character.AnimController returns null, whereas this field i...
readonly Character character
List< DamageModifier > DamageModifiers
IEnumerable< Limb > GetConnectedLimbs()
void ApplyStatusEffects(ActionType actionType, float deltaTime)
readonly LimbParams Params
Dictionary< ActionType, List< StatusEffect > > StatusEffects
readonly List< WearableSprite > WearingItems
void MoveToPos(Vector2 pos, float force, bool pullFromCenter=false)
void Update(float deltaTime)
Vector2 PullJointWorldAnchorB
Vector2 PullJointWorldAnchorA
float GetArmorSectorSize(Vector2 armorSector)
bool SectorHit(Vector2 armorSector, Vector2 simPosition)
bool UpdateAttack(float deltaTime, Vector2 attackSimPos, IDamageable damageTarget, out AttackResult attackResult, float distance=-1, Limb targetLimb=null)
Returns true if the attack successfully hit something. If the distance is not given,...
const float SoundInterval
IEnumerable< LimbJoint > GetConnectedJoints()
Limb(Ragdoll ragdoll, Character character, LimbParams limbParams)
Items.Components.Rope AttachedRope
float LastAttackSoundTime
void UpdateBlink(float deltaTime, float referenceRotation)
void ExecuteAttack(IDamageable damageTarget, Limb targetLimb, out AttackResult attackResult)
AttackResult AddDamage(Vector2 simPosition, IEnumerable< Affliction > afflictions, bool playSound, float damageMultiplier=1, float penetration=0f, Character attacker=null)
readonly List< WearableSprite > OtherWearables
Dictionary< Identifier, SerializableProperty > SerializableProperties
float Alpha
Can be used by status effects
Vector2 PullJointLocalAnchorA
void HideAndDisable(float duration=0, bool ignoreCollisions=true)
readonly WeldJoint weldJoint
LimbJoint(Limb limbA, Limb limbB, JointParams jointParams, Ragdoll ragdoll)
readonly JointParams Params
LimbJoint(Limb limbA, Limb limbB, Vector2 anchor1, Vector2 anchor2, bool weld=false)
readonly RevoluteJoint revoluteJoint
void ApplyTorque(float torque)
void ApplyLinearImpulse(Vector2 impulse)
bool SetTransform(Vector2 simPosition, float rotation, bool setPrevTransform=true)
Vector2 GetLocalFront(float? spritesheetRotation=null)
Returns the farthest point towards the forward of the body. For capsules and circles,...
void ApplyWaterForces()
Applies buoyancy, drag and angular drag caused by water
void MoveToPos(Vector2 simPosition, float force, Vector2? pullPos=null)
Category CollisionCategories
float TransformedRotation
Takes flipping (Dir) into account.
void SmoothRotate(float targetRotation, float force=10.0f, bool wrapAngle=true)
Rotate the body towards the target rotation in the "shortest direction", taking into account the curr...
abstract RagdollParams RagdollParams
bool? SimplePhysicsEnabled
Limb GetLimb(LimbType limbType, bool excludeSevered=true)
Note that if there are multiple limbs of the same type, only the first (valid) limb is returned.
void SubtractMass(Limb limb)
static Dictionary< Identifier, SerializableProperty > GetProperties(object obj)
Vector2 RelativeOrigin
0 - 1
StatusEffects can be used to execute various kinds of effects: modifying the state of some entity in ...
bool HasTargetType(TargetType targetType)
bool ShouldWaitForInterval(Entity entity, float deltaTime)
static StatusEffect Load(ContentXElement element, string parentDebugName)
void AddNearbyTargets(Vector2 worldPosition, List< ISerializableEntity > targets)
readonly bool OnlyWhenDamagedByPlayer
If enabled, the effect only executes when the entity receives damage from a player character (a chara...
bool HasRequiredAfflictions(AttackResult attackResult)
readonly LimbType[] targetLimbs
Which types of limbs this effect can target? Only valid when targeting characters or limbs.
virtual void Apply(ActionType type, float deltaTime, Entity entity, ISerializableEntity target, Vector2? worldPosition=null)
Submarine(SubmarineInfo info, bool showErrorMessages=true, Func< Submarine, List< MapEntity >> loadEntities=null, IdRemap linkedRemap=null)
static Body CheckVisibility(Vector2 rayStart, Vector2 rayEnd, bool ignoreLevel=false, bool ignoreSubs=false, bool ignoreSensors=true, bool ignoreDisabledWalls=true, bool ignoreBranches=true, Predicate< Fixture > blocksVisibilityPredicate=null)
Check visibility between two points (in sim units).
override Vector2? Position
Wearable WearableComponent
ActionType
ActionTypes define when a StatusEffect is executed.