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