Server LuaCsForBarotrauma
Ragdoll.cs
2 using FarseerPhysics;
3 using FarseerPhysics.Dynamics;
4 using FarseerPhysics.Dynamics.Contacts;
5 using FarseerPhysics.Dynamics.Joints;
6 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 MoonSharp.Interpreter;
15 
16 namespace Barotrauma
17 {
18  abstract partial class Ragdoll
19  {
20  public abstract RagdollParams RagdollParams { get; protected set; }
21 
22  const float ImpactDamageMultiplayer = 10.0f;
26  const float MaxImpactDamage = 0.1f;
27 
28  private static readonly List<Ragdoll> list = new List<Ragdoll>();
29 
30  struct Impact
31  {
32  public Fixture F1, F2;
33  public Vector2 LocalNormal;
34  public Vector2 Velocity;
35  public Vector2 ImpactPos;
36 
37  public Impact(Fixture f1, Fixture f2, Contact contact, Vector2 velocity)
38  {
39  F1 = f1;
40  F2 = f2;
41  Velocity = velocity;
42  LocalNormal = contact.Manifold.LocalNormal;
43  contact.GetWorldManifold(out _, out FarseerPhysics.Common.FixedArray2<Vector2> points);
44  ImpactPos = points[0];
45  }
46  }
47 
48  private readonly Queue<Impact> impactQueue = new Queue<Impact>();
49 
50  protected Hull currentHull;
51 
52  private bool accessRemovedCharacterErrorShown;
53 
54  private Limb[] limbs;
55  public Limb[] Limbs
56  {
57  get
58  {
59  if (limbs == null)
60  {
62  return Array.Empty<Limb>();
63  }
64  return limbs;
65  }
66  }
67 
68  public bool HasMultipleLimbsOfSameType => limbs != null && limbs.Length > limbDictionary.Count;
69 
70  private bool frozen;
71  public bool Frozen
72  {
73  get { return frozen; }
74  set
75  {
76  if (frozen == value) return;
77 
78  frozen = value;
79 
80  Collider.FarseerBody.LinearDamping = frozen ? (1.5f / (float)Timing.Step) : 0.0f;
81  Collider.FarseerBody.AngularDamping = frozen ? (1.5f / (float)Timing.Step) : PhysicsBody.DefaultAngularDamping;
82  Collider.FarseerBody.IgnoreGravity = frozen;
83 
84  //Collider.PhysEnabled = !frozen;
85  if (frozen && MainLimb != null) { MainLimb.PullJointWorldAnchorB = MainLimb.SimPosition; }
86  }
87  }
88 
89  private Dictionary<LimbType, Limb> limbDictionary;
91 
92  private bool simplePhysicsEnabled;
93 
95  protected readonly Character character;
96 
97  protected float strongestImpact;
98 
99  private float splashSoundTimer;
100 
101  //the ragdoll builds a "tolerance" to the flow force when being pushed by water.
102  //Allows sudden forces (breach, letting water through a door) to heavily push the character around while ensuring flowing water won't make the characters permanently stuck.
103  private float flowForceTolerance, flowStunTolerance;
104 
105  //the movement speed of the ragdoll
106  public Vector2 movement;
107  //the target speed towards which movement is interpolated
108  protected Vector2 targetMovement;
109 
110  //a movement vector that overrides targetmovement if trying to steer
111  //a Character to the position sent by server in multiplayer mode
112  protected Vector2 overrideTargetMovement;
113 
114  protected float floorY, standOnFloorY;
115  protected Fixture floorFixture;
116  protected Vector2 floorNormal = Vector2.UnitY;
117  protected float surfaceY;
118 
119  protected bool inWater, headInWater;
120  protected bool onGround;
121  public bool OnGround => onGround;
122  private Vector2 lastFloorCheckPos;
123  private bool lastFloorCheckIgnoreStairs, lastFloorCheckIgnorePlatforms;
124 
125 
130 
132 
133  protected Direction dir;
134 
136 
137  protected List<PhysicsBody> collider;
138  protected int colliderIndex = 0;
139 
140  private Category prevCollisionCategory = Category.None;
141 
142  public bool IsStuck => Limbs.Any(l => l.IsStuck);
143 
145  {
146  get
147  {
148  return collider?[colliderIndex];
149  }
150  }
151 
152  public bool TryGetCollider(int index, out PhysicsBody collider)
153  {
154  collider = null;
155  try
156  {
157  collider = this.collider?[index];
158  return true;
159  }
160  catch
161  {
162  return false;
163  }
164  }
165 
166  public int ColliderIndex
167  {
168  get
169  {
170  return colliderIndex;
171  }
172  set
173  {
174  if (value == colliderIndex || collider == null) { return; }
175  if (value >= collider.Count || value < 0) { return; }
176 
177  if (collider[colliderIndex].Height < collider[value].Height)
178  {
179  Vector2 pos1 = collider[colliderIndex].SimPosition;
180  pos1.Y -= collider[colliderIndex].Height * ColliderHeightFromFloor;
181  Vector2 pos2 = pos1;
182  pos2.Y += collider[value].Height * 1.1f;
183  if (GameMain.World.RayCast(pos1, pos2).Any(f => f.CollisionCategories.HasFlag(Physics.CollisionWall) && !(f.Body.UserData is Submarine))) { return; }
184  }
185 
186  Vector2 pos = collider[colliderIndex].SimPosition;
187  pos.Y -= collider[colliderIndex].Height * 0.5f;
188  pos.Y += collider[value].Height * 0.5f;
189  collider[value].SetTransform(pos, collider[colliderIndex].Rotation);
190 
191  collider[value].LinearVelocity = collider[colliderIndex].LinearVelocity;
192  collider[value].AngularVelocity = collider[colliderIndex].AngularVelocity;
193  collider[value].Submarine = collider[colliderIndex].Submarine;
194  collider[value].PhysEnabled = !frozen;
195  collider[value].Enabled = !simplePhysicsEnabled;
196 
197  collider[colliderIndex].PhysEnabled = false;
198  colliderIndex = value;
199  }
200  }
201 
202  public float FloorY
203  {
204  get { return floorY; }
205  }
206 
207  public float Mass
208  {
209  get;
210  private set;
211  }
212 
213  public void SubtractMass(Limb limb)
214  {
215  if (limbs.Contains(limb))
216  {
217  Mass -= limb.Mass;
218  }
219  }
220 
221  public Limb MainLimb
222  {
223  get
224  {
225  Limb mainLimb = GetLimb(RagdollParams.MainLimb);
226  if (!IsValid(mainLimb))
227  {
228  Limb torso = GetLimb(LimbType.Torso);
229  Limb head = GetLimb(LimbType.Head);
230  mainLimb = torso ?? head;
231  if (!IsValid(mainLimb))
232  {
233  mainLimb = Limbs.FirstOrDefault(l => IsValid(l));
234  }
235  if (mainLimb == null)
236  {
237  DebugConsole.ThrowError("Couldn't find a valid main limb. The limb can't be hidden nor be set to ignore collisions!");
238  mainLimb = Limbs.FirstOrDefault();
239  }
240  }
241  static bool IsValid(Limb limb) => limb != null && !limb.IsSevered && !limb.IgnoreCollisions && !limb.Hidden;
242  return mainLimb;
243  }
244  }
245 
246  public Vector2 WorldPosition
247  {
248  get
249  {
250  return character.Submarine == null ?
251  ConvertUnits.ToDisplayUnits(Collider.SimPosition) :
252  ConvertUnits.ToDisplayUnits(Collider.SimPosition) + character.Submarine.Position;
253  }
254  }
255 
257  {
258  get { return simplePhysicsEnabled; }
259  set
260  {
261  if (value == simplePhysicsEnabled) { return; }
262 
263  simplePhysicsEnabled = value;
264 
265  foreach (Limb limb in Limbs)
266  {
267  if (limb.IsSevered) { continue; }
268  if (limb.body == null)
269  {
270  DebugConsole.ThrowError("Limb has no body! (" + (character != null ? character.Name : "Unknown character") + ", " + limb.type.ToString());
271  continue;
272  }
273  limb.body.Enabled = !simplePhysicsEnabled;
274  }
275 
276  foreach (LimbJoint joint in LimbJoints)
277  {
278  joint.Enabled = !joint.IsSevered && !simplePhysicsEnabled;
279  }
280 
281  if (!simplePhysicsEnabled)
282  {
283  foreach (Limb limb in Limbs)
284  {
285  if (limb.IsSevered || !limb.body.PhysEnabled) { continue; }
287  //reset pull joints (they may be somewhere far away if the character has moved from the position where animations were last updated)
288  limb.PullJointEnabled = false;
290  }
291  }
292  }
293  }
294 
295  public const float MAX_SPEED = 20;
296 
297  public Vector2 TargetMovement
298  {
299  get
300  {
301  return (overrideTargetMovement == Vector2.Zero) ? targetMovement : overrideTargetMovement;
302  }
303  set
304  {
305  if (!MathUtils.IsValid(value)) return;
306  targetMovement.X = MathHelper.Clamp(value.X, -MAX_SPEED, MAX_SPEED);
307  targetMovement.Y = MathHelper.Clamp(value.Y, -MAX_SPEED, MAX_SPEED);
308  }
309  }
310 
311  public abstract float? HeadPosition { get; }
312  public abstract float? HeadAngle { get; }
313  public abstract float? TorsoPosition { get; }
314  public abstract float? TorsoAngle { get; }
315 
316  float? impactTolerance;
317  public float ImpactTolerance
318  {
319  get
320  {
321  if (impactTolerance == null)
322  {
323  impactTolerance = RagdollParams.ImpactTolerance;
324  if (character.Params.VariantFile != null)
325  {
326  float? tolerance = character.Params.VariantFile.GetRootExcludingOverride().GetChildElement("ragdoll")?.GetAttributeFloat("impacttolerance", impactTolerance.Value);
327  if (tolerance.HasValue)
328  {
329  impactTolerance = tolerance;
330  }
331  }
332  }
333  return impactTolerance.Value;
334  }
335  }
336 
338 
340 
341  public float Dir => dir == Direction.Left ? -1.0f : 1.0f;
342 
344 
345  public bool InWater
346  {
347  get { return inWater; }
348  }
349 
350  public bool HeadInWater
351  {
352  get { return headInWater; }
353  }
354 
356  {
357  get { return currentHull; }
358  set
359  {
360  if (value == currentHull) return;
361 
362  currentHull = value;
363  Submarine currSubmarine = currentHull?.Submarine;
364  foreach (Limb limb in Limbs)
365  {
366  if (limb.IsSevered) { continue; }
367  limb.body.Submarine = currSubmarine;
368  }
369  Collider.Submarine = currSubmarine;
370  }
371  }
372 
373  public bool IgnorePlatforms { get; set; }
374 
378  public virtual void Recreate(RagdollParams ragdollParams = null)
379  {
380  if (IsFlipped)
381  {
382  Flip();
383  }
384  dir = Direction.Right;
385  Dictionary<LimbParams, List<WearableSprite>> items = null;
386  if (ragdollParams != null)
387  {
388  RagdollParams = ragdollParams;
389  }
390  else
391  {
392  // Only re-equip items if the ragdoll doesn't change, because re-equiping items might throw exceptions if the limbs have changed.
393  items = limbs?.ToDictionary(l => l.Params, l => l.WearingItems);
394  }
395  if (character.Params.VariantFile is XDocument variantFile)
396  {
397  RagdollParams.TryApplyVariantScale(variantFile);
398  }
399  foreach (var limbParams in RagdollParams.Limbs)
400  {
401  if (!PhysicsBody.IsValidShape(limbParams.Radius, limbParams.Height, limbParams.Width))
402  {
403  DebugConsole.ThrowError($"Invalid collider dimensions (r: {limbParams.Radius}, h: {limbParams.Height}, w: {limbParams.Width}) on limb: {limbParams.Name}. Fixing.");
404  limbParams.Radius = 10;
405  }
406  }
407  foreach (var colliderParams in RagdollParams.Colliders)
408  {
409  if (!PhysicsBody.IsValidShape(colliderParams.Radius, colliderParams.Height, colliderParams.Width))
410  {
411  DebugConsole.ThrowError($"Invalid collider dimensions (r: {colliderParams.Radius}, h: {colliderParams.Height}, w: {colliderParams.Width}) on collider: {colliderParams.Name}. Fixing.");
412  colliderParams.Radius = 10;
413  }
414  }
415  CreateColliders();
416  CreateLimbs();
417  CreateJoints();
418  UpdateCollisionCategories();
420  if (items != null)
421  {
422  foreach (var kvp in items)
423  {
424  int id = kvp.Key.ID;
425  // This can be the case if we manipulate the ragdoll at runtime (husk appendage, limb removal in the character editor)
426  if (id > limbs.Length - 1) { continue; }
427  var limb = limbs[id];
428  var itemList = kvp.Value;
429  limb.WearingItems.AddRange(itemList);
430  }
431  }
432 
434  {
435  var characterPrefab = CharacterPrefab.FindByFilePath(character.ConfigPath);
436  if (characterPrefab?.ConfigElement != null)
437  {
438  var mainElement = characterPrefab.ConfigElement;
439  foreach (var huskAppendage in mainElement.GetChildElements("huskappendage"))
440  {
441  if (huskAppendage.GetAttributeBool("onlyfromafflictions", false)) { continue; }
442 
443  Identifier afflictionIdentifier = huskAppendage.GetAttributeIdentifier("affliction", Identifier.Empty);
444  if (!AfflictionPrefab.Prefabs.TryGet(afflictionIdentifier, out AfflictionPrefab affliction) ||
445  affliction is not AfflictionPrefabHusk matchingAffliction)
446  {
447  DebugConsole.ThrowError($"Could not find an affliction of type 'huskinfection' that matches the affliction '{afflictionIdentifier}'!",
448  contentPackage: huskAppendage.ContentPackage);
449  }
450  else
451  {
452  AfflictionHusk.AttachHuskAppendage(character, matchingAffliction, huskedSpeciesName: character.SpeciesName, huskAppendage, ragdoll: this);
453  }
454  }
455  }
456  }
457  }
458 
459  public Ragdoll(Character character, string seed, RagdollParams ragdollParams = null)
460  {
461  list.Add(this);
462  this.character = character;
463  Recreate(ragdollParams ?? RagdollParams);
464  }
465 
466  protected void CreateColliders()
467  {
468  collider?.ForEach(c => c.Remove());
469  DebugConsole.Log($"Creating colliders from {RagdollParams.Name}.");
470  collider = new List<PhysicsBody>();
471  foreach (var cParams in RagdollParams.Colliders)
472  {
473  if (!PhysicsBody.IsValidShape(cParams.Radius, cParams.Height, cParams.Width))
474  {
475  DebugConsole.ThrowError("Invalid collider dimensions: " + cParams.Name);
476  break; ;
477  }
478  var body = new PhysicsBody(cParams, findNewContacts: false);
479  collider.Add(body);
480  body.UserData = character;
481  body.FarseerBody.OnCollision += OnLimbCollision;
482  if (collider.Count > 1)
483  {
484  body.PhysEnabled = false;
485  }
486  }
487  }
488 
489  protected void CreateJoints()
490  {
491  if (LimbJoints != null)
492  {
493  foreach (LimbJoint joint in LimbJoints)
494  {
495  if (GameMain.World.JointList.Contains(joint.Joint)) { GameMain.World.Remove(joint.Joint); }
496  }
497  }
498  DebugConsole.Log($"Creating joints from {RagdollParams.Name}.");
500  RagdollParams.Joints.ForEach(j => AddJoint(j));
501  // Check the joints
502  for (int i = 0; i < LimbJoints.Length; i++)
503  {
504  if (LimbJoints[i] == null)
505  {
506  DebugConsole.ThrowError($"Joint {i} null.");
507  }
508  }
509 
510  UpdateCollisionCategories();
511  SetInitialLimbPositions();
512  }
513 
514  private void SetInitialLimbPositions()
515  {
516  foreach (var joint in LimbJoints)
517  {
518  if (joint == null) { continue; }
519  float angle = (joint.LowerLimit + joint.UpperLimit) / 2.0f;
520  joint.LimbB?.body?.SetTransformIgnoreContacts(
521  (joint.WorldAnchorA - MathUtils.RotatePointAroundTarget(joint.LocalAnchorB, Vector2.Zero, joint.BodyA.Rotation + angle, true)),
522  joint.BodyA.Rotation + angle);
523  }
524  }
525 
526  protected void CreateLimbs()
527  {
528  limbs?.ForEach(l => l.Remove());
529  Mass = 0;
530  DebugConsole.Log($"Creating limbs from {RagdollParams.Name}.");
531  limbDictionary = new Dictionary<LimbType, Limb>();
532  limbs = new Limb[RagdollParams.Limbs.Count];
533  RagdollParams.Limbs.ForEach(AddLimb);
534  if (limbs.Contains(null)) { return; }
535  SetupDrawOrder();
536  }
537 
538  partial void SetupDrawOrder();
539 
543  public void SaveRagdoll(string fileNameWithoutExtension = null)
544  {
545  RagdollParams.Save(fileNameWithoutExtension);
546  }
547 
552  public void ResetRagdoll()
553  {
554  RagdollParams.Reset(forceReload: true);
555  ResetJoints();
556  ResetLimbs();
557  }
558 
562  public void ResetJoints()
563  {
564  LimbJoints.ForEach(j => j.LoadParams());
565  }
566 
570  public void ResetLimbs()
571  {
572  Limbs.ForEach(l => l.LoadParams());
573  SetupDrawOrder();
574  }
575 
576  public void AddJoint(JointParams jointParams)
577  {
578  if (!checkLimbIndex(jointParams.Limb1, "Limb1") || !checkLimbIndex(jointParams.Limb2, "Limb2"))
579  {
580  return;
581  }
582  LimbJoint joint = new LimbJoint(Limbs[jointParams.Limb1], Limbs[jointParams.Limb2], jointParams, this);
583  GameMain.World.Add(joint.Joint);
584  for (int i = 0; i < LimbJoints.Length; i++)
585  {
586  if (LimbJoints[i] != null) continue;
587  LimbJoints[i] = joint;
588  return;
589  }
590  Array.Resize(ref LimbJoints, LimbJoints.Length + 1);
591  LimbJoints[LimbJoints.Length - 1] = joint;
592 
593  bool checkLimbIndex(int index, string debugName)
594  {
595  if (index < 0 || index >= limbs.Length)
596  {
597  string errorMsg = $"Failed to add a joint to character {character.Name}. {debugName} out of bounds (index: {index}, limbs: {limbs.Length}.";
598  DebugConsole.ThrowError(errorMsg, contentPackage: jointParams.Element?.ContentPackage);
599  if (jointParams.Element?.ContentPackage == GameMain.VanillaContent)
600  {
601  GameAnalyticsManager.AddErrorEventOnce("Ragdoll.AddJoint:IndexOutOfRange", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
602  }
603  return false;
604  }
605  return true;
606  }
607  }
608 
609  protected void AddLimb(LimbParams limbParams)
610  {
611  if (limbParams.ID < 0 || limbParams.ID > 255)
612  {
613  throw new Exception($"Invalid limb params in limb \"{limbParams.Type}\". \"{limbParams.ID}\" is not a valid limb ID.");
614  }
615  byte ID = Convert.ToByte(limbParams.ID);
616  Limb limb = new Limb(this, character, limbParams);
617  limb.body.FarseerBody.OnCollision += OnLimbCollision;
618  if (ID >= Limbs.Length)
619  {
620  throw new Exception($"Failed to add a limb to the character \"{Character?.ConfigPath ?? "null"}\" (limb index {ID} out of bounds). The ragdoll file may be configured incorrectly.");
621  }
622  Limbs[ID] = limb;
623  Mass += limb.Mass;
624  if (!limbDictionary.ContainsKey(limb.type)) { limbDictionary.Add(limb.type, limb); }
625  }
626 
627  public void AddLimb(Limb limb)
628  {
629  if (Limbs.Contains(limb)) { return; }
630  limb.body.FarseerBody.OnCollision += OnLimbCollision;
631  Array.Resize(ref limbs, Limbs.Length + 1);
632  Limbs[Limbs.Length - 1] = limb;
633  Mass += limb.Mass;
634  if (!limbDictionary.ContainsKey(limb.type)) { limbDictionary.Add(limb.type, limb); }
635  SetupDrawOrder();
636  }
637 
638  public void RemoveLimb(Limb limb)
639  {
640  if (!Limbs.Contains(limb)) return;
641 
642  Limb[] newLimbs = new Limb[Limbs.Length - 1];
643 
644  int i = 0;
645  foreach (Limb existingLimb in Limbs)
646  {
647  if (existingLimb == limb) continue;
648  newLimbs[i] = existingLimb;
649  i++;
650  }
651 
652  limbs = newLimbs;
653  if (limbDictionary.ContainsKey(limb.type))
654  {
655  limbDictionary.Remove(limb.type);
656  // If there is another limb of the same type, replace the limb in the dictionary.
658  {
659  var otherLimb = Limbs.FirstOrDefault(l => l != limb && l.type == limb.type);
660  if (otherLimb != null)
661  {
662  limbDictionary.Add(otherLimb.type, otherLimb);
663  }
664  }
665  }
666 
667  // TODO: this could be optimized if needed, but at least we need to remove the limb from the inversedDrawOrder array.
668  SetupDrawOrder();
669 
670  //remove all joints that were attached to the removed limb
671  LimbJoint[] attachedJoints = Array.FindAll(LimbJoints, lj => lj.LimbA == limb || lj.LimbB == limb);
672  if (attachedJoints.Length > 0)
673  {
674  LimbJoint[] newJoints = new LimbJoint[LimbJoints.Length - attachedJoints.Length];
675  i = 0;
676  foreach (LimbJoint limbJoint in LimbJoints)
677  {
678  if (attachedJoints.Contains(limbJoint)) continue;
679  newJoints[i] = limbJoint;
680  i++;
681  }
682  LimbJoints = newJoints;
683  }
684 
685  limb.Remove();
686  foreach (LimbJoint limbJoint in attachedJoints)
687  {
688  GameMain.World.Remove(limbJoint.Joint);
689  }
690  }
691 
692  private enum LimbStairCollisionResponse
693  {
694  DontClimbStairs,
695  ClimbWithoutLimbCollision,
696  ClimbWithLimbCollision
697  }
698 
699  public bool OnLimbCollision(Fixture f1, Fixture f2, Contact contact)
700  {
701  if (f2.Body.UserData is Submarine submarine && character.Submarine == submarine) { return false; }
702  if (f2.UserData is Hull)
703  {
704  if (character.Submarine != null)
705  {
706  return false;
707  }
708  if (CanEnterSubmarine == CanEnterSubmarine.Partial)
709  {
710  //collider collides with hulls to prevent the character going fully inside the sub, limbs don't
711  return
712  f1.Body == Collider.FarseerBody ||
713  (f1.Body.UserData is Limb limb && !limb.Params.CanEnterSubmarine);
714  }
715  }
716 
717  //using the velocity of the limb would make the impact damage more realistic,
718  //but would also make it harder to edit the animations because the forces/torques
719  //would all have to be balanced in a way that prevents the character from doing
720  //impact damage to itself
721  Vector2 velocity = Collider.LinearVelocity;
722  if (character.Submarine == null && f2.Body.UserData is Submarine sub)
723  {
724  velocity -= sub.Velocity;
725  }
726 
727  //always collides with bodies other than structures
728  if (f2.Body.UserData is not Structure structure)
729  {
730  if (!f2.IsSensor)
731  {
732  lock (impactQueue)
733  {
734  impactQueue.Enqueue(new Impact(f1, f2, contact, velocity));
735  }
736  }
737  return true;
738  }
739  else if (character.Submarine != null && structure.Submarine != null && character.Submarine != structure.Submarine)
740  {
741  return false;
742  }
743 
744  Vector2 colliderBottom = GetColliderBottom();
745  if (structure.IsPlatform)
746  {
747  if (IgnorePlatforms || currentHull == null) { return false; }
748 
749  if (colliderBottom.Y < ConvertUnits.ToSimUnits(structure.Rect.Y - 5)) { return false; }
750  if (f1.Body.Position.Y < ConvertUnits.ToSimUnits(structure.Rect.Y - 5)) { return false; }
751  }
752  else if (structure.StairDirection != Direction.None)
753  {
754  if (character.SelectedBy != null)
755  {
757  }
758 
759  var collisionResponse = getStairCollisionResponse();
760  if (collisionResponse == LimbStairCollisionResponse.ClimbWithLimbCollision)
761  {
762  Stairs = structure;
763  }
764  else
765  {
766  if (collisionResponse == LimbStairCollisionResponse.DontClimbStairs) { Stairs = null; }
767 
768  return false;
769  }
770 
771  LimbStairCollisionResponse getStairCollisionResponse()
772  {
773  //don't collide with stairs if
774 
775  //1. bottom of the collider is at the bottom of the stairs and the character isn't trying to move upwards
776  float stairBottomPos = ConvertUnits.ToSimUnits(structure.Rect.Y - structure.Rect.Height + 10);
777  if (colliderBottom.Y < stairBottomPos && targetMovement.Y < 0.5f) { return LimbStairCollisionResponse.DontClimbStairs; }
778  if (character.SelectedBy != null &&
779  character.SelectedBy.AnimController.GetColliderBottom().Y < stairBottomPos &&
781  {
782  return LimbStairCollisionResponse.DontClimbStairs;
783  }
784 
785  //2. bottom of the collider is at the top of the stairs and the character isn't trying to move downwards
786  if (targetMovement.Y >= 0.0f && colliderBottom.Y >= ConvertUnits.ToSimUnits(structure.Rect.Y - Submarine.GridSize.Y * 5)) { return LimbStairCollisionResponse.DontClimbStairs; }
787 
788  //3. collided with the stairs from below
789  if (contact.Manifold.LocalNormal.Y < 0.0f)
790  {
791  return Stairs != structure
792  ? LimbStairCollisionResponse.DontClimbStairs
793  : LimbStairCollisionResponse.ClimbWithoutLimbCollision;
794  }
795 
796  //4. contact points is above the bottom half of the collider
797  contact.GetWorldManifold(out _, out FarseerPhysics.Common.FixedArray2<Vector2> points);
798  if (points[0].Y > Collider.SimPosition.Y) { return LimbStairCollisionResponse.DontClimbStairs; }
799 
800  //5. in water
801  if (inWater && targetMovement.Y < 0.5f) { return LimbStairCollisionResponse.DontClimbStairs; }
802 
803  return LimbStairCollisionResponse.ClimbWithLimbCollision;
804  }
805  }
806 
807  lock (impactQueue)
808  {
809  impactQueue.Enqueue(new Impact(f1, f2, contact, velocity));
810  }
811 
812  return true;
813  }
814 
815  private void ApplyImpact(Fixture f1, Fixture f2, Vector2 localNormal, Vector2 impactPos, Vector2 velocity)
816  {
817  if (character.DisableImpactDamageTimer > 0.0f) { return; }
818 
819  if (f2.Body?.UserData is Item)
820  {
821  //no impact damage from items
822  //items that can impact characters (melee weapons, projectiles) should handle the damage themselves
823  return;
824  }
825 
826  Vector2 normal = localNormal;
827  float impact = Vector2.Dot(velocity, -normal);
828  if (f1.Body == Collider.FarseerBody || !Collider.Enabled)
829  {
830  bool isNotRemote = true;
831  if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { isNotRemote = !character.IsRemotelyControlled; }
832 
833  if (isNotRemote)
834  {
835  float impactTolerance = ImpactTolerance;
836  if (character.Stun > 0.0f) { impactTolerance *= 0.5f; }
837  if (impact > impactTolerance)
838  {
839  impactPos = ConvertUnits.ToDisplayUnits(impactPos);
840  if (character.Submarine != null) { impactPos += character.Submarine.Position; }
841 
842  float impactDamage = GetImpactDamage(impact, impactTolerance);
843 
844  var should = GameMain.LuaCs.Hook.Call<float?>("changeFallDamage", impactDamage, character, impactPos, velocity);
845 
846  if (should != null)
847  {
848  impactDamage = should.Value;
849  }
850 
852  character.AddDamage(impactPos, AfflictionPrefab.ImpactDamage.Instantiate(impactDamage).ToEnumerable(), 0.0f, true);
853  strongestImpact = Math.Max(strongestImpact, impact - impactTolerance);
854  character.ApplyStatusEffects(ActionType.OnImpact, 1.0f);
855  //briefly disable impact damage
856  //otherwise the character will take damage multiple times when for example falling,
857  //because we use the velocity of the collider to determine the impact
858  //(i.e. the character would take damage until the collider hits the floor and stops)
860  }
861  }
862  }
863 
864  ImpactProjSpecific(impact, f1.Body);
865  }
866 
867  public float GetImpactDamage(float impact, float? impactTolerance = null)
868  {
869  float tolerance = impactTolerance ?? ImpactTolerance;
870  return Math.Min((impact - tolerance) * ImpactDamageMultiplayer, character.MaxVitality * MaxImpactDamage);
871  }
872 
873  private readonly List<Limb> connectedLimbs = new List<Limb>();
874  private readonly List<LimbJoint> checkedJoints = new List<LimbJoint>();
875  public bool SeverLimbJoint(LimbJoint limbJoint)
876  {
877  if (!limbJoint.CanBeSevered || limbJoint.IsSevered)
878  {
879  return false;
880  }
881 
882  limbJoint.IsSevered = true;
883  limbJoint.Enabled = false;
884 
885  Vector2 limbDiff = limbJoint.LimbA.SimPosition - limbJoint.LimbB.SimPosition;
886  if (limbDiff.LengthSquared() < 0.0001f) { limbDiff = Rand.Vector(1.0f); }
887  limbDiff = Vector2.Normalize(limbDiff);
888  float mass = limbJoint.BodyA.Mass + limbJoint.BodyB.Mass;
889  limbJoint.LimbA.body.ApplyLinearImpulse(limbDiff * Math.Min(mass, limbJoint.BodyA.Mass * 500), (limbJoint.LimbA.SimPosition + limbJoint.LimbB.SimPosition) / 2.0f);
890  limbJoint.LimbB.body.ApplyLinearImpulse(-limbDiff * Math.Min(mass, limbJoint.BodyB.Mass * 500), (limbJoint.LimbA.SimPosition + limbJoint.LimbB.SimPosition) / 2.0f);
891 
892  connectedLimbs.Clear();
893  checkedJoints.Clear();
894  GetConnectedLimbs(connectedLimbs, checkedJoints, MainLimb);
895  foreach (Limb limb in Limbs)
896  {
897  if (connectedLimbs.Contains(limb)) { continue; }
898  limb.IsSevered = true;
899  if (limb.type == LimbType.RightHand)
900  {
902  }
903  else if (limb.type == LimbType.LeftHand)
904  {
906  }
907  }
908 
909  if (!string.IsNullOrEmpty(character.BloodDecalName))
910  {
912  (limbJoint.LimbA.WorldPosition + limbJoint.LimbB.WorldPosition) / 2, MathHelper.Clamp(Math.Min(limbJoint.LimbA.Mass, limbJoint.LimbB.Mass), 0.5f, 2.0f), isNetworkEvent: false);
913  }
914 
915  SeverLimbJointProjSpecific(limbJoint, playSound: true);
916  if (GameMain.NetworkMember is { IsServer: true })
917  {
919  }
920  return true;
921  }
922 
923  partial void SeverLimbJointProjSpecific(LimbJoint limbJoint, bool playSound);
924 
925  protected List<Limb> GetConnectedLimbs(Limb limb)
926  {
927  connectedLimbs.Clear();
928  checkedJoints.Clear();
929  GetConnectedLimbs(connectedLimbs, checkedJoints, limb);
930  return connectedLimbs;
931  }
932 
933  private void GetConnectedLimbs(List<Limb> connectedLimbs, List<LimbJoint> checkedJoints, Limb limb)
934  {
935  connectedLimbs.Add(limb);
936 
937  foreach (LimbJoint joint in LimbJoints)
938  {
939  if (joint.IsSevered || checkedJoints.Contains(joint)) { continue; }
940  if (joint.LimbA == limb)
941  {
942  if (!connectedLimbs.Contains(joint.LimbB))
943  {
944  checkedJoints.Add(joint);
945  GetConnectedLimbs(connectedLimbs, checkedJoints, joint.LimbB);
946  }
947  }
948  else if (joint.LimbB == limb)
949  {
950  if (!connectedLimbs.Contains(joint.LimbA))
951  {
952  checkedJoints.Add(joint);
953  GetConnectedLimbs(connectedLimbs, checkedJoints, joint.LimbA);
954  }
955  }
956  }
957  }
958 
959  partial void ImpactProjSpecific(float impact, Body body);
960 
961  public bool IsFlipped { get; private set; }
962 
963  public virtual void Flip()
964  {
965  IsFlipped = !IsFlipped;
966  dir = (dir == Direction.Left) ? Direction.Right : Direction.Left;
967 
968  for (int i = 0; i < LimbJoints.Length; i++)
969  {
970  float lowerLimit = -LimbJoints[i].UpperLimit;
971  float upperLimit = -LimbJoints[i].LowerLimit;
972 
973  LimbJoints[i].LowerLimit = lowerLimit;
974  LimbJoints[i].UpperLimit = upperLimit;
975 
976  LimbJoints[i].LocalAnchorA = new Vector2(-LimbJoints[i].LocalAnchorA.X, LimbJoints[i].LocalAnchorA.Y);
977  LimbJoints[i].LocalAnchorB = new Vector2(-LimbJoints[i].LocalAnchorB.X, LimbJoints[i].LocalAnchorB.Y);
978  }
979 
980  foreach (Limb limb in Limbs)
981  {
982  if (limb == null || limb.IsSevered || !limb.DoesMirror) { continue; }
983  limb.Dir = Dir;
984  limb.MouthPos = new Vector2(-limb.MouthPos.X, limb.MouthPos.Y);
985  limb.MirrorPullJoint();
986  }
987 
988  FlipProjSpecific();
989  }
990 
991  partial void FlipProjSpecific();
992 
993  public Vector2 GetCenterOfMass()
994  {
995  //all limbs disabled -> use the position of the collider
996  if (!Limbs.Any(l => !l.IsSevered && l.body.Enabled))
997  {
998  return Collider.SimPosition;
999  }
1000 
1001  Vector2 centerOfMass = Vector2.Zero;
1002  float totalMass = 0.0f;
1003  foreach (Limb limb in Limbs)
1004  {
1005  if (limb.IsSevered || !limb.body.Enabled) continue;
1006  centerOfMass += limb.Mass * limb.SimPosition;
1007  totalMass += limb.Mass;
1008  }
1009 
1010  if (totalMass <= 0.0f) return Collider.SimPosition;
1011  centerOfMass /= totalMass;
1012 
1013  if (!MathUtils.IsValid(centerOfMass))
1014  {
1015  string errorMsg = "Ragdoll.GetCenterOfMass returned an invalid value (" + centerOfMass + "). Limb positions: {"
1016  + string.Join(", ", limbs.Select(l => l.SimPosition)) + "}, total mass: " + totalMass + ".";
1017  DebugConsole.ThrowError(errorMsg);
1018  GameAnalyticsManager.AddErrorEventOnce("Ragdoll.GetCenterOfMass", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
1019  return Collider.SimPosition;
1020  }
1021 
1022  return centerOfMass;
1023  }
1024 
1025 
1027  public void MoveLimb(Limb limb, Vector2 pos, float amount, bool pullFromCenter = false)
1028  {
1029  limb.MoveToPos(pos, amount, pullFromCenter);
1030  }
1031 
1032  public void ResetPullJoints(Func<Limb, bool> condition = null)
1033  {
1034  for (int i = 0; i < Limbs.Length; i++)
1035  {
1036  if (Limbs[i] == null) { continue; }
1037  if (condition != null && !condition(Limbs[i])) { continue; }
1038  Limbs[i].PullJointEnabled = false;
1039  }
1040  }
1041 
1042  public static void UpdateAll(float deltaTime, Camera cam)
1043  {
1044  foreach (Ragdoll r in list)
1045  {
1046  r.UpdateRagdoll(deltaTime, cam);
1047  }
1048  }
1049 
1050  public void FindHull(Vector2? worldPosition = null, bool setSubmarine = true)
1051  {
1052  Vector2 findPos = worldPosition == null ? this.WorldPosition : (Vector2)worldPosition;
1053  if (!MathUtils.IsValid(findPos))
1054  {
1055  GameAnalyticsManager.AddErrorEventOnce(
1056  "Ragdoll.FindHull:InvalidPosition",
1057  GameAnalyticsManager.ErrorSeverity.Error,
1058  "Attempted to find a hull at an invalid position (" + findPos + ")\n" + Environment.StackTrace.CleanupStackTrace());
1059  return;
1060  }
1061 
1062  Hull newHull = Hull.FindHull(findPos, currentHull);
1063 
1064  if (newHull == currentHull) { return; }
1065 
1066  if (CanEnterSubmarine == CanEnterSubmarine.False ||
1068  {
1069  //character is inside the sub even though it shouldn't be able to enter -> teleport it out
1070 
1071  //far from an ideal solution, but monsters getting lodged inside the sub seems to be
1072  //pretty rare during normal gameplay (requires abnormally high velocities), so I think
1073  //this is preferable to the cost of using continuous collision detection for the character collider
1074  if (newHull?.Submarine != null)
1075  {
1076  Vector2 hullDiff = WorldPosition - newHull.WorldPosition;
1077  Vector2 moveDir = hullDiff.LengthSquared() < 0.001f ? Vector2.UnitY : Vector2.Normalize(hullDiff);
1078 
1079  //find a position 32 units away from the hull
1080  if (MathUtils.GetLineRectangleIntersection(
1081  newHull.WorldPosition,
1082  newHull.WorldPosition + moveDir * Math.Max(newHull.Rect.Width, newHull.Rect.Height),
1083  new Rectangle(newHull.WorldRect.X - 32, newHull.WorldRect.Y + 32, newHull.WorldRect.Width + 64, newHull.Rect.Height + 64),
1084  out Vector2 intersection))
1085  {
1086  Collider.SetTransform(ConvertUnits.ToSimUnits(intersection), Collider.Rotation);
1087  }
1088  return;
1089  }
1090  }
1091 
1093  {
1094  return;
1095  }
1096 
1097  if (setSubmarine)
1098  {
1099  //in -> out
1100  if (newHull?.Submarine == null && currentHull?.Submarine != null)
1101  {
1102  //don't teleport out yet if the character is going through a gap
1103  if (Gap.FindAdjacent(Gap.GapList.Where(g => g.Submarine == currentHull.Submarine), findPos, 150.0f, allowRoomToRoom: true) != null) { return; }
1104  if (Limbs.Any(l => Gap.FindAdjacent(currentHull.ConnectedGaps, l.WorldPosition, ConvertUnits.ToDisplayUnits(l.body.GetSize().Combine()), allowRoomToRoom: true) != null)) { return; }
1105  character.MemLocalState?.Clear();
1107  }
1108  //out -> in
1109  else if (currentHull == null && newHull.Submarine != null)
1110  {
1111  character.MemLocalState?.Clear();
1112  Teleport(-ConvertUnits.ToSimUnits(newHull.Submarine.Position), -newHull.Submarine.Velocity);
1113  }
1114  //from one sub to another
1115  else if (newHull != null && currentHull != null && newHull.Submarine != currentHull.Submarine)
1116  {
1117  character.MemLocalState?.Clear();
1118  Vector2 newSubPos = newHull.Submarine == null ? Vector2.Zero : newHull.Submarine.Position;
1119  Vector2 prevSubPos = currentHull.Submarine == null ? Vector2.Zero : currentHull.Submarine.Position;
1120  Teleport(ConvertUnits.ToSimUnits(prevSubPos - newSubPos), Vector2.Zero);
1121  }
1122  }
1123 
1124  CurrentHull = newHull;
1126  foreach (var attachedProjectile in character.AttachedProjectiles)
1127  {
1128  attachedProjectile.Item.CurrentHull = currentHull;
1129  attachedProjectile.Item.Submarine = character.Submarine;
1130  attachedProjectile.Item.UpdateTransform();
1131  }
1132  }
1133 
1134  private void PreventOutsideCollision()
1135  {
1136  if (currentHull?.Submarine == null) { return; }
1137 
1138  var connectedGaps = currentHull.ConnectedGaps.Where(g => !g.IsRoomToRoom);
1139  foreach (Gap gap in connectedGaps)
1140  {
1141  if (gap.IsHorizontal)
1142  {
1143  if (character.Position.Y > gap.Rect.Y || character.Position.Y < gap.Rect.Y - gap.Rect.Height) continue;
1144  if (Math.Sign(gap.Rect.Center.X - currentHull.Rect.Center.X) !=
1145  Math.Sign(character.Position.X - currentHull.Rect.Center.X))
1146  {
1147  continue;
1148  }
1149  }
1150  else
1151  {
1152  if (character.Position.X < gap.Rect.X || character.Position.X > gap.Rect.Right) continue;
1153  if (Math.Sign((gap.Rect.Y - gap.Rect.Height / 2) - (currentHull.Rect.Y - currentHull.Rect.Height / 2)) !=
1154  Math.Sign(character.Position.Y - (currentHull.Rect.Y - currentHull.Rect.Height / 2)))
1155  {
1156  continue;
1157  }
1158  }
1159 
1160  gap.RefreshOutsideCollider();
1161  }
1162  }
1163 
1164  public void Teleport(Vector2 moveAmount, Vector2 velocityChange, bool detachProjectiles = true)
1165  {
1166  foreach (Limb limb in Limbs)
1167  {
1168  if (limb.IsSevered) { continue; }
1169  if (limb.body.FarseerBody.ContactList == null) { continue; }
1170 
1171  ContactEdge ce = limb.body.FarseerBody.ContactList;
1172  while (ce != null && ce.Contact != null)
1173  {
1174  ce.Contact.Enabled = false;
1175  ce = ce.Next;
1176  }
1177  }
1178 
1179  foreach (Limb limb in Limbs)
1180  {
1181  if (limb.IsSevered) { continue; }
1182  limb.body.LinearVelocity += velocityChange;
1183  }
1184 
1186 
1187  SetPosition(Collider.SimPosition + moveAmount);
1188  character.CursorPosition += moveAmount;
1189 
1191  foreach (Limb limb in Limbs)
1192  {
1193  limb.body.UpdateDrawPosition();
1194  }
1195  }
1196 
1197  private void UpdateCollisionCategories()
1198  {
1199  Category wall = currentHull?.Submarine == null ?
1200  Physics.CollisionLevel | Physics.CollisionWall
1201  : Physics.CollisionWall;
1202 
1203  Category collisionCategory = (IgnorePlatforms) ?
1204  wall | Physics.CollisionProjectile | Physics.CollisionStairs
1205  : wall | Physics.CollisionProjectile | Physics.CollisionPlatform | Physics.CollisionStairs;
1206 
1207  if (collisionCategory == prevCollisionCategory) { return; }
1208  prevCollisionCategory = collisionCategory;
1209 
1210  Collider.CollidesWith = collisionCategory | Physics.CollisionItemBlocking;
1211 
1212  foreach (Limb limb in Limbs)
1213  {
1214  if (limb.IgnoreCollisions || limb.IsSevered) { continue; }
1215 
1216  try
1217  {
1218  limb.body.CollidesWith = collisionCategory;
1219  }
1220  catch (Exception e)
1221  {
1222  DebugConsole.ThrowError("Failed to update ragdoll limb collisioncategories", e);
1223  }
1224  }
1225  }
1226 
1227  protected bool levitatingCollider = true;
1228 
1232  private float bodyInRestTimer;
1233 
1234  private float BodyInRestDelay = 1.0f;
1235 
1236  public bool BodyInRest
1237  {
1238  get { return bodyInRestTimer > BodyInRestDelay; }
1239  set
1240  {
1241  foreach (Limb limb in Limbs)
1242  {
1243  limb.body.PhysEnabled = !value;
1244  }
1245  bodyInRestTimer = value ? BodyInRestDelay : 0.0f;
1246  }
1247  }
1248 
1249  public bool forceStanding;
1250  public bool forceNotStanding;
1251 
1252  public void UpdateRagdoll(float deltaTime, Camera cam)
1253  {
1254  if (!character.Enabled || character.Removed || Frozen || Invalid || Collider == null || Collider.Removed) { return; }
1255 
1256  while (impactQueue.Count > 0)
1257  {
1258  var impact = impactQueue.Dequeue();
1259  ApplyImpact(impact.F1, impact.F2, impact.LocalNormal, impact.ImpactPos, impact.Velocity);
1260  }
1261 
1262  CheckValidity();
1263 
1264  UpdateNetPlayerPosition(deltaTime);
1266  UpdateCollisionCategories();
1267 
1268  FindHull();
1269  PreventOutsideCollision();
1270 
1271  CheckBodyInRest(deltaTime);
1272 
1273  splashSoundTimer -= deltaTime;
1274 
1275  if (character.Submarine == null && Level.Loaded != null)
1276  {
1277  if (Collider.SimPosition.Y > Level.Loaded.TopBarrier.Position.Y)
1278  {
1279  Collider.LinearVelocity = new Vector2(Collider.LinearVelocity.X, Math.Min(Collider.LinearVelocity.Y, -1));
1280  }
1281  else if (Collider.SimPosition.Y < Level.Loaded.BottomBarrier.Position.Y)
1282  {
1284  MathHelper.Clamp(Collider.LinearVelocity.Y, Level.Loaded.BottomBarrier.Position.Y - Collider.SimPosition.Y, 10.0f));
1285  }
1286  foreach (Limb limb in Limbs)
1287  {
1288  if (limb.SimPosition.Y > Level.Loaded.TopBarrier.Position.Y)
1289  {
1290  limb.body.LinearVelocity = new Vector2(limb.LinearVelocity.X, Math.Min(limb.LinearVelocity.Y, -1));
1291  }
1292  else if (limb.SimPosition.Y < Level.Loaded.BottomBarrier.Position.Y)
1293  {
1294  limb.body.LinearVelocity = new Vector2(
1295  limb.LinearVelocity.X,
1296  MathHelper.Clamp(limb.LinearVelocity.Y, Level.Loaded.BottomBarrier.Position.Y - limb.SimPosition.Y, 10.0f));
1297  }
1298  }
1299  }
1300 
1301  if (forceStanding)
1302  {
1303  inWater = false;
1304  headInWater = false;
1305  RefreshFloorY(deltaTime, ignoreStairs: Stairs == null);
1306  }
1307  //ragdoll isn't in any room -> it's in the water
1308  else if (currentHull == null)
1309  {
1310  inWater = true;
1311  headInWater = true;
1312  }
1313  else
1314  {
1315  headInWater = false;
1316  inWater = false;
1317  RefreshFloorY(deltaTime, ignoreStairs: Stairs == null);
1318  if (currentHull.WaterPercentage > 0.001f)
1319  {
1320  (float waterSurfaceDisplayUnits, float ceilingDisplayUnits) = GetWaterSurfaceAndCeilingY();
1321  float waterSurfaceY = ConvertUnits.ToSimUnits(waterSurfaceDisplayUnits);
1322  float ceilingY = ConvertUnits.ToSimUnits(ceilingDisplayUnits);
1323  if (targetMovement.Y < 0.0f)
1324  {
1325  Vector2 colliderBottom = GetColliderBottom();
1326  floorY = Math.Min(colliderBottom.Y, floorY);
1327  //check if the bottom of the collider is below the current hull
1328  if (floorY < ConvertUnits.ToSimUnits(currentHull.Rect.Y - currentHull.Rect.Height))
1329  {
1330  //set floorY to the position of the floor in the hull below the character
1331  var lowerHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(colliderBottom), useWorldCoordinates: false);
1332  if (lowerHull != null)
1333  {
1334  floorY = ConvertUnits.ToSimUnits(lowerHull.Rect.Y - lowerHull.Rect.Height);
1335  }
1336  }
1337  }
1338  float standHeight = HeadPosition ?? TorsoPosition ?? Collider.GetMaxExtent() * 0.5f;
1339  if (Collider.SimPosition.Y < waterSurfaceY)
1340  {
1341  //too deep to stand up, or not enough room to stand up
1342  if (waterSurfaceY - floorY > standHeight * 0.8f ||
1343  ceilingY - floorY < standHeight * 0.8f)
1344  {
1345  inWater = true;
1346  }
1347  }
1348  }
1349  }
1350 
1351  UpdateHullFlowForces(deltaTime);
1352 
1353  if (currentHull == null ||
1355  ConvertUnits.ToSimUnits(currentHull.Surface) > Collider.SimPosition.Y)
1356  {
1358  }
1359 
1360  foreach (Limb limb in Limbs)
1361  {
1362  //find the room which the limb is in
1363  //the room where the ragdoll is in is used as the "guess", meaning that it's checked first
1364  Hull newHull = currentHull == null ? null : Hull.FindHull(limb.WorldPosition, currentHull);
1365 
1366  bool prevInWater = limb.InWater;
1367  limb.InWater = false;
1368 
1369  if (forceStanding)
1370  {
1371  limb.InWater = false;
1372  }
1373  else if (newHull == null)
1374  {
1375  //limb isn't in any room -> it's in the water
1376  limb.InWater = true;
1377  if (limb.type == LimbType.Head) { headInWater = true; }
1378  }
1379  else if (newHull.WaterVolume > 0.0f && Submarine.RectContains(newHull.Rect, limb.Position))
1380  {
1381  if (limb.Position.Y < newHull.Surface)
1382  {
1383  limb.InWater = true;
1384  surfaceY = newHull.Surface;
1385  if (limb.type == LimbType.Head)
1386  {
1387  headInWater = true;
1388  }
1389  }
1390  //the limb has gone through the surface of the water
1391  if (Math.Abs(limb.LinearVelocity.Y) > 5.0f && limb.InWater != prevInWater && newHull == limb.Hull)
1392  {
1393  Splash(limb, newHull);
1394  //if the Character dropped into water, create a wave
1395  if (limb.LinearVelocity.Y < 0.0f)
1396  {
1397  Vector2 impulse = limb.LinearVelocity * limb.Mass;
1398  int n = (int)((limb.Position.X - newHull.Rect.X) / Hull.WaveWidth);
1399  newHull.WaveVel[n] += MathHelper.Clamp(impulse.Y, -5.0f, 5.0f);
1400  }
1401  }
1402  }
1403  limb.Hull = newHull;
1404  limb.Update(deltaTime);
1405  }
1406 
1407  bool isAttachedToController =
1408  character.SelectedItem?.GetComponent<Items.Components.Controller>() is { } controller &&
1409  controller.User == character &&
1410  controller.IsAttachedUser(controller.User);
1411 
1412  if (!inWater && character.AllowInput && levitatingCollider && !isAttachedToController)
1413  {
1415  {
1416  float targetY = standOnFloorY + ((float)Math.Abs(Math.Cos(Collider.Rotation)) * Collider.Height * 0.5f) + Collider.Radius + ColliderHeightFromFloor;
1417 
1418  const float LevitationSpeedMultiplier = 5f;
1419 
1420  // If the character is walking down a slope, target a position that moves along it
1421  float slopePull = 0f;
1422  if (floorNormal.Y is > 0f and < 1f
1423  && Math.Sign(movement.X) == Math.Sign(floorNormal.X))
1424  {
1425  float steepness = Math.Abs(floorNormal.X);
1426  slopePull = Math.Abs(movement.X * steepness) / LevitationSpeedMultiplier;
1427  }
1428 
1429  if (Math.Abs(Collider.SimPosition.Y - targetY - slopePull) > 0.01f)
1430  {
1431  float yVelocity = (targetY - Collider.SimPosition.Y) * LevitationSpeedMultiplier;
1432  if (Stairs != null && targetY < Collider.SimPosition.Y)
1433  {
1434  yVelocity = Math.Sign(yVelocity);
1435  }
1436 
1437  yVelocity -= slopePull * LevitationSpeedMultiplier;
1438 
1439  Collider.LinearVelocity = new Vector2(Collider.LinearVelocity.X, yVelocity);
1440  }
1441  }
1442  else
1443  {
1444  // Falling -> ragdoll briefly if we are not moving at all, because we are probably stuck.
1445  if (Collider.LinearVelocity == Vector2.Zero)
1446  {
1447  character.IsRagdolled = true;
1448  if (character.IsBot)
1449  {
1450  // Seems to work without this on player controlled characters -> not sure if we should call it always or just for the bots.
1451  character.SetInput(InputType.Ragdoll, hit: false, held: true);
1452  }
1453  }
1454  }
1455  }
1456  UpdateProjSpecific(deltaTime, cam);
1457  forceNotStanding = false;
1458  }
1459 
1460  private void CheckBodyInRest(float deltaTime)
1461  {
1462  if (SimplePhysicsEnabled) { return; }
1463 
1464  if (InWater || Collider.LinearVelocity.LengthSquared() > 0.01f || character.SelectedBy != null || !character.IsDead)
1465  {
1466  bodyInRestTimer = 0.0f;
1467  foreach (Limb limb in Limbs)
1468  {
1469  limb.body.PhysEnabled = true;
1470  }
1471  }
1472  else if (Limbs.All(l => l != null && !l.body.Enabled || l.LinearVelocity.LengthSquared() < 0.001f))
1473  {
1474  bodyInRestTimer += deltaTime;
1475  if (bodyInRestTimer > BodyInRestDelay)
1476  {
1477  foreach (Limb limb in Limbs)
1478  {
1479  limb.body.PhysEnabled = false;
1480  }
1481  }
1482  }
1483  }
1484 
1485  public bool Invalid { get; private set; }
1486  private int validityResets;
1487  private bool CheckValidity()
1488  {
1489  if (limbs == null)
1490  {
1491  DebugConsole.ThrowError("Attempted to check the validity of a potentially removed ragdoll. Character: " + character.Name + ", id: " + character.ID + ", removed: " + character.Removed + ", ragdoll removed: " + !list.Contains(this));
1492  Invalid = true;
1493  return false;
1494  }
1495  bool isColliderValid = CheckValidity(Collider);
1496  if (!isColliderValid) { Collider.ResetDynamics(); }
1497  bool limbsValid = true;
1498  foreach (Limb limb in limbs)
1499  {
1500  if (limb?.body == null || !limb.body.Enabled) { continue; }
1501  if (!CheckValidity(limb.body))
1502  {
1503  limbsValid = false;
1504  limb.body.ResetDynamics();
1505  break;
1506  }
1507  }
1508  bool isValid = isColliderValid && limbsValid;
1509  if (!isValid)
1510  {
1511  validityResets++;
1512  if (validityResets > 3)
1513  {
1514  Invalid = true;
1515  DebugConsole.ThrowError("Invalid ragdoll physics. Ragdoll frozen to prevent crashes.");
1516  Collider.SetTransform(Vector2.Zero, 0.0f);
1518  foreach (Limb limb in Limbs)
1519  {
1520  limb.body?.SetTransform(Collider.SimPosition, 0.0f);
1521  limb.body?.ResetDynamics();
1522  }
1523  Frozen = true;
1524  }
1525  }
1526  return isValid;
1527  }
1528 
1529  private bool CheckValidity(PhysicsBody body)
1530  {
1531  string errorMsg = null;
1532  if (!MathUtils.IsValid(body.SimPosition) || Math.Abs(body.SimPosition.X) > 1e10f || Math.Abs(body.SimPosition.Y) > 1e10f)
1533  {
1534  errorMsg = GetBodyName() + " position invalid (" + body.SimPosition + ", character: [name]).";
1535  }
1536  else if (!MathUtils.IsValid(body.LinearVelocity) || Math.Abs(body.LinearVelocity.X) > 1000f || Math.Abs(body.LinearVelocity.Y) > 1000f)
1537  {
1538  errorMsg = GetBodyName() + " velocity invalid (" + body.LinearVelocity + ", character: [name]).";
1539  }
1540  else if (!MathUtils.IsValid(body.Rotation))
1541  {
1542  errorMsg = GetBodyName() + " rotation invalid (" + body.Rotation + ", character: [name]).";
1543  }
1544  else if (!MathUtils.IsValid(body.AngularVelocity) || Math.Abs(body.AngularVelocity) > 1000f)
1545  {
1546  errorMsg = GetBodyName() + " angular velocity invalid (" + body.AngularVelocity + ", character: [name]).";
1547  }
1548  if (errorMsg != null)
1549  {
1551  {
1552  errorMsg += " Ragdoll controlled remotely.";
1553  }
1555  {
1556  errorMsg += " Simple physics enabled.";
1557  }
1558  if (GameMain.NetworkMember != null)
1559  {
1560  errorMsg += GameMain.NetworkMember.IsClient ? " Playing as a client." : " Hosting a server.";
1561  }
1562 
1563 #if DEBUG
1564  DebugConsole.ThrowError(errorMsg.Replace("[name]", Character.Name));
1565 #else
1566  DebugConsole.NewMessage(errorMsg.Replace("[name]", Character.Name), Color.Red);
1567 #endif
1568  GameAnalyticsManager.AddErrorEventOnce("Ragdoll.CheckValidity:" + character.ID, GameAnalyticsManager.ErrorSeverity.Error, errorMsg.Replace("[name]", Character.SpeciesName.Value));
1569 
1570  if (!MathUtils.IsValid(Collider.SimPosition) || Math.Abs(Collider.SimPosition.X) > 1e10f || Math.Abs(Collider.SimPosition.Y) > 1e10f)
1571  {
1572  Collider.SetTransform(Vector2.Zero, 0.0f);
1573  }
1574  foreach (Limb otherLimb in Limbs)
1575  {
1576  otherLimb.body.SetTransform(Collider.SimPosition, 0.0f);
1577  otherLimb.body.ResetDynamics();
1578  }
1579  SetInitialLimbPositions();
1580  return false;
1581  }
1582 
1583  string GetBodyName()
1584  {
1585  return body.UserData is Limb limb ? "Limb (" + limb.type + ")" : "Collider";
1586  }
1587 
1588  return true;
1589  }
1590 
1592  {
1593  if (!accessRemovedCharacterErrorShown)
1594  {
1595  string errorMsg = "Attempted to access a potentially removed ragdoll. Character: " + character.Name + ", id: " + character.ID + ", removed: " + character.Removed + ", ragdoll removed: " + !list.Contains(this);
1596  errorMsg += '\n' + Environment.StackTrace.CleanupStackTrace();
1597  DebugConsole.ThrowError(errorMsg);
1598  GameAnalyticsManager.AddErrorEventOnce(
1599  "Ragdoll:AccessRemoved",
1600  GameAnalyticsManager.ErrorSeverity.Error,
1601  "Attempted to access a potentially removed ragdoll. Character: " + character.SpeciesName + ", id: " + character.ID + ", removed: " + character.Removed + ", ragdoll removed: " + !list.Contains(this) + "\n" + Environment.StackTrace.CleanupStackTrace());
1602  accessRemovedCharacterErrorShown = true;
1603  }
1604  }
1605 
1606  partial void UpdateProjSpecific(float deltaTime, Camera cam);
1607 
1608  partial void Splash(Limb limb, Hull limbHull);
1609 
1610  private void UpdateHullFlowForces(float deltaTime)
1611  {
1612  if (currentHull == null) { return; }
1613 
1614  const float StunForceThreshold = 5.0f;
1615  const float StunDuration = 0.5f;
1616  const float ToleranceIncreaseSpeed = 5.0f;
1617  const float ToleranceDecreaseSpeed = 1.0f;
1618 
1619  //how much distance to a gap affects the force it exerts on the character
1620  const float DistanceFactor = 0.5f;
1621  const float ForceMultiplier = 0.035f;
1622 
1623  Vector2 flowForce = Vector2.Zero;
1624  foreach (Gap gap in Gap.GapList)
1625  {
1626  if (gap.Open <= 0.0f || !gap.linkedTo.Contains(currentHull) || gap.LerpedFlowForce.LengthSquared() < 0.01f) { continue; }
1627  float dist = Vector2.Distance(MainLimb.WorldPosition, gap.WorldPosition) * DistanceFactor;
1628  flowForce += Vector2.Normalize(gap.LerpedFlowForce) * (Math.Max(gap.LerpedFlowForce.Length() - dist, 0.0f) * ForceMultiplier);
1629  }
1630 
1631  //throwing conscious/moving characters around takes more force -> double the flow force
1632  if (character.CanMove) { flowForce *= 2.0f; }
1633  flowForce *= 1 - Math.Clamp(character.GetStatValue(StatTypes.FlowResistance), 0f, 1f);
1634 
1635  float flowForceMagnitude = flowForce.Length();
1636  float limbMultipier = limbs.Count(l => l.InWater) / (float)limbs.Length;
1637  //if the force strong enough, stun the character to let it get thrown around by the water
1638  if ((flowForceMagnitude * limbMultipier) - flowStunTolerance > StunForceThreshold)
1639  {
1640  character.Stun = Math.Max(character.Stun, StunDuration);
1641  flowStunTolerance = Math.Max(flowStunTolerance, flowForceMagnitude);
1642  }
1643 
1644  if (character == Character.Controlled && inWater && Screen.Selected?.Cam != null)
1645  {
1646  float shakeStrength = Math.Min(flowForceMagnitude / 10.0f, 5.0f) * limbMultipier;
1647  Screen.Selected.Cam.Shake = Math.Max(Screen.Selected.Cam.Shake, shakeStrength);
1648  }
1649 
1650  if (flowForceMagnitude > 0.0001f)
1651  {
1652  flowForce = Vector2.Normalize(flowForce) * Math.Max(flowForceMagnitude - flowForceTolerance, 0.0f);
1653  }
1654 
1655  if (flowForceTolerance <= flowForceMagnitude * 1.5f && inWater)
1656  {
1657  //build up "tolerance" to the flow force
1658  //ensures the character won't get permanently stuck by forces, while allowing sudden changes in flow to push the character hard
1659  flowForceTolerance += deltaTime * ToleranceIncreaseSpeed;
1660  flowStunTolerance = Math.Max(flowStunTolerance, flowForceTolerance);
1661  }
1662  else
1663  {
1664  flowForceTolerance = Math.Max(flowForceTolerance - deltaTime * ToleranceDecreaseSpeed, 0.0f);
1665  flowStunTolerance = Math.Max(flowStunTolerance - deltaTime * ToleranceDecreaseSpeed, 0.0f);
1666  }
1667 
1668  if (flowForce.LengthSquared() > 0.001f)
1669  {
1670  Collider.ApplyForce(flowForce * (Collider.Mass / Mass));
1671  foreach (Limb limb in limbs)
1672  {
1673  if (!limb.InWater) { continue; }
1674  limb.body.ApplyForce(flowForce * (limb.Mass / Mass * limbs.Length));
1675  }
1676  }
1677  }
1678 
1679  public void ForceRefreshFloorY()
1680  {
1681  lastFloorCheckPos = Vector2.Zero;
1682  }
1683 
1684  // Force check floor y at least once a second so that we'll drop through gaps that we are standing upon.
1685  private const float FloorYStaleTime = 1;
1686  private float floorYCheckTimer;
1687  private void RefreshFloorY(float deltaTime, bool ignoreStairs = false)
1688  {
1689  floorYCheckTimer -= deltaTime;
1690  PhysicsBody refBody = Collider;
1691  if (floorYCheckTimer < 0 ||
1692  lastFloorCheckIgnoreStairs != ignoreStairs ||
1693  lastFloorCheckIgnorePlatforms != IgnorePlatforms ||
1694  Vector2.DistanceSquared(lastFloorCheckPos, refBody.SimPosition) > 0.1f * 0.1f)
1695  {
1696  floorY = GetFloorY(refBody.SimPosition, ignoreStairs);
1697  lastFloorCheckPos = refBody.SimPosition;
1698  lastFloorCheckIgnoreStairs = ignoreStairs;
1699  lastFloorCheckIgnorePlatforms = IgnorePlatforms;
1700  // Add some randomness to prevent all stationary characters doing the checks at the same frame.
1701  floorYCheckTimer = FloorYStaleTime * Rand.Range(0.9f, 1.1f);
1702  }
1703  }
1704 
1705  private float GetFloorY(Vector2 simPosition, bool ignoreStairs = false)
1706  {
1707  onGround = false;
1708  Stairs = null;
1709  floorFixture = null;
1710  Vector2 rayStart = simPosition;
1711  float height = ColliderHeightFromFloor;
1712  if (HeadPosition.HasValue && MathUtils.IsValid(HeadPosition.Value)) { height = Math.Max(height, HeadPosition.Value); }
1713  if (TorsoPosition.HasValue && MathUtils.IsValid(TorsoPosition.Value)) { height = Math.Max(height, TorsoPosition.Value); }
1714 
1715  Vector2 rayEnd = rayStart - new Vector2(0.0f, height * 2f);
1716  Vector2 colliderBottomDisplay = ConvertUnits.ToDisplayUnits(GetColliderBottom());
1717 
1718  Fixture standOnFloorFixture = null;
1719  float standOnFloorFraction = 1;
1720  float closestFraction = 1;
1721  GameMain.World.RayCast((fixture, point, normal, fraction) =>
1722  {
1723  switch (fixture.CollisionCategories)
1724  {
1725  case Physics.CollisionStairs:
1726  if (inWater && TargetMovement.Y < 0.5f) { return -1; }
1727 
1728  if (character.SelectedBy == null && fraction < standOnFloorFraction)
1729  {
1730  Structure structure = fixture.Body.UserData as Structure;
1731  if (colliderBottomDisplay.Y >= structure.Rect.Y - structure.Rect.Height + 30 || TargetMovement.Y > 0.5f || Stairs != null)
1732  {
1733  standOnFloorFraction = fraction;
1734  standOnFloorFixture = fixture;
1735  }
1736  }
1737 
1738  if (ignoreStairs) { return -1; }
1739  break;
1740  case Physics.CollisionPlatform:
1741  Structure platform = fixture.Body.UserData as Structure;
1742 
1743  if (!IgnorePlatforms && fraction < standOnFloorFraction)
1744  {
1745  if (colliderBottomDisplay.Y >= platform.Rect.Y - 16 || (targetMovement.Y > 0.0f && Stairs == null))
1746  {
1747  standOnFloorFraction = fraction;
1748  standOnFloorFixture = fixture;
1749  }
1750  }
1751 
1752  if (colliderBottomDisplay.Y < platform.Rect.Y - 16 && (targetMovement.Y <= 0.0f || Stairs != null)) return -1;
1753  if (IgnorePlatforms && TargetMovement.Y < -0.5f || Collider.Position.Y < platform.Rect.Y) return -1;
1754  break;
1755  case Physics.CollisionWall:
1756  case Physics.CollisionLevel:
1757  if (!fixture.CollidesWith.HasFlag(Physics.CollisionCharacter)) { return -1; }
1758  if (fixture.Body.UserData is Submarine && character.Submarine != null) { return -1; }
1759  if (fixture.IsSensor) { return -1; }
1760  if (fraction < standOnFloorFraction)
1761  {
1762  standOnFloorFraction = fraction;
1763  standOnFloorFixture = fixture;
1764  }
1765  break;
1766  default:
1767  System.Diagnostics.Debug.Assert(false, "Floor raycast should not have hit a fixture with the collision category " + fixture.CollisionCategories);
1768  return -1;
1769  }
1770 
1771  if (fraction < closestFraction)
1772  {
1773  floorNormal = normal;
1774  closestFraction = fraction;
1775  }
1776 
1777  return closestFraction;
1778  }, rayStart, rayEnd, Physics.CollisionStairs | Physics.CollisionPlatform | Physics.CollisionWall | Physics.CollisionLevel);
1779 
1780  if (standOnFloorFixture != null && !IsHangingWithRope)
1781  {
1782  floorFixture = standOnFloorFixture;
1783  standOnFloorY = rayStart.Y + (rayEnd.Y - rayStart.Y) * standOnFloorFraction;
1784  if (rayStart.Y - standOnFloorY < Collider.Height * 0.5f + Collider.Radius + ColliderHeightFromFloor * 1.2f)
1785  {
1786  onGround = true;
1787  if (standOnFloorFixture.CollisionCategories == Physics.CollisionStairs)
1788  {
1789  Stairs = standOnFloorFixture.Body.UserData as Structure;
1790  }
1791  }
1792  }
1793 
1794  if (closestFraction >= 1) //raycast didn't hit anything
1795  {
1796  floorNormal = Vector2.UnitY;
1797  if (CurrentHull == null)
1798  {
1799  return -1000.0f;
1800  }
1801  else
1802  {
1803  float hullBottom = currentHull.Rect.Y - currentHull.Rect.Height;
1804  //check if there's a connected hull below
1805  foreach (var gap in currentHull.ConnectedGaps)
1806  {
1807  if (!gap.IsRoomToRoom || gap.Open < 1.0f || gap.ConnectedDoor != null || gap.IsHorizontal) { continue; }
1808  if (WorldPosition.X > gap.WorldRect.X && WorldPosition.X < gap.WorldRect.Right && gap.WorldPosition.Y < WorldPosition.Y)
1809  {
1810  var lowerHull = gap.linkedTo[0] == currentHull ? gap.linkedTo[1] : gap.linkedTo[0];
1811  hullBottom = Math.Min(hullBottom, lowerHull.Rect.Y - lowerHull.Rect.Height);
1812  }
1813  }
1814  return ConvertUnits.ToSimUnits(hullBottom);
1815  }
1816  }
1817  else
1818  {
1819  return rayStart.Y + (rayEnd.Y - rayStart.Y) * closestFraction;
1820  }
1821  }
1822 
1826  public float GetSurfaceY()
1827  {
1828  return GetWaterSurfaceAndCeilingY().WaterSurfaceY;
1829  }
1830 
1834  private (float WaterSurfaceY, float CeilingY) GetWaterSurfaceAndCeilingY()
1835  {
1836  //check both hulls: the hull whose coordinate space the ragdoll is in, and the hull whose bounds the character's origin actually is inside
1837  if (currentHull == null || character.CurrentHull == null)
1838  {
1839  return (float.PositiveInfinity, float.PositiveInfinity);
1840  }
1841 
1842  float surfaceY = currentHull.Surface;
1843  float ceilingY = currentHull.Rect.Y;
1844  float surfaceThreshold = ConvertUnits.ToDisplayUnits(Collider.SimPosition.Y + 1.0f);
1845  //if the hull is almost full of water, check if there's a water-filled hull above it
1846  //and use its water surface instead of the current hull's
1847  if (currentHull.Rect.Y - currentHull.Surface < 5.0f)
1848  {
1849  GetSurfacePos(currentHull, ref surfaceY, ref ceilingY);
1850  void GetSurfacePos(Hull hull, ref float prevSurfacePos, ref float ceilingPos)
1851  {
1852  if (prevSurfacePos > surfaceThreshold) { return; }
1853  foreach (Gap gap in hull.ConnectedGaps)
1854  {
1855  if (gap.IsHorizontal || gap.Open <= 0.0f || gap.WorldPosition.Y < hull.WorldPosition.Y) { continue; }
1856  if (Collider.SimPosition.X < ConvertUnits.ToSimUnits(gap.Rect.X) || Collider.SimPosition.X > ConvertUnits.ToSimUnits(gap.Rect.Right)) { continue; }
1857 
1858  //if the gap is above us and leads outside, there's no surface to limit the movement
1859  if (!gap.IsRoomToRoom && gap.Position.Y > hull.Position.Y)
1860  {
1861  ceilingPos += 100000.0f;
1862  prevSurfacePos += 100000.0f;
1863  return;
1864  }
1865 
1866  foreach (var linkedTo in gap.linkedTo)
1867  {
1868  if (linkedTo is Hull otherHull && otherHull != hull && otherHull != currentHull)
1869  {
1870  prevSurfacePos = Math.Max(surfaceY, otherHull.Surface);
1871  ceilingPos = Math.Max(ceilingPos, otherHull.Rect.Y);
1872  GetSurfacePos(otherHull, ref prevSurfacePos, ref ceilingPos);
1873  break;
1874  }
1875  }
1876  }
1877  }
1878  }
1879  return (surfaceY, ceilingY);
1880  }
1881 
1882  public void SetPosition(Vector2 simPosition, bool lerp = false, bool ignorePlatforms = true, bool forceMainLimbToCollider = false, bool moveLatchers = true)
1883  {
1884  if (!MathUtils.IsValid(simPosition))
1885  {
1886  DebugConsole.ThrowError("Attempted to move a ragdoll (" + character.Name + ") to an invalid position (" + simPosition + "). " + Environment.StackTrace.CleanupStackTrace());
1887  GameAnalyticsManager.AddErrorEventOnce(
1888  "Ragdoll.SetPosition:InvalidPosition",
1889  GameAnalyticsManager.ErrorSeverity.Error,
1890  "Attempted to move a ragdoll (" + character.SpeciesName + ") to an invalid position (" + simPosition + "). " + Environment.StackTrace.CleanupStackTrace());
1891  return;
1892  }
1893  if (MainLimb == null) { return; }
1894 
1895  Vector2 limbMoveAmount = forceMainLimbToCollider ? simPosition - MainLimb.SimPosition : simPosition - Collider.SimPosition;
1896 
1897  // A Work-around for an issue with teleporting the characters:
1898  // Detach every latcher when either one of the latchers or the target is teleported,
1899  // because otherwise all the characters are teleported to invalid positions.
1900  const float ForceDeattachThreshold = 10.0f;
1901  if (limbMoveAmount.LengthSquared() > ForceDeattachThreshold * ForceDeattachThreshold &&
1902  Character.AIController is EnemyAIController enemyAI && enemyAI.LatchOntoAI != null && enemyAI.LatchOntoAI.IsAttached)
1903  {
1904  var target = enemyAI.LatchOntoAI.TargetCharacter;
1905  if (target != null)
1906  {
1907  target.Latchers.ForEachMod(l => l?.DeattachFromBody(reset: true));
1908  target.Latchers.Clear();
1909  }
1910  enemyAI.LatchOntoAI.DeattachFromBody(reset: true);
1911  }
1912  Character.Latchers.ForEachMod(l => l?.DeattachFromBody(reset: true));
1913  Character.Latchers.Clear();
1914 
1915  if (lerp)
1916  {
1917  Collider.TargetPosition = simPosition;
1918  Collider.MoveToTargetPosition(true);
1919  }
1920  else
1921  {
1922  Collider.SetTransformIgnoreContacts(simPosition, Collider.Rotation);
1923  }
1924 
1925  if (!MathUtils.NearlyEqual(limbMoveAmount, Vector2.Zero))
1926  {
1927  foreach (Limb limb in Limbs)
1928  {
1929  if (limb.IsSevered) { continue; }
1930  //check visibility from the new position of the collider to the new position of this limb
1931  Vector2 movePos = limb.SimPosition + limbMoveAmount;
1932  TrySetLimbPosition(limb, simPosition, movePos, limb.Rotation, lerp, ignorePlatforms);
1933  }
1934  }
1935  }
1936 
1940  public bool IsHoldingToRope { get; private set; }
1941  protected bool shouldHoldToRope;
1942 
1946  public bool IsHangingWithRope { get; private set; }
1947  protected bool shouldHangWithRope;
1948 
1952  public bool IsDraggedWithRope { get; private set; }
1953  protected bool shouldBeDraggedWithRope;
1954 
1955  public void HangWithRope()
1956  {
1957  shouldHangWithRope = true;
1958  IsHangingWithRope = true;
1959  ResetPullJoints();
1960  onGround = false;
1961  levitatingCollider = false;
1962  }
1963 
1964  public void HoldToRope()
1965  {
1966  shouldHoldToRope = true;
1967  IsHoldingToRope = true;
1968  }
1969 
1970  public void DragWithRope()
1971  {
1972  shouldBeDraggedWithRope = true;
1973  IsDraggedWithRope = true;
1974  }
1975 
1976  protected void StopHangingWithRope()
1977  {
1978  shouldHangWithRope = false;
1979  IsHangingWithRope = false;
1980  }
1981 
1982  protected void StopHoldingToRope()
1983  {
1984  shouldHoldToRope = false;
1985  IsHoldingToRope = false;
1986  }
1987 
1989  {
1990  shouldBeDraggedWithRope = false;
1991  IsDraggedWithRope = false;
1992  }
1993 
1994  protected void TrySetLimbPosition(Limb limb, Vector2 original, Vector2 simPosition, float rotation, bool lerp = false, bool ignorePlatforms = true)
1995  {
1996  Vector2 movePos = simPosition;
1997  Vector2 prevPosition = limb.body.SimPosition;
1998  if (Vector2.DistanceSquared(original, simPosition) > 0.0001f)
1999  {
2000  Category collisionCategory = Physics.CollisionWall | Physics.CollisionLevel;
2001  if (!ignorePlatforms) { collisionCategory |= Physics.CollisionPlatform; }
2002 
2003  Body body = Submarine.PickBody(original, simPosition, null, collisionCategory);
2004 
2005  //if there's something in between the limbs
2006  if (body != null)
2007  {
2008  //move the limb close to the position where the raycast hit something
2009  movePos = original + ((simPosition - original) * Submarine.LastPickedFraction * 0.9f);
2010  }
2011  }
2012 
2013  if (lerp)
2014  {
2015  limb.body.TargetPosition = movePos;
2016  limb.body.TargetRotation = rotation;
2017  limb.body.MoveToTargetPosition(true);
2018  }
2019  else
2020  {
2021  limb.body.SetTransformIgnoreContacts(movePos, rotation);
2023  limb.PullJointEnabled = false;
2024  }
2025  foreach (var attachedProjectile in character.AttachedProjectiles)
2026  {
2027  if (attachedProjectile.IsAttachedTo(limb.body))
2028  {
2029  attachedProjectile.Item.SetTransform(
2030  attachedProjectile.Item.SimPosition + (movePos - prevPosition),
2031  attachedProjectile.Item.body.Rotation,
2032  findNewHull: false);
2033  }
2034  }
2035  }
2036 
2037 
2038  private bool collisionsDisabled;
2039  private double lastObstacleRayCastTime;
2040 
2041  protected void CheckDistFromCollider()
2042  {
2043  float allowedDist = Math.Max(Math.Max(Collider.Radius, Collider.Width), Collider.Height) * 2.0f;
2044  allowedDist = Math.Max(allowedDist, 1.0f);
2045  float resetDist = allowedDist * 5.0f;
2046 
2047  float obstacleCheckDist = 0.3f;
2048 
2049  Vector2 diff = Collider.SimPosition - MainLimb.SimPosition;
2050  float distSqrd = diff.LengthSquared();
2051 
2052  bool shouldReset = distSqrd > resetDist * resetDist;
2053  if (!shouldReset && distSqrd > obstacleCheckDist * obstacleCheckDist)
2054  {
2055  if (Timing.TotalTime > lastObstacleRayCastTime + 1 &&
2056  Submarine.PickBody(Collider.SimPosition, MainLimb.SimPosition, collisionCategory: Physics.CollisionWall) != null)
2057  {
2058  shouldReset = true;
2059  lastObstacleRayCastTime = Timing.TotalTime;
2060  }
2061  }
2062 
2063  if (shouldReset)
2064  {
2065  //ragdoll way too far, reset position
2066  SetPosition(Collider.SimPosition, lerp: true, forceMainLimbToCollider: true);
2067  }
2068  else if (distSqrd > allowedDist * allowedDist)
2069  {
2070  //ragdoll too far from the collider, disable collisions until it's close enough
2071  //(in case the ragdoll has gotten stuck somewhere)
2072 
2073  Vector2 forceDir = diff / (float)Math.Sqrt(distSqrd);
2074  foreach (Limb limb in Limbs)
2075  {
2076  if (limb.IsSevered) { continue; }
2077  limb.body.CollidesWith = Physics.CollisionNone;
2078  limb.body.ApplyForce(forceDir * limb.Mass * 10.0f, maxVelocity: 10.0f);
2079  }
2080 
2081  collisionsDisabled = true;
2082  }
2083  else if (collisionsDisabled)
2084  {
2085  //set the position of the ragdoll to make sure limbs don't get stuck inside walls when re-enabling collisions
2086  SetPosition(Collider.SimPosition, lerp: true);
2087  collisionsDisabled = false;
2088  //force collision categories to be updated
2089  prevCollisionCategory = Category.None;
2090  }
2091  }
2092 
2093  partial void UpdateNetPlayerPositionProjSpecific(float deltaTime, float lowestSubPos);
2094  private void UpdateNetPlayerPosition(float deltaTime)
2095  {
2096  if (GameMain.NetworkMember == null) return;
2097 
2098  float lowestSubPos = float.MaxValue;
2099  if (Submarine.Loaded.Any())
2100  {
2101  lowestSubPos = ConvertUnits.ToSimUnits(Submarine.Loaded.Min(s => s.HiddenSubPosition.Y - s.Borders.Height - 128.0f));
2102  for (int i = 0; i < character.MemState.Count; i++)
2103  {
2104  if (character.Submarine == null)
2105  {
2106  //transform in-sub coordinates to outside coordinates
2107  if (character.MemState[i].Position.Y > lowestSubPos)
2108  character.MemState[i].TransformInToOutside();
2109  }
2110  else if (currentHull?.Submarine != null)
2111  {
2112  //transform outside coordinates to in-sub coordinates
2113  if (character.MemState[i].Position.Y < lowestSubPos)
2114  character.MemState[i].TransformOutToInside(currentHull.Submarine);
2115  }
2116  }
2117  }
2118 
2119  UpdateNetPlayerPositionProjSpecific(deltaTime, lowestSubPos);
2120  }
2121 
2130  public Limb GetLimb(LimbType limbType, bool excludeSevered = true, bool excludeLimbsWithSecondaryType = false, bool useSecondaryType = false)
2131  {
2132  Limb limb = null;
2133  if (!HasMultipleLimbsOfSameType && !useSecondaryType && !excludeLimbsWithSecondaryType)
2134  {
2135  // Faster method, but doesn't work when there's multiple limbs of the same type or if we want to seek/exclude limbs with different conditions.
2136  if (limbDictionary.TryGetValue(limbType, out limb))
2137  {
2138  if (limb.Removed)
2139  {
2140  limb = null;
2141  }
2142  if (excludeSevered && limb is { IsSevered: true } )
2143  {
2144  limb = null;
2145  }
2146  }
2147  }
2148  if (limb == null)
2149  {
2150  // Didn't seek or find a (valid) limb of the matching type. If there's multiple limbs of the same type, check the other limbs.
2151  foreach (var l in limbs)
2152  {
2153  if (l.Removed) { continue; }
2154  if (useSecondaryType)
2155  {
2156  if (l.Params.SecondaryType != limbType) { continue; }
2157  }
2158  else if (l.type != limbType)
2159  {
2160  continue;
2161  }
2162  if (excludeSevered && l.IsSevered) { continue; }
2163  if (excludeLimbsWithSecondaryType && l.Params.SecondaryType != LimbType.None) { continue; }
2164  // Found a valid and match
2165  limb = l;
2166  break;
2167  }
2168  }
2169  return limb;
2170  }
2171 
2172  public Vector2? GetMouthPosition()
2173  {
2174  Limb mouthLimb = GetLimb(LimbType.Head);
2175  if (mouthLimb == null) { return null; }
2176  float cos = (float)Math.Cos(mouthLimb.Rotation);
2177  float sin = (float)Math.Sin(mouthLimb.Rotation);
2178  Vector2 bodySize = mouthLimb.body.GetSize();
2179  Vector2 offset = new Vector2(mouthLimb.MouthPos.X * bodySize.X / 2, mouthLimb.MouthPos.Y * bodySize.Y / 2);
2180  return mouthLimb.SimPosition + new Vector2(offset.X * cos - offset.Y * sin, offset.X * sin + offset.Y * cos);
2181  }
2182 
2183  public Vector2 GetColliderBottom()
2184  {
2185  float offset = 0.0f;
2186 
2187  if (!character.IsDead && character.Stun <= 0.0f && !character.IsIncapacitated)
2188  {
2189  offset = -ColliderHeightFromFloor;
2190  }
2191 
2192  float lowestBound = Collider.SimPosition.Y;
2193  if (Collider.FarseerBody.FixtureList != null)
2194  {
2195  for (int i = 0; i < Collider.FarseerBody.FixtureList.Count; i++)
2196  {
2197  Collider.FarseerBody.GetTransform(out FarseerPhysics.Common.Transform transform);
2198  Collider.FarseerBody.FixtureList[i].Shape.ComputeAABB(out FarseerPhysics.Collision.AABB aabb, ref transform, i);
2199 
2200  lowestBound = Math.Min(aabb.LowerBound.Y, lowestBound);
2201  }
2202  }
2203  return new Vector2(Collider.SimPosition.X, lowestBound + offset);
2204  }
2205 
2207  {
2208  Limb lowestLimb = null;
2209  foreach (Limb limb in Limbs)
2210  {
2211  if (limb.IsSevered) { continue; }
2212  if (lowestLimb == null)
2213  {
2214  lowestLimb = limb;
2215  }
2216  else if (limb.SimPosition.Y < lowestLimb.SimPosition.Y)
2217  {
2218  lowestLimb = limb;
2219  }
2220  }
2221 
2222  return lowestLimb;
2223  }
2224 
2225  public void ReleaseStuckLimbs()
2226  {
2227  // Commented out, because stuck limbs is not a feature that we currently use, as it would require that we sync all the limbs, which we don't do.
2228  //Limbs.ForEach(l => l.Release());
2229  }
2230 
2231  public void HideAndDisable(LimbType limbType, float duration = 0, bool ignoreCollisions = true)
2232  {
2233  foreach (var limb in Limbs)
2234  {
2235  if (limb.type == limbType)
2236  {
2237  limb.HideAndDisable(duration, ignoreCollisions);
2238  }
2239  }
2240  }
2241 
2242  public void RestoreTemporarilyDisabled() => Limbs.ForEach(l => l.ReEnable());
2243 
2244  public void Remove()
2245  {
2246  if (Limbs != null)
2247  {
2248  foreach (Limb l in Limbs)
2249  {
2250  l?.Remove();
2251  }
2252  limbs = null;
2253  }
2254 
2255  if (collider != null)
2256  {
2257  foreach (PhysicsBody b in collider)
2258  {
2259  b?.Remove();
2260  }
2261  collider = null;
2262  }
2263 
2264  if (LimbJoints != null)
2265  {
2266  foreach (var joint in LimbJoints)
2267  {
2268  var j = joint?.Joint;
2269  if (GameMain.World.JointList.Contains(j))
2270  {
2271  GameMain.World.Remove(j);
2272  }
2273  }
2274  LimbJoints = null;
2275  }
2276 
2277  list.Remove(this);
2278  }
2279 
2280  public static void RemoveAll()
2281  {
2282  for (int i = list.Count - 1; i >= 0; i--)
2283  {
2284  list[i].Remove();
2285  }
2286  System.Diagnostics.Debug.Assert(list.Count == 0, "Some ragdolls were not removed in Ragdoll.RemoveAll");
2287  }
2288  }
2289 }
virtual CanEnterSubmarine CanEnterSubmarine
Definition: AIController.cs:80
A special affliction type that gradually makes the character turn into another type of character....
static List< Limb > AttachHuskAppendage(Character character, AfflictionPrefabHusk matchingAffliction, Identifier huskedSpeciesName, ContentXElement appendageDefinition=null, Ragdoll ragdoll=null)
AfflictionPrefab is a prefab that defines a type of affliction that can be applied to a character....
static readonly PrefabCollection< AfflictionPrefab > Prefabs
AfflictionPrefabHusk is a special type of affliction that has added functionality for husk infection.
void SetInput(InputType inputType, bool hit, bool held)
bool IsRemotelyControlled
Is the character controlled remotely (either by another player, or a server-side AIController)
float GetStatValue(StatTypes statType, bool includeSaved=true)
void ApplyStatusEffects(ActionType actionType, float deltaTime)
AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, Vector2 impulseDirection, float deltaTime, bool playSound=true)
Item? SelectedItem
The primary selected item. It can be any device that character interacts with. This excludes items li...
static CharacterPrefab FindByFilePath(string filePath)
ContentXElement ConfigElement
Identifier GetAttributeIdentifier(string key, string def)
virtual Vector2 WorldPosition
Definition: Entity.cs:49
Submarine Submarine
Definition: Entity.cs:53
readonly ushort ID
Unique, but non-persistent identifier. Stays the same if the entities are created in the exactly same...
Definition: Entity.cs:43
static ContentPackage VanillaContent
Definition: GameMain.cs:71
static World World
Definition: GameMain.cs:28
static NetworkMember NetworkMember
Definition: GameMain.cs:41
static Gap FindAdjacent(IEnumerable< Gap > gaps, Vector2 worldPos, float allowedOrthogonalDist, bool allowRoomToRoom=false)
Definition: Gap.cs:764
static List< Gap > GapList
Definition: Gap.cs:15
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 Drop(Character dropper, bool createNetworkEvent=true, bool setTransform=true)
LatchOntoAI(XElement element, EnemyAIController enemyAI)
Definition: LatchOntoAI.cs:63
Vector2 LinearVelocity
Definition: Limb.cs:485
bool DoesMirror
Definition: Limb.cs:292
bool IgnoreCollisions
Definition: Limb.cs:231
readonly LimbParams Params
Definition: Limb.cs:214
Vector2 MouthPos
Definition: Limb.cs:258
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
void Remove()
Definition: Limb.cs:1443
readonly LimbType type
Definition: Limb.cs:227
Vector2 PullJointWorldAnchorB
Definition: Limb.cs:579
bool InWater
Definition: Limb.cs:223
Vector2 PullJointWorldAnchorA
Definition: Limb.cs:546
Vector2??? Position
Definition: Limb.cs:406
bool? IsSevered
Definition: Limb.cs:351
PhysicsBody body
Definition: Limb.cs:217
float?? Dir
Definition: Limb.cs:502
bool PullJointEnabled
Definition: Limb.cs:534
Vector2 SimPosition
Definition: Limb.cs:411
Vector2?? WorldPosition
Definition: Limb.cs:401
float Mass
Definition: Limb.cs:466
bool Hidden
Definition: Limb.cs:388
void MirrorPullJoint()
Definition: Limb.cs:766
void HideAndDisable(float duration=0, bool ignoreCollisions=true)
Definition: Limb.cs:974
float Rotation
Definition: Limb.cs:446
bool CanBeSevered
Definition: Limb.cs:27
float? UpperLimit
Definition: Limb.cs:117
Vector2? LocalAnchorA
Definition: Limb.cs:61
Vector2? LocalAnchorB
Definition: Limb.cs:77
float? LowerLimit
Definition: Limb.cs:105
readonly Limb LimbA
Definition: Limb.cs:30
Rectangle? WorldRect
Definition: MapEntity.cs:105
readonly List< MapEntity > linkedTo
Definition: MapEntity.cs:52
static bool IsValidShape(float radius, float height, float width)
void ApplyForce(Vector2 force, float maxVelocity=NetConfig.MaxPhysicsBodyVelocity)
bool SetTransform(Vector2 simPosition, float rotation, bool setPrevTransform=true)
void ApplyWaterForces()
Applies buoyancy, drag and angular drag caused by water
bool SetTransformIgnoreContacts(Vector2 simPosition, float rotation, bool setPrevTransform=true)
void FindHull(Vector2? worldPosition=null, bool setSubmarine=true)
Definition: Ragdoll.cs:1050
bool TryGetCollider(int index, out PhysicsBody collider)
Definition: Ragdoll.cs:152
void CreateJoints()
Definition: Ragdoll.cs:489
bool shouldBeDraggedWithRope
Definition: Ragdoll.cs:1953
void ResetJoints()
Resets the current joint values to the serialized joint params.
Definition: Ragdoll.cs:562
float?? ImpactTolerance
Definition: Ragdoll.cs:318
void CreateColliders()
Definition: Ragdoll.cs:466
Limb FindLowestLimb()
Definition: Ragdoll.cs:2206
void ResetLimbs()
Resets the current limb values to the serialized limb params.
Definition: Ragdoll.cs:570
Vector2? TargetMovement
Definition: Ragdoll.cs:298
void CreateLimbs()
Definition: Ragdoll.cs:526
void ResetPullJoints(Func< Limb, bool > condition=null)
Definition: Ragdoll.cs:1032
bool levitatingCollider
Definition: Ragdoll.cs:1227
Vector2 floorNormal
Definition: Ragdoll.cs:116
void ResetRagdoll()
Resets the serializable data to the currently selected ragdoll params. Always loads the xml stored on...
Definition: Ragdoll.cs:552
Vector2 targetMovement
Definition: Ragdoll.cs:108
Ragdoll(Character character, string seed, RagdollParams ragdollParams=null)
Definition: Ragdoll.cs:459
List< Limb > GetConnectedLimbs(Limb limb)
Definition: Ragdoll.cs:925
void StopHangingWithRope()
Definition: Ragdoll.cs:1976
abstract ? float HeadPosition
Definition: Ragdoll.cs:311
virtual void Flip()
Definition: Ragdoll.cs:963
PhysicsBody? Collider
Definition: Ragdoll.cs:145
Vector2 GetColliderBottom()
Definition: Ragdoll.cs:2183
bool OnLimbCollision(Fixture f1, Fixture f2, Contact contact)
Definition: Ragdoll.cs:699
bool SeverLimbJoint(LimbJoint limbJoint)
Definition: Ragdoll.cs:875
const float MAX_SPEED
Definition: Ragdoll.cs:295
static void UpdateAll(float deltaTime, Camera cam)
Definition: Ragdoll.cs:1042
List< PhysicsBody > collider
Definition: Ragdoll.cs:137
void LogAccessedRemovedCharacterError()
Definition: Ragdoll.cs:1591
void CheckDistFromCollider()
Definition: Ragdoll.cs:2041
Direction dir
Definition: Ragdoll.cs:133
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
void SaveRagdoll(string fileNameWithoutExtension=null)
Saves all serializable data in the currently selected ragdoll params. This method should properly han...
Definition: Ragdoll.cs:543
Structure Stairs
Definition: Ragdoll.cs:131
void RemoveLimb(Limb limb)
Definition: Ragdoll.cs:638
void SetPosition(Vector2 simPosition, bool lerp=false, bool ignorePlatforms=true, bool forceMainLimbToCollider=false, bool moveLatchers=true)
Definition: Ragdoll.cs:1882
abstract ? float HeadAngle
Definition: Ragdoll.cs:312
void RestoreTemporarilyDisabled()
bool? SimplePhysicsEnabled
Definition: Ragdoll.cs:257
Vector2 overrideTargetMovement
Definition: Ragdoll.cs:112
void TrySetLimbPosition(Limb limb, Vector2 original, Vector2 simPosition, float rotation, bool lerp=false, bool ignorePlatforms=true)
Definition: Ragdoll.cs:1994
Vector2? GetMouthPosition()
Definition: Ragdoll.cs:2172
float GetImpactDamage(float impact, float? impactTolerance=null)
Definition: Ragdoll.cs:867
abstract ? float TorsoPosition
Definition: Ragdoll.cs:313
readonly Character character
Definition: Ragdoll.cs:95
float GetSurfaceY()
Get the position of the surface of water at the position of the character, in display units (taking i...
Definition: Ragdoll.cs:1826
void HangWithRope()
Definition: Ragdoll.cs:1955
void SubtractMass(Limb limb)
Definition: Ragdoll.cs:213
void ForceRefreshFloorY()
Definition: Ragdoll.cs:1679
Vector2? WorldPosition
Definition: Ragdoll.cs:247
void UpdateRagdoll(float deltaTime, Camera cam)
Definition: Ragdoll.cs:1252
bool IgnorePlatforms
Definition: Ragdoll.cs:373
float ColliderHeightFromFloor
In sim units. Joint scale applied.
Definition: Ragdoll.cs:129
bool shouldHangWithRope
Definition: Ragdoll.cs:1947
bool HasMultipleLimbsOfSameType
Definition: Ragdoll.cs:68
void ReleaseStuckLimbs()
Definition: Ragdoll.cs:2225
float strongestImpact
Definition: Ragdoll.cs:97
Vector2 GetCenterOfMass()
Definition: Ragdoll.cs:993
Character Character
Definition: Ragdoll.cs:94
void StopGettingDraggedWithRope()
Definition: Ragdoll.cs:1988
void AddLimb(LimbParams limbParams)
Definition: Ragdoll.cs:609
virtual void Recreate(RagdollParams ragdollParams=null)
Call this to create the ragdoll from the RagdollParams.
Definition: Ragdoll.cs:378
void AddLimb(Limb limb)
Definition: Ragdoll.cs:627
static void RemoveAll()
Definition: Ragdoll.cs:2280
void Teleport(Vector2 moveAmount, Vector2 velocityChange, bool detachProjectiles=true)
Definition: Ragdoll.cs:1164
void MoveLimb(Limb limb, Vector2 pos, float amount, bool pullFromCenter=false)
if false, force is applied to the position of pullJoint
Definition: Ragdoll.cs:1027
Direction TargetDir
Definition: Ragdoll.cs:135
void StopHoldingToRope()
Definition: Ragdoll.cs:1982
Vector2 movement
Definition: Ragdoll.cs:106
void HideAndDisable(LimbType limbType, float duration=0, bool ignoreCollisions=true)
Definition: Ragdoll.cs:2231
void AddJoint(JointParams jointParams)
Definition: Ragdoll.cs:576
Fixture floorFixture
Definition: Ragdoll.cs:115
void DragWithRope()
Definition: Ragdoll.cs:1970
abstract ? float TorsoAngle
Definition: Ragdoll.cs:314
LimbJoint[] LimbJoints
Definition: Ragdoll.cs:90
override bool Reset(bool forceReload=false)
Resets the current properties to the xml (stored in memory). Force reload reloads the file from disk.
void TryApplyVariantScale(XDocument variantFile)
bool Save(string fileNameWithoutExtension=null)
List< ColliderParams > Colliders
List< LimbParams > Limbs
CanEnterSubmarine CanEnterSubmarine
List< JointParams > Joints
Submarine(SubmarineInfo info, bool showErrorMessages=true, Func< Submarine, List< MapEntity >> loadEntities=null, IdRemap linkedRemap=null)
static bool RectContains(Rectangle rect, Vector2 pos, bool inclusive=false)
static Body PickBody(Vector2 rayStart, Vector2 rayEnd, IEnumerable< Body > ignoredBodies=null, Category? collisionCategory=null, bool ignoreSensors=true, Predicate< Fixture > customPredicate=null, bool allowInsideFixture=false)
LimbType
Definition: Limb.cs:19
ActionType
ActionTypes define when a StatusEffect is executed.
Definition: Enums.cs:26
StatTypes
StatTypes are used to alter several traits of a character. They are mostly used by talents.
Definition: Enums.cs:195
@ Structure
Structures and hulls, but also items (for backwards support)!