Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Characters/Limb.cs
1 using FarseerPhysics;
2 using FarseerPhysics.Dynamics;
3 using FarseerPhysics.Dynamics.Contacts;
4 using FarseerPhysics.Dynamics.Joints;
5 using Microsoft.Xna.Framework;
7 using System;
8 using System.Collections.Generic;
9 using System.Linq;
10 using System.Xml.Linq;
12 using LimbParams = Barotrauma.RagdollParams.LimbParams;
13 using JointParams = Barotrauma.RagdollParams.JointParams;
14 using Barotrauma.Abilities;
15 
16 namespace Barotrauma
17 {
18  public enum LimbType
19  {
22  };
23 
24  partial class LimbJoint
25  {
26  public bool IsSevered;
27  public bool CanBeSevered => Params.CanBeSevered;
28  public readonly JointParams Params;
29  public readonly Ragdoll ragdoll;
30  public readonly Limb LimbA, LimbB;
31 
32  public float Scale => Params.Scale * ragdoll.RagdollParams.JointScale;
33 
34  public readonly RevoluteJoint revoluteJoint;
35  public readonly WeldJoint weldJoint;
37 
38  public bool Enabled
39  {
40  get => Joint.Enabled;
41  set => Joint.Enabled = value;
42  }
43 
44  public Body BodyA => Joint.BodyA;
45 
46  public Body BodyB => Joint.BodyB;
47 
48  public Vector2 WorldAnchorA
49  {
50  get => Joint.WorldAnchorA;
51  set => Joint.WorldAnchorA = value;
52  }
53 
54  public Vector2 WorldAnchorB
55  {
56  get => Joint.WorldAnchorB;
57  set => Joint.WorldAnchorB = value;
58  }
59 
60  public Vector2 LocalAnchorA
61  {
62  get => revoluteJoint != null ? revoluteJoint.LocalAnchorA : weldJoint.LocalAnchorA;
63  set
64  {
65  if (weldJoint != null)
66  {
67  weldJoint.LocalAnchorA = value;
68  }
69  else
70  {
71  revoluteJoint.LocalAnchorA = value;
72  }
73  }
74  }
75 
76  public Vector2 LocalAnchorB
77  {
78  get => revoluteJoint != null ? revoluteJoint.LocalAnchorB : weldJoint.LocalAnchorB;
79  set
80  {
81  if (weldJoint != null)
82  {
83  weldJoint.LocalAnchorB = value;
84  }
85  else
86  {
87  revoluteJoint.LocalAnchorB = value;
88  }
89  }
90  }
91 
92  public bool LimitEnabled
93  {
94  get => revoluteJoint != null ? revoluteJoint.LimitEnabled : false;
95  set
96  {
97  if (revoluteJoint != null)
98  {
99  revoluteJoint.LimitEnabled = value;
100  }
101  }
102  }
103 
104  public float LowerLimit
105  {
106  get => revoluteJoint != null ? revoluteJoint.LowerLimit : 0;
107  set
108  {
109  if (revoluteJoint != null)
110  {
111  revoluteJoint.LowerLimit = value;
112  }
113  }
114  }
115 
116  public float UpperLimit
117  {
118  get => revoluteJoint != null ? revoluteJoint.UpperLimit : 0;
119  set
120  {
121  if (revoluteJoint != null)
122  {
123  revoluteJoint.UpperLimit = value;
124  }
125  }
126  }
127 
128  public float JointAngle => revoluteJoint != null ? revoluteJoint.JointAngle : weldJoint.ReferenceAngle;
129 
130  public LimbJoint(Limb limbA, Limb limbB, JointParams jointParams, Ragdoll ragdoll) : this(limbA, limbB, Vector2.One, Vector2.One, jointParams.WeldJoint)
131  {
132  Params = jointParams;
133  this.ragdoll = ragdoll;
134  LoadParams();
135  }
136 
137  public LimbJoint(Limb limbA, Limb limbB, Vector2 anchor1, Vector2 anchor2, bool weld = false)
138  {
139  if (weld)
140  {
141  weldJoint = new WeldJoint(limbA.body.FarseerBody, limbB.body.FarseerBody, anchor1, anchor2);
142  }
143  else
144  {
145  revoluteJoint = new RevoluteJoint(limbA.body.FarseerBody, limbB.body.FarseerBody, anchor1, anchor2)
146  {
147  MotorEnabled = true,
148  MaxMotorTorque = 0.25f
149  };
150  }
151  Joint.CollideConnected = false;
152  LimbA = limbA;
153  LimbB = limbB;
154  }
155 
156  public void LoadParams()
157  {
158  if (revoluteJoint != null)
159  {
160  revoluteJoint.MaxMotorTorque = Params.Stiffness;
161  revoluteJoint.LimitEnabled = Params.LimitEnabled;
162  }
163  if (float.IsNaN(Params.LowerLimit))
164  {
165  Params.LowerLimit = 0;
166  }
167  if (float.IsNaN(Params.UpperLimit))
168  {
169  Params.UpperLimit = 0;
170  }
171  if (ragdoll.IsFlipped)
172  {
173  if (weldJoint != null)
174  {
175  weldJoint.LocalAnchorA = ConvertUnits.ToSimUnits(new Vector2(-Params.Limb1Anchor.X, Params.Limb1Anchor.Y) * Scale);
176  weldJoint.LocalAnchorB = ConvertUnits.ToSimUnits(new Vector2(-Params.Limb2Anchor.X, Params.Limb2Anchor.Y) * Scale);
177  }
178  else
179  {
180  revoluteJoint.LocalAnchorA = ConvertUnits.ToSimUnits(new Vector2(-Params.Limb1Anchor.X, Params.Limb1Anchor.Y) * Scale);
181  revoluteJoint.LocalAnchorB = ConvertUnits.ToSimUnits(new Vector2(-Params.Limb2Anchor.X, Params.Limb2Anchor.Y) * Scale);
182  revoluteJoint.UpperLimit = MathHelper.ToRadians(-Params.LowerLimit);
183  revoluteJoint.LowerLimit = MathHelper.ToRadians(-Params.UpperLimit);
184  }
185  }
186  else
187  {
188  if (weldJoint != null)
189  {
190  weldJoint.LocalAnchorA = ConvertUnits.ToSimUnits(Params.Limb1Anchor * Scale);
191  weldJoint.LocalAnchorB = ConvertUnits.ToSimUnits(Params.Limb2Anchor * Scale);
192  }
193  else
194  {
195  revoluteJoint.LocalAnchorA = ConvertUnits.ToSimUnits(Params.Limb1Anchor * Scale);
196  revoluteJoint.LocalAnchorB = ConvertUnits.ToSimUnits(Params.Limb2Anchor * Scale);
197  revoluteJoint.UpperLimit = MathHelper.ToRadians(Params.UpperLimit);
198  revoluteJoint.LowerLimit = MathHelper.ToRadians(Params.LowerLimit);
199  }
200  }
201  }
202  }
203 
205  {
206  //how long it takes for severed limbs to fade out
207  public float SeveredFadeOutTime { get; private set; } = 10;
208 
209  public readonly Character character;
213  public readonly Ragdoll ragdoll;
214  public readonly LimbParams Params;
215 
216  //the physics body of the limb
218 
219  public Vector2 StepOffset => ConvertUnits.ToSimUnits(Params.StepOffset) * ragdoll.RagdollParams.JointScale;
220 
221  public Hull Hull;
222 
223  public bool InWater { get; set; }
224 
225  private FixedMouseJoint pullJoint;
226 
227  public readonly LimbType type;
228 
229  private bool ignoreCollisions;
230  public bool IgnoreCollisions
231  {
232  get { return ignoreCollisions; }
233  set
234  {
235  ignoreCollisions = value;
236  if (body != null)
237  {
238  if (ignoreCollisions)
239  {
240  body.CollisionCategories = Category.None;
241  body.CollidesWith = Category.None;
242  }
243  else
244  {
245  //limbs don't collide with each other
246  body.CollisionCategories = Physics.CollisionCharacter;
247  body.CollidesWith = Physics.CollisionAll & ~Physics.CollisionCharacter & ~Physics.CollisionItem & ~Physics.CollisionItemBlocking;
248  }
249  }
250  }
251  }
252 
253  private bool isSevered;
254  private float severedFadeOutTimer;
255 
256  private Vector2? mouthPos;
257  public Vector2 MouthPos
258  {
259  get
260  {
261  if (!mouthPos.HasValue)
262  {
263  mouthPos = Params.MouthPos;
264  }
265  return mouthPos.Value;
266  }
267  set
268  {
269  mouthPos = value;
270  }
271  }
272 
273  public readonly Attack attack;
274  public List<DamageModifier> DamageModifiers { get; private set; } = new List<DamageModifier>();
275 
276  private Direction dir;
277 
278  public int HealthIndex => Params.HealthIndex;
279  public float Scale => Params.Scale * Params.Ragdoll.LimbScale;
280  public float AttackPriority => Params.AttackPriority;
281  public bool DoesFlip
282  {
283  get
284  {
286  {
287  // Legs always has to flip when not swimming
288  return true;
289  }
290  return Params.Flip;
291  }
292  }
293 
294  public bool DoesMirror
295  {
296  get
297  {
298  if (IsLeg)
299  {
300  // Legs always has to mirror
301  return true;
302  }
303  return DoesFlip;
304  }
305  }
306 
307  public float SteerForce => Params.SteerForce;
308 
309  public Vector2 DebugTargetPos;
310  public Vector2 DebugRefPos;
311 
312  public bool IsLowerBody
313  {
314  get
315  {
316  switch (type)
317  {
318  case LimbType.LeftLeg:
319  case LimbType.RightLeg:
320  case LimbType.LeftFoot:
321  case LimbType.RightFoot:
322  case LimbType.Tail:
323  case LimbType.Legs:
324  case LimbType.LeftThigh:
325  case LimbType.RightThigh:
326  case LimbType.Waist:
327  return true;
328  default:
329  return false;
330  }
331  }
332  }
333 
334  public bool IsLeg
335  {
336  get
337  {
338  switch (type)
339  {
340  case LimbType.LeftFoot:
341  case LimbType.LeftLeg:
342  case LimbType.LeftThigh:
343  case LimbType.RightFoot:
344  case LimbType.RightLeg:
345  case LimbType.RightThigh:
346  return true;
347  default:
348  return false;
349  }
350  }
351  }
352 
353  public bool IsSevered
354  {
355  get { return isSevered; }
356  set
357  {
358  if (isSevered == value) { return; }
359  if (value == true)
360  {
361  // If any of the connected limbs have a longer fade out time, use that
362  var connectedLimbs = GetConnectedLimbs();
363  SeveredFadeOutTime = Math.Max(Params.SeveredFadeOutTime, connectedLimbs.Any() ? connectedLimbs.Max(l => l.SeveredFadeOutTime) : 0);
364  }
365  isSevered = value;
366  if (isSevered)
367  {
368  ragdoll.SubtractMass(this);
369  if (type == LimbType.Head)
370  {
371  character.Kill(CauseOfDeathType.Unknown, null);
372  }
373  }
374  else
375  {
376  severedFadeOutTimer = 0.0f;
377  }
378 #if CLIENT
379  if (isSevered)
380  {
381  damageOverlayStrength = 1.0f;
382  }
383 #endif
384  }
385  }
386 
388 
389  public bool Hidden
390  {
391  get => Params.Hide;
392  set => Params.Hide = value;
393  }
394 
395  public Vector2 WorldPosition
396  {
397  get { return character?.Submarine == null ? Position : Position + character.Submarine.Position; }
398  }
399 
400  public Vector2 Position
401  {
402  get { return ConvertUnits.ToDisplayUnits(body?.SimPosition ?? Vector2.Zero); }
403  }
404 
405  public Vector2 SimPosition
406  {
407  get
408  {
409  if (Removed)
410  {
411 #if DEBUG
412  DebugConsole.ThrowError("Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace());
413 #endif
414  GameAnalyticsManager.AddErrorEventOnce("Limb.LinearVelocity:SimPosition", GameAnalyticsManager.ErrorSeverity.Error,
415  "Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace());
416  return Vector2.Zero;
417  }
418  return body.SimPosition;
419  }
420  }
421 
422 
423  public Vector2 DrawPosition
424  {
425  get
426  {
427  if (Removed)
428  {
429 #if DEBUG
430  DebugConsole.ThrowError("Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace());
431 #endif
432  GameAnalyticsManager.AddErrorEventOnce("Limb.LinearVelocity:DrawPosition", GameAnalyticsManager.ErrorSeverity.Error,
433  "Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace());
434  return Vector2.Zero;
435  }
436  return body.DrawPosition;
437  }
438  }
439 
440  public float Rotation
441  {
442  get
443  {
444  if (Removed)
445  {
446 #if DEBUG
447  DebugConsole.ThrowError("Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace());
448 #endif
449  GameAnalyticsManager.AddErrorEventOnce("Limb.LinearVelocity:SimPosition", GameAnalyticsManager.ErrorSeverity.Error,
450  "Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace());
451  return 0.0f;
452  }
453  return body.Rotation;
454  }
455  }
456 
457  //where an animcontroller is trying to pull the limb, only used for debug visualization
458  public Vector2 AnimTargetPos { get; private set; }
459 
460  public float Mass
461  {
462  get
463  {
464  if (Removed)
465  {
466 #if DEBUG
467  DebugConsole.ThrowError("Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace());
468 #endif
469  GameAnalyticsManager.AddErrorEventOnce("Limb.Mass:AccessRemoved", GameAnalyticsManager.ErrorSeverity.Error,
470  "Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace());
471  return 1.0f;
472  }
473  return body.Mass;
474  }
475  }
476 
477  public bool Disabled { get; set; }
478 
479  public Vector2 LinearVelocity
480  {
481  get
482  {
483  if (Removed)
484  {
485 #if DEBUG
486  DebugConsole.ThrowError("Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace());
487 #endif
488  GameAnalyticsManager.AddErrorEventOnce("Limb.LinearVelocity:AccessRemoved", GameAnalyticsManager.ErrorSeverity.Error,
489  "Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace());
490  return Vector2.Zero;
491  }
492  return body.LinearVelocity;
493  }
494  }
495 
496  public float Dir
497  {
498  get { return (dir == Direction.Left) ? -1.0f : 1.0f; }
499  set
500  {
501  dir = (value == -1.0f) ? Direction.Left : Direction.Right;
502  if (body != null)
503  {
504  body.Dir = Dir;
505  }
506  }
507  }
508 
509  private float _alpha = 1.0f;
513  public float Alpha
514  {
515  get => _alpha;
516  set
517  {
518  _alpha = MathHelper.Clamp(value, 0.0f, 1.0f);
519  }
520  }
521 
522  public int RefJointIndex => Params.RefJoint;
523 
524  public readonly List<WearableSprite> WearingItems = new List<WearableSprite>();
525 
526  public readonly List<WearableSprite> OtherWearables = new List<WearableSprite>();
527 
528  public bool PullJointEnabled
529  {
530  get { return pullJoint.Enabled; }
531  set { pullJoint.Enabled = value; }
532  }
533 
534  public float PullJointMaxForce
535  {
536  get { return pullJoint.MaxForce; }
537  set { pullJoint.MaxForce = value; }
538  }
539 
540  public Vector2 PullJointWorldAnchorA
541  {
542  get { return pullJoint.WorldAnchorA; }
543  set
544  {
545  if (!MathUtils.IsValid(value))
546  {
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);
549 #if DEBUG
550  DebugConsole.ThrowError(errorMsg);
551 #endif
552  return;
553  }
554 
555  if (Vector2.DistanceSquared(SimPosition, value) > 50.0f * 50.0f)
556  {
557  Vector2 diff = value - SimPosition;
558  string errorMsg = "Attempted to move the anchor A of a limb's pull joint extremely far from the limb (diff: " + diff +
559  ", limb enabled: " + body.Enabled +
560  ", simple physics enabled: " + character.AnimController.SimplePhysicsEnabled + ")\n"
561  + Environment.StackTrace.CleanupStackTrace();
562  GameAnalyticsManager.AddErrorEventOnce("Limb.SetPullJointAnchorA:ExcessiveValue", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
563 #if DEBUG
564  DebugConsole.ThrowError(errorMsg);
565 #endif
566  return;
567  }
568 
569  pullJoint.WorldAnchorA = value;
570  }
571  }
572 
573  public Vector2 PullJointWorldAnchorB
574  {
575  get { return pullJoint.WorldAnchorB; }
576  set
577  {
578  if (!MathUtils.IsValid(value))
579  {
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);
582 #if DEBUG
583  DebugConsole.ThrowError(errorMsg);
584 #endif
585  return;
586  }
587 
588  if (Vector2.DistanceSquared(pullJoint.WorldAnchorA, value) > 50.0f * 50.0f)
589  {
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 +
592  ", limb enabled: " + body.Enabled +
593  ", simple physics enabled: " + character.AnimController.SimplePhysicsEnabled + ")\n"
594  + Environment.StackTrace.CleanupStackTrace();
595  GameAnalyticsManager.AddErrorEventOnce("Limb.SetPullJointAnchorB:ExcessiveValue", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
596 #if DEBUG
597  DebugConsole.ThrowError(errorMsg);
598 #endif
599  return;
600  }
601 
602  pullJoint.WorldAnchorB = value;
603  }
604  }
605 
606  public Vector2 PullJointLocalAnchorA
607  {
608  get { return pullJoint.LocalAnchorA; }
609  }
610 
611  public bool Removed
612  {
613  get;
614  private set;
615  }
616 
617  public Items.Components.Rope AttachedRope { get; set; }
618 
619  public string Name => Params.Name;
620 
621  // These properties are exposed for status effects
622  public bool IsDead => character.IsDead;
623  public float Health => character.Health;
625  public bool IsHuman => character.IsHuman;
626 
627  public AIState AIState => character.AIController is EnemyAIController enemyAI ? enemyAI.State : AIState.Idle;
629 
630  public bool CanBeSeveredAlive
631  {
632  get
633  {
634  if (character.IsHumanoid) { return false; }
635  // TODO: We might need this or solve the cases where a limb is severed while holding on to an item
636  //if (character.Params.CanInteract) { return false; }
637  if (this == character.AnimController.MainLimb) { return false; }
638  bool canBeSevered = Params.CanBeSeveredAlive;
640  {
641  switch (type)
642  {
643  case LimbType.LeftFoot:
644  case LimbType.RightFoot:
645  case LimbType.LeftLeg:
646  case LimbType.RightLeg:
647  case LimbType.LeftThigh:
648  case LimbType.RightThigh:
649  case LimbType.Legs:
650  case LimbType.Waist:
651  return false;
652  }
653  }
654  return canBeSevered;
655  }
656  }
657 
658  public Dictionary<Identifier, SerializableProperty> SerializableProperties
659  {
660  get;
661  private set;
662  }
663 
664  private readonly Dictionary<ActionType, List<StatusEffect>> statusEffects = new Dictionary<ActionType, List<StatusEffect>>();
665 
666  public Dictionary<ActionType, List<StatusEffect>> StatusEffects { get { return statusEffects; } }
667 
668  public Limb(Ragdoll ragdoll, Character character, LimbParams limbParams)
669  {
670  this.ragdoll = ragdoll;
671  this.character = character;
672  this.Params = limbParams;
673  dir = Direction.Right;
674  body = new PhysicsBody(limbParams);
675  type = limbParams.Type;
676  IgnoreCollisions = limbParams.IgnoreCollisions;
677  body.UserData = this;
678  pullJoint = new FixedMouseJoint(body.FarseerBody, ConvertUnits.ToSimUnits(limbParams.PullPos * Scale))
679  {
680  Enabled = false,
681  //MaxForce = ((type == LimbType.LeftHand || type == LimbType.RightHand) ? 400.0f : 150.0f) * body.Mass
682  // 150 or even 400 is too low if the joint is used for moving the character position from the mainlimb towards the collider position
683  MaxForce = 1000 * Mass
684  };
685 
686  GameMain.World.Add(pullJoint);
687 
688  var element = limbParams.Element;
689 
690  body.BodyType = BodyType.Dynamic;
691 
692  foreach (var subElement in element.Elements())
693  {
694  switch (subElement.Name.ToString().ToLowerInvariant())
695  {
696  case "attack":
697  attack = new Attack(subElement, (character == null ? "null" : character.Name) + ", limb " + type);
698  if (attack.DamageRange <= 0)
699  {
700  switch (body.BodyShape)
701  {
702  case PhysicsBody.Shape.Circle:
704  break;
705  case PhysicsBody.Shape.Capsule:
707  break;
708  case PhysicsBody.Shape.Rectangle:
709  attack.DamageRange = new Vector2(body.Width / 2.0f, body.Height / 2.0f).Length();
710  break;
711  }
712  attack.DamageRange = ConvertUnits.ToDisplayUnits(attack.DamageRange);
713  }
714  if (character is { VariantOf.IsEmpty: false })
715  {
716  var attackElement = character.Params.VariantFile.GetRootExcludingOverride().GetChildElement("attack");
717  if (attackElement != null)
718  {
719  attack.DamageMultiplier = attackElement.GetAttributeFloat("damagemultiplier", 1f);
720  attack.RangeMultiplier = attackElement.GetAttributeFloat("rangemultiplier", 1f);
721  attack.ImpactMultiplier = attackElement.GetAttributeFloat("impactmultiplier", 1f);
722  }
723  }
724  break;
725  case "damagemodifier":
726  DamageModifiers.Add(new DamageModifier(subElement, character.Name));
727  break;
728  case "statuseffect":
729  var statusEffect = StatusEffect.Load(subElement, character.Name + ", " + Name);
730  if (statusEffect != null)
731  {
732  if (!statusEffects.ContainsKey(statusEffect.type))
733  {
734  statusEffects.Add(statusEffect.type, new List<StatusEffect>());
735  }
736  statusEffects[statusEffect.type].Add(statusEffect);
737  }
738  break;
739  }
740  }
741 
743 
744  InitProjSpecific(element);
745  }
746  partial void InitProjSpecific(ContentXElement element);
747 
748  public void MoveToPos(Vector2 pos, float force, bool pullFromCenter = false)
749  {
750  Vector2 pullPos = body.SimPosition;
751  if (!pullFromCenter)
752  {
753  pullPos = pullJoint.WorldAnchorA;
754  }
755 
756  AnimTargetPos = pos;
757 
758  body.MoveToPos(pos, force, pullPos);
759  }
760 
761  public void MirrorPullJoint()
762  {
763  pullJoint.LocalAnchorA = new Vector2(-pullJoint.LocalAnchorA.X, pullJoint.LocalAnchorA.Y);
764  }
765 
766  public AttackResult AddDamage(Vector2 simPosition, float damage, float bleedingDamage, float burnDamage, bool playSound)
767  {
768  List<Affliction> afflictions = new List<Affliction>();
769  if (damage > 0.0f) afflictions.Add(AfflictionPrefab.InternalDamage.Instantiate(damage));
770  if (bleedingDamage > 0.0f) afflictions.Add(AfflictionPrefab.Bleeding.Instantiate(bleedingDamage));
771  if (burnDamage > 0.0f) afflictions.Add(AfflictionPrefab.Burn.Instantiate(burnDamage));
772 
773  return AddDamage(simPosition, afflictions, playSound);
774  }
775 
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)
780  {
781  appliedDamageModifiers.Clear();
782  afflictionsCopy.Clear();
783  foreach (var affliction in afflictions)
784  {
785  tempModifiers.Clear();
786  var newAffliction = affliction;
787  float random = Rand.Value(Rand.RandSync.Unsynced);
788  bool foundMatchingModifier = false;
789  bool applyAffliction = true;
790  foreach (DamageModifier damageModifier in DamageModifiers)
791  {
792  if (!damageModifier.MatchesAffliction(affliction)) { continue; }
793  foundMatchingModifier = true;
794  if (random > affliction.Probability * damageModifier.ProbabilityMultiplier)
795  {
796  applyAffliction = false;
797  continue;
798  }
799  if (SectorHit(damageModifier.ArmorSectorInRadians, simPosition))
800  {
801  tempModifiers.Add(damageModifier);
802  }
803  }
804  foreach (WearableSprite wearable in WearingItems)
805  {
806  foreach (DamageModifier damageModifier in wearable.WearableComponent.DamageModifiers)
807  {
808  if (!damageModifier.MatchesAffliction(affliction)) { continue; }
809  foundMatchingModifier = true;
810  if (random > affliction.Probability * damageModifier.ProbabilityMultiplier)
811  {
812  applyAffliction = false;
813  continue;
814  }
815  if (SectorHit(damageModifier.ArmorSectorInRadians, simPosition))
816  {
817  tempModifiers.Add(damageModifier);
818  }
819  }
820  }
821  if (!foundMatchingModifier && random > affliction.Probability) { continue; }
822  float finalDamageModifier = damageMultiplier;
823  if (character.EmpVulnerability > 0 && affliction.Prefab.AfflictionType == AfflictionPrefab.EMPType)
824  {
825  finalDamageModifier *= character.EmpVulnerability;
826  }
828  {
829  if (affliction.Prefab.AfflictionType == AfflictionPrefab.PoisonType || affliction.Prefab.AfflictionType == AfflictionPrefab.ParalysisType)
830  {
831  finalDamageModifier *= character.PoisonVulnerability;
832  }
833  }
834  foreach (DamageModifier damageModifier in tempModifiers)
835  {
836  float damageModifierValue = damageModifier.DamageMultiplier;
837  if (damageModifier.DeflectProjectiles && damageModifierValue < 1f)
838  {
839  damageModifierValue = MathHelper.Lerp(damageModifierValue, 1f, penetration);
840  }
841  finalDamageModifier *= damageModifierValue;
842  }
843  if (affliction.MultiplyByMaxVitality)
844  {
845  finalDamageModifier *= character.MaxVitality / 100f;
846  }
847  if (!MathUtils.NearlyEqual(finalDamageModifier, 1.0f))
848  {
849  newAffliction = affliction.CreateMultiplied(finalDamageModifier, affliction);
850  }
851  else
852  {
853  newAffliction.SetStrength(affliction.NonClampedStrength);
854  }
855  if (attacker != null)
856  {
857  var abilityAfflictionCharacter = new AbilityAfflictionCharacter(newAffliction, character);
858  attacker.CheckTalents(AbilityEffectType.OnAddDamageAffliction, abilityAfflictionCharacter);
859  newAffliction = abilityAfflictionCharacter.Affliction;
860  }
861  if (applyAffliction)
862  {
863  afflictionsCopy.Add(newAffliction);
864  newAffliction.Source ??= attacker;
865  }
866  appliedDamageModifiers.AddRange(tempModifiers);
867  }
868  var result = new AttackResult(afflictionsCopy, this, appliedDamageModifiers);
869  if (result.Afflictions.None())
870  {
871  playSound = false;
872  }
873  AddDamageProjSpecific(playSound, result);
874 
875  float bleedingDamage = 0;
877  {
878  foreach (var affliction in result.Afflictions)
879  {
880  if (affliction is AfflictionBleeding)
881  {
882  bleedingDamage += affliction.GetVitalityDecrease(character.CharacterHealth);
883  }
884  }
885  if (bleedingDamage > 0)
886  {
887  float bloodDecalSize = MathHelper.Clamp(bleedingDamage / 5, 0.1f, 1.0f);
888  if (character.CurrentHull != null && !string.IsNullOrEmpty(character.BloodDecalName))
889  {
890  character.CurrentHull.AddDecal(character.BloodDecalName, WorldPosition, MathHelper.Clamp(bloodDecalSize, 0.5f, 1.0f), isNetworkEvent: false);
891  }
892  }
893  }
894 
895  return result;
896  }
897 
898  partial void AddDamageProjSpecific(bool playSound, AttackResult result);
899 
900  public bool SectorHit(Vector2 armorSector, Vector2 simPosition)
901  {
902  if (armorSector == Vector2.Zero) { return false; }
903  //sector 360 degrees or more -> always hits
904  if (Math.Abs(armorSector.Y - armorSector.X) >= MathHelper.TwoPi) { return true; }
905  float rotation = body.TransformedRotation;
906  float offset = (MathHelper.PiOver2 - MathUtils.GetMidAngle(armorSector.X, armorSector.Y)) * Dir;
907  float hitAngle = VectorExtensions.Angle(VectorExtensions.Forward(rotation + offset), SimPosition - simPosition);
908  float sectorSize = GetArmorSectorSize(armorSector);
909  return hitAngle < sectorSize / 2;
910  }
911 
912  protected float GetArmorSectorSize(Vector2 armorSector)
913  {
914  return Math.Abs(armorSector.X - armorSector.Y);
915  }
916 
917  public void Update(float deltaTime)
918  {
919  UpdateProjSpecific(deltaTime);
920  ApplyStatusEffects(ActionType.Always, deltaTime);
921  ApplyStatusEffects(ActionType.OnActive, deltaTime);
922 
923  if (InWater)
924  {
926  }
927 
928  if (isSevered)
929  {
930  severedFadeOutTimer += deltaTime;
931  if (severedFadeOutTimer >= SeveredFadeOutTime)
932  {
933  body.Enabled = false;
934  }
935  else if (character.CurrentHull == null && Hull.FindHull(WorldPosition) != null)
936  {
937  severedFadeOutTimer = SeveredFadeOutTime;
938  }
939  }
940  else if (!IsDead)
941  {
942  if (Params.BlinkFrequency > 0)
943  {
944  if (BlinkTimer > -TotalBlinkDurationOut)
945  {
946  BlinkTimer -= deltaTime;
947  }
948  else
949  {
950  BlinkTimer = Params.BlinkFrequency;
951  }
952  }
953  if (reEnableTimer > 0)
954  {
955  reEnableTimer -= deltaTime;
956  }
957  else if (reEnableTimer > -1)
958  {
959  ReEnable();
960  }
961  }
962 
963  attack?.UpdateCoolDown(deltaTime);
964  }
965 
966  private bool temporarilyDisabled;
967  private float reEnableTimer = -1;
968  private bool originalIgnoreCollisions;
969  public void HideAndDisable(float duration = 0, bool ignoreCollisions = true)
970  {
971  if (Hidden || Disabled) { return; }
972  temporarilyDisabled = true;
973  Hidden = true;
974  Disabled = true;
975  originalIgnoreCollisions = IgnoreCollisions;
976  IgnoreCollisions = ignoreCollisions;
977  if (duration > 0)
978  {
979  reEnableTimer = duration;
980  }
981 #if CLIENT
982  if (Hidden && LightSource != null)
983  {
984  LightSource.Enabled = false;
985  }
986 #endif
987  }
988 
989  public void ReEnable()
990  {
991  if (!temporarilyDisabled) { return; }
992  Hidden = false;
993  Disabled = false;
994  IgnoreCollisions = originalIgnoreCollisions;
995  reEnableTimer = -1;
996  }
997 
998  partial void UpdateProjSpecific(float deltaTime);
999 
1000  private readonly List<Body> contactBodies = new List<Body>();
1004  public bool UpdateAttack(float deltaTime, Vector2 attackSimPos, IDamageable damageTarget, out AttackResult attackResult, float distance = -1, Limb targetLimb = null)
1005  {
1006  attackResult = default;
1008  float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(simPos, attackSimPos));
1009  bool wasRunning = attack.IsRunning;
1010  attack.UpdateAttackTimer(deltaTime, character);
1011  if (attack.Blink)
1012  {
1013  if (attack.ForceOnLimbIndices != null && attack.ForceOnLimbIndices.Any())
1014  {
1015  foreach (int limbIndex in attack.ForceOnLimbIndices)
1016  {
1017  if (limbIndex < 0 || limbIndex >= character.AnimController.Limbs.Length) { continue; }
1018  Limb limb = character.AnimController.Limbs[limbIndex];
1019  if (limb.IsSevered) { continue; }
1020  limb.Blink();
1021  }
1022  }
1023  else
1024  {
1025  Blink();
1026  }
1027  }
1028 
1029  bool wasHit = false;
1030  Body structureBody = null;
1031  if (damageTarget != null)
1032  {
1033  switch (attack.HitDetectionType)
1034  {
1035  case HitDetection.Distance:
1036  if (dist < attack.DamageRange)
1037  {
1038  Vector2 rayStart = simPos;
1039  Vector2 rayEnd = attackSimPos;
1040  if (Submarine == null && damageTarget is ISpatialEntity spatialEntity && spatialEntity.Submarine != null)
1041  {
1042  rayStart -= spatialEntity.Submarine.SimPosition;
1043  rayEnd -= spatialEntity.Submarine.SimPosition;
1044  }
1045  structureBody = Submarine.CheckVisibility(rayStart, rayEnd);
1046  if (damageTarget is Item i && i.GetComponent<Items.Components.Door>() != null)
1047  {
1048  // If the attack is aimed to an item and hits an item, it's successful.
1049  // Ignore blocking checks on doors, because it causes cases where a Mudraptor cannot hit the hatch, for example.
1050  wasHit = true;
1051  }
1052  else if (damageTarget is Structure wall && structureBody != null &&
1053  (structureBody.UserData is Structure || (structureBody.UserData is Submarine sub && sub == wall.Submarine)))
1054  {
1055  // If the attack is aimed to a structure (wall) and hits a structure or the sub, it's successful
1056  wasHit = true;
1057  }
1058  else
1059  {
1060  // If there is nothing between, the hit is successful
1061  wasHit = structureBody == null;
1062  }
1063  }
1064  break;
1065  case HitDetection.Contact:
1066  contactBodies.Clear();
1067  if (damageTarget is Character targetCharacter)
1068  {
1069  foreach (Limb limb in targetCharacter.AnimController.Limbs)
1070  {
1071  if (!limb.IsSevered && limb.body?.FarseerBody != null) contactBodies.Add(limb.body.FarseerBody);
1072  }
1073  }
1074  else if (damageTarget is Structure targetStructure)
1075  {
1076  if (character.Submarine == null && targetStructure.Submarine != null)
1077  {
1078  contactBodies.Add(targetStructure.Submarine.PhysicsBody.FarseerBody);
1079  }
1080  else
1081  {
1082  contactBodies.AddRange(targetStructure.Bodies);
1083  }
1084  }
1085  else if (damageTarget is Item)
1086  {
1087  Item targetItem = damageTarget as Item;
1088  if (targetItem.body?.FarseerBody != null) contactBodies.Add(targetItem.body.FarseerBody);
1089  }
1090  ContactEdge contactEdge = body.FarseerBody.ContactList;
1091  while (contactEdge != null)
1092  {
1093  if (contactEdge.Contact != null &&
1094  contactEdge.Contact.IsTouching &&
1095  contactBodies.Any(b => b == contactEdge.Contact.FixtureA?.Body || b == contactEdge.Contact.FixtureB?.Body))
1096  {
1097  structureBody = contactBodies.LastOrDefault();
1098  wasHit = true;
1099  break;
1100  }
1101  contactEdge = contactEdge.Next;
1102  }
1103  break;
1104  }
1105  }
1106 
1107  if (wasHit)
1108  {
1109  wasHit = damageTarget != null;
1110  }
1111 
1112  if (wasHit || attack.HitDetectionType == HitDetection.None)
1113  {
1115  {
1116  ExecuteAttack(damageTarget, targetLimb, out attackResult);
1117  }
1118 #if SERVER
1120  attackLimb: this, targetEntity: damageTarget, targetLimb: targetLimb,
1121  targetSimPos: attackSimPos));
1122 #endif
1123  }
1124 
1125  Vector2 diff = attackSimPos - SimPosition;
1126  bool applyForces = !attack.ApplyForcesOnlyOnce || !wasRunning;
1127 
1128  if (applyForces)
1129  {
1130  if (attack.ForceOnLimbIndices != null && attack.ForceOnLimbIndices.Count > 0)
1131  {
1132  foreach (int limbIndex in attack.ForceOnLimbIndices)
1133  {
1134  if (limbIndex < 0 || limbIndex >= character.AnimController.Limbs.Length) { continue; }
1135  Limb limb = character.AnimController.Limbs[limbIndex];
1136  if (limb.IsSevered) { continue; }
1137  diff = attackSimPos - limb.SimPosition;
1138  if (diff == Vector2.Zero) { continue; }
1139  limb.body.ApplyTorque(limb.Mass * character.AnimController.Dir * attack.Torque * limb.Params.AttackForceMultiplier);
1140  Vector2 forcePos = limb.pullJoint == null ? limb.body.SimPosition : limb.pullJoint.WorldAnchorA;
1141  limb.body.ApplyLinearImpulse(limb.Mass * attack.Force * limb.Params.AttackForceMultiplier * Vector2.Normalize(diff), forcePos, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
1142  }
1143  }
1144  else if (diff != Vector2.Zero)
1145  {
1146  body.ApplyTorque(Mass * character.AnimController.Dir * attack.Torque * Params.AttackForceMultiplier);
1147  Vector2 forcePos = pullJoint == null ? body.SimPosition : pullJoint.WorldAnchorA;
1148  body.ApplyLinearImpulse(Mass * attack.Force * Params.AttackForceMultiplier * Vector2.Normalize(diff), forcePos, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
1149  }
1150  }
1152  forceWorld.X *= character.AnimController.Dir;
1153  character.AnimController.MainLimb.body.ApplyLinearImpulse(character.Mass * forceWorld, character.SimPosition, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
1154  if (!attack.IsRunning && !attack.Ranged)
1155  {
1156  // Set the main collider where the body lands after the attack
1157  if (Vector2.DistanceSquared(character.AnimController.Collider.SimPosition, character.AnimController.MainLimb.body.SimPosition) > 0.1f * 0.1f)
1158  {
1160  }
1161  }
1162  return wasHit;
1163  }
1164 
1165  public void ExecuteAttack(IDamageable damageTarget, Limb targetLimb, out AttackResult attackResult)
1166  {
1167  bool playSound = false;
1168 #if CLIENT
1169  playSound = LastAttackSoundTime < Timing.TotalTime - SoundInterval;
1170  if (playSound)
1171  {
1173  }
1174 #endif
1175  if (damageTarget is Character targetCharacter && targetLimb != null)
1176  {
1177  attackResult = attack.DoDamageToLimb(character, targetLimb, WorldPosition, 1.0f, playSound, body, this);
1178  }
1179  else
1180  {
1181  if (damageTarget is Item targetItem && !targetItem.Prefab.DamagedByMonsters)
1182  {
1183  attackResult = new AttackResult();
1184  }
1185  else
1186  {
1187  attackResult = attack.DoDamage(character, damageTarget, WorldPosition, 1.0f, playSound, body, this);
1188  }
1189  }
1190  /*if (structureBody != null && attack.StickChance > Rand.Range(0.0f, 1.0f, Rand.RandSync.ServerAndClient))
1191  {
1192  // TODO: use the hit pos?
1193  var localFront = body.GetLocalFront(Params.GetSpriteOrientation());
1194  var from = body.FarseerBody.GetWorldPoint(localFront);
1195  var to = from;
1196  var drawPos = body.DrawPosition;
1197  StickTo(structureBody, from, to);
1198  }*/
1200  attack.SetCoolDown(applyRandom: !character.IsPlayer);
1201  }
1202 
1203  private WeldJoint attachJoint;
1204  private WeldJoint colliderJoint;
1205  public bool IsStuck => attachJoint != null;
1206 
1211  private void StickTo(Body target, Vector2 from, Vector2 to)
1212  {
1213  if (attachJoint != null)
1214  {
1215  // Already attached to the target body, no need to do anything
1216  if (attachJoint.BodyB == target) { return; }
1217  Release();
1218  }
1219 
1220  if (!ragdoll.IsStuck)
1221  {
1222  PhysicsBody mainLimbBody = ragdoll.MainLimb.body;
1223  Body colliderBody = ragdoll.Collider.FarseerBody;
1224  Vector2 mainLimbLocalFront = mainLimbBody.GetLocalFront(ragdoll.MainLimb.Params.GetSpriteOrientation());
1225  if (Dir < 0)
1226  {
1227  mainLimbLocalFront.X = -mainLimbLocalFront.X;
1228  }
1229  Vector2 mainLimbFront = mainLimbBody.FarseerBody.GetWorldPoint(mainLimbLocalFront);
1230  colliderBody.SetTransform(mainLimbBody.SimPosition, mainLimbBody.Rotation);
1231  // Attach the collider to the main body so that they don't go out of sync (TODO: why is the collider still rotated 90d off?)
1232  colliderJoint = new WeldJoint(colliderBody, mainLimbBody.FarseerBody, mainLimbFront, mainLimbFront, true)
1233  {
1234  KinematicBodyB = true,
1235  CollideConnected = false
1236  };
1237  GameMain.World.Add(colliderJoint);
1238  }
1239 
1240  attachJoint = new WeldJoint(body.FarseerBody, target, from, to, true)
1241  {
1242  FrequencyHz = 1,
1243  DampingRatio = 0.5f,
1244  KinematicBodyB = true,
1245  CollideConnected = false
1246  };
1247  GameMain.World.Add(attachJoint);
1248  }
1249 
1250  public void Release()
1251  {
1252  if (!IsStuck) { return; }
1253  GameMain.World.Remove(attachJoint);
1254  attachJoint = null;
1255  if (colliderJoint != null)
1256  {
1257  GameMain.World.Remove(colliderJoint);
1258  colliderJoint = null;
1259  }
1260  }
1261 
1262  private readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
1263  public void ApplyStatusEffects(ActionType actionType, float deltaTime)
1264  {
1265  if (!statusEffects.TryGetValue(actionType, out var statusEffectList)) { return; }
1266  foreach (StatusEffect statusEffect in statusEffectList)
1267  {
1268  if (statusEffect.ShouldWaitForInterval(character, deltaTime)) { return; }
1269 
1270  statusEffect.sourceBody = body;
1271  if (statusEffect.type == ActionType.OnDamaged)
1272  {
1273  if (!statusEffect.HasRequiredAfflictions(character.LastDamage)) { continue; }
1274  if (statusEffect.OnlyWhenDamagedByPlayer)
1275  {
1277  {
1278  continue;
1279  }
1280  }
1281  }
1282  if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
1283  statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
1284  {
1285  targets.Clear();
1286  statusEffect.AddNearbyTargets(WorldPosition, targets);
1287  statusEffect.Apply(actionType, deltaTime, character, targets);
1288  }
1289  else if (statusEffect.targetLimbs != null)
1290  {
1291  foreach (var limbType in statusEffect.targetLimbs)
1292  {
1293  if (statusEffect.HasTargetType(StatusEffect.TargetType.AllLimbs))
1294  {
1295  // Target all matching limbs
1296  foreach (var limb in ragdoll.Limbs)
1297  {
1298  if (limb.IsSevered) { continue; }
1299  if (limb.type == limbType)
1300  {
1301  ApplyToLimb(actionType, deltaTime, statusEffect, character, limb);
1302  }
1303  }
1304  }
1305  else if (statusEffect.HasTargetType(StatusEffect.TargetType.Limb) || statusEffect.HasTargetType(StatusEffect.TargetType.Character) || statusEffect.HasTargetType(StatusEffect.TargetType.This))
1306  {
1307  // Target just the first matching limb
1308  Limb limb = ragdoll.GetLimb(limbType);
1309  if (limb != null)
1310  {
1311  ApplyToLimb(actionType, deltaTime, statusEffect, character, limb);
1312  }
1313  }
1314  else if (statusEffect.HasTargetType(StatusEffect.TargetType.LastLimb))
1315  {
1316  // Target just the last matching limb
1317  Limb limb = ragdoll.Limbs.LastOrDefault(l => l.type == limbType && !l.IsSevered && !l.Hidden);
1318  if (limb != null)
1319  {
1320  ApplyToLimb(actionType, deltaTime, statusEffect, character, limb);
1321  }
1322  }
1323  }
1324  }
1325  else if (statusEffect.HasTargetType(StatusEffect.TargetType.AllLimbs))
1326  {
1327  // Target all limbs
1328  foreach (var limb in ragdoll.Limbs)
1329  {
1330  if (limb.IsSevered) { continue; }
1331  ApplyToLimb(actionType, deltaTime, statusEffect, character, limb);
1332  }
1333  }
1334  else if (statusEffect.HasTargetType(StatusEffect.TargetType.Character))
1335  {
1336  statusEffect.Apply(actionType, deltaTime, character, character, WorldPosition);
1337  }
1338  else if (statusEffect.HasTargetType(StatusEffect.TargetType.This) || statusEffect.HasTargetType(StatusEffect.TargetType.Limb))
1339  {
1340  ApplyToLimb(actionType, deltaTime, statusEffect, character, limb: this);
1341  }
1342  }
1343  static void ApplyToLimb(ActionType actionType, float deltaTime, StatusEffect statusEffect, Character character, Limb limb)
1344  {
1345  statusEffect.sourceBody = limb.body;
1346  statusEffect.Apply(actionType, deltaTime, entity: character, target: limb);
1347  }
1348  }
1349 
1350  public float BlinkTimer { get; private set; }
1351  public float BlinkPhase { get; set; }
1352 
1353  public bool FreezeBlinkState;
1354 
1355  private float TotalBlinkDurationOut => Params.BlinkDurationOut + Params.BlinkHoldTime;
1356 
1357  public void Blink()
1358  {
1359  BlinkTimer = -TotalBlinkDurationOut;
1360  }
1361 
1362  public void UpdateBlink(float deltaTime, float referenceRotation)
1363  {
1364  if (BlinkTimer > -TotalBlinkDurationOut)
1365  {
1366  if (!FreezeBlinkState)
1367  {
1368  BlinkPhase -= deltaTime;
1369  }
1370  if (BlinkPhase > 0)
1371  {
1372  // in
1373  float t = ToolBox.GetEasing(Params.BlinkTransitionIn, MathUtils.InverseLerp(1, 0, BlinkPhase / Params.BlinkDurationIn));
1374  body.SmoothRotate(referenceRotation + MathHelper.ToRadians(Params.BlinkRotationIn) * Dir, Mass * Params.BlinkForce * t, wrapAngle: true);
1375  if (Params.UseTextureOffsetForBlinking)
1376  {
1377 #if CLIENT
1378  ActiveSprite.RelativeOrigin = Vector2.Lerp(Params.BlinkTextureOffsetOut, Params.BlinkTextureOffsetIn, t);
1379 #endif
1380  }
1381  }
1382  else
1383  {
1384  if (Math.Abs(BlinkPhase) < Params.BlinkHoldTime)
1385  {
1386  // hold
1387  body.SmoothRotate(referenceRotation + MathHelper.ToRadians(Params.BlinkRotationIn) * Dir, Mass * Params.BlinkForce, wrapAngle: true);
1388  }
1389  else
1390  {
1391  // out
1392  //float t = ToolBox.GetEasing(Params.BlinkTransitionOut, MathUtils.InverseLerp(0, 1, -blinkPhase / TotalBlinkDurationOut));
1393  float t = ToolBox.GetEasing(Params.BlinkTransitionOut, MathUtils.InverseLerp(0, 1, (-BlinkPhase - Params.BlinkHoldTime) / Params.BlinkDurationOut));
1394  body.SmoothRotate(referenceRotation + MathHelper.ToRadians(Params.BlinkRotationOut) * Dir, Mass * Params.BlinkForce * t, wrapAngle: true);
1395  if (Params.UseTextureOffsetForBlinking)
1396  {
1397 #if CLIENT
1398  ActiveSprite.RelativeOrigin = Vector2.Lerp(Params.BlinkTextureOffsetIn, Params.BlinkTextureOffsetOut, t);
1399 #endif
1400  }
1401  }
1402  }
1403  }
1404  else
1405  {
1406  // out
1407  if (!FreezeBlinkState)
1408  {
1409  BlinkPhase = Params.BlinkDurationIn;
1410  }
1411  body.SmoothRotate(referenceRotation + MathHelper.ToRadians(Params.BlinkRotationOut) * Dir, Mass * Params.BlinkForce, wrapAngle: true);
1412  }
1413  }
1414 
1415  public IEnumerable<LimbJoint> GetConnectedJoints() => ragdoll.LimbJoints.Where(j => !j.IsSevered && (j.LimbA == this || j.LimbB == this));
1416 
1417  public IEnumerable<Limb> GetConnectedLimbs()
1418  {
1419  var connectedJoints = GetConnectedJoints();
1420  var connectedLimbs = new HashSet<Limb>();
1421  foreach (Limb limb in ragdoll.Limbs)
1422  {
1423  var otherJoints = limb.GetConnectedJoints();
1424  foreach (LimbJoint connectedJoint in connectedJoints)
1425  {
1426  if (otherJoints.Contains(connectedJoint))
1427  {
1428  connectedLimbs.Add(limb);
1429  }
1430  }
1431  }
1432  return connectedLimbs;
1433  }
1434 
1435  public void Remove()
1436  {
1437  body?.Remove();
1438  body = null;
1439  if (pullJoint != null)
1440  {
1441  if (GameMain.World.JointList.Contains(pullJoint))
1442  {
1443  GameMain.World.Remove(pullJoint);
1444  }
1445  pullJoint = null;
1446  }
1447  Release();
1448  RemoveProjSpecific();
1449  Removed = true;
1450  }
1451 
1452  partial void RemoveProjSpecific();
1453 
1454  public void LoadParams()
1455  {
1456  pullJoint.LocalAnchorA = ConvertUnits.ToSimUnits(Params.PullPos * Scale);
1457  LoadParamsProjSpecific();
1458  }
1459 
1460  partial void LoadParamsProjSpecific();
1461  }
1462 
1464  {
1465  public AbilityAfflictionCharacter(Affliction affliction, Character character)
1466  {
1467  Affliction = affliction;
1468  Character = character;
1469  }
1470  public Character Character { get; set; }
1471  public Affliction Affliction { get; set; }
1472  }
1473 
1475  {
1476  public AbilityReduceAffliction(Character character, float value)
1477  {
1478  Character = character;
1479  Value = value;
1480  }
1481 
1482  public Character Character { get; set; }
1483  public float Value { get; set; }
1484  }
1485 
1486 }
AbilityAfflictionCharacter(Affliction affliction, Character character)
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)
float RangeMultiplier
Used for multiplying all the ranges.
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)
void Kill(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool isNetworkMessage=false, bool log=true)
bool MatchesAffliction(string identifier, string type)
Returns true if the type or the identifier matches the defined types/identifiers.
Submarine Submarine
Definition: Entity.cs:53
static World World
Definition: GameMain.cs:105
static NetworkMember NetworkMember
Definition: GameMain.cs:190
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)
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...
void ApplyStatusEffects(ActionType actionType, float deltaTime)
Dictionary< ActionType, List< StatusEffect > > StatusEffects
readonly List< WearableSprite > WearingItems
void MoveToPos(Vector2 pos, float force, bool pullFromCenter=false)
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,...
IEnumerable< LimbJoint > GetConnectedJoints()
Limb(Ragdoll ragdoll, Character character, LimbParams limbParams)
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
void HideAndDisable(float duration=0, bool ignoreCollisions=true)
LimbJoint(Limb limbA, Limb limbB, JointParams jointParams, Ragdoll ragdoll)
LimbJoint(Limb limbA, Limb limbB, Vector2 anchor1, Vector2 anchor2, bool weld=false)
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)
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...
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.
static Dictionary< Identifier, SerializableProperty > GetProperties(object obj)
StatusEffects can be used to execute various kinds of effects: modifying the state of some entity in ...
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...
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).
ActionType
ActionTypes define when a StatusEffect is executed.
Definition: Enums.cs:19
AbilityEffectType
Definition: Enums.cs:125