Client LuaCsForBarotrauma
HumanoidAnimController.cs
4 using FarseerPhysics;
5 using Microsoft.Xna.Framework;
6 using System;
7 using System.Linq;
8 
9 namespace Barotrauma
10 {
12  {
13  private const float SteepestWalkableSlopeAngleDegrees = 55f;
14  private const float SlowlyWalkableSlopeAngleDegrees = 30f;
15 
16  private static readonly float SteepestWalkableSlopeNormalX =
17  MathF.Sin(MathHelper.ToRadians(SteepestWalkableSlopeAngleDegrees));
18  private static readonly float SlowlyWalkableSlopeNormalX =
19  MathF.Sin(MathHelper.ToRadians(SlowlyWalkableSlopeAngleDegrees));
20 
21  private const float MaxSpeedOnStairs = 1.7f;
22  private const float SteepSlopePushMagnitude = MaxSpeedOnStairs;
23 
24  public const float BreakFromGrabDistance = 1.4f;
25 
26  public override RagdollParams RagdollParams
27  {
28  get { return HumanRagdollParams; }
29  protected set { HumanRagdollParams = value as HumanRagdollParams; }
30  }
31 
32  private HumanRagdollParams _ragdollParams;
34  {
35  get
36  {
37  if (character.Info == null)
38  {
39  if (_ragdollParams == null)
40  {
42  }
43  return _ragdollParams;
44  }
46  }
47  protected set
48  {
49  if (character.Info == null)
50  {
51  _ragdollParams = value;
52  }
53  else
54  {
55  character.Info.Ragdoll = value;
56  }
57  }
58  }
59 
60  private HumanWalkParams _humanWalkParams;
62  {
63  get
64  {
65  if (_humanWalkParams == null)
66  {
68  }
69  return _humanWalkParams;
70  }
71  set { _humanWalkParams = value; }
72  }
73 
74  private HumanRunParams _humanRunParams;
76  {
77  get
78  {
79  if (_humanRunParams == null)
80  {
82  }
83  return _humanRunParams;
84  }
85  set { _humanRunParams = value; }
86  }
87 
88  private HumanCrouchParams _humanCrouchParams;
90  {
91  get
92  {
93  if (_humanCrouchParams == null)
94  {
95  _humanCrouchParams = HumanCrouchParams.GetDefaultAnimParams(character);
96  }
97  return _humanCrouchParams;
98  }
99  set { _humanCrouchParams = value; }
100  }
101 
102  private HumanSwimSlowParams _humanSwimSlowParams;
104  {
105  get
106  {
107  if (_humanSwimSlowParams == null)
108  {
109  _humanSwimSlowParams = HumanSwimSlowParams.GetDefaultAnimParams(character);
110  }
111  return _humanSwimSlowParams;
112  }
113  set { _humanSwimSlowParams = value; }
114  }
115 
116  private HumanSwimFastParams _humanSwimFastParams;
118  {
119  get
120  {
121  if (_humanSwimFastParams == null)
122  {
123  _humanSwimFastParams = HumanSwimFastParams.GetDefaultAnimParams(character);
124  }
125  return _humanSwimFastParams;
126  }
127  set { _humanSwimFastParams = value; }
128  }
129 
130  public new HumanGroundedParams CurrentGroundedParams => base.CurrentGroundedParams as HumanGroundedParams;
131 
132  public new HumanSwimParams CurrentSwimParams => base.CurrentSwimParams as HumanSwimParams;
133 
135 
137  {
138  get { return HumanWalkParams; }
139  set { HumanWalkParams = value as HumanWalkParams; }
140  }
141 
143  {
144  get { return HumanRunParams; }
145  set { HumanRunParams = value as HumanRunParams; }
146  }
147 
148  public override SwimParams SwimSlowParams
149  {
150  get { return HumanSwimSlowParams; }
151  set { HumanSwimSlowParams = value as HumanSwimSlowParams; }
152  }
153 
154  public override SwimParams SwimFastParams
155  {
156  get { return HumanSwimFastParams; }
157  set { HumanSwimFastParams = value as HumanSwimFastParams; }
158  }
159 
160  public bool Crouching { get; set; }
161 
162  private float upperLegLength = 0.0f, lowerLegLength = 0.0f;
163 
164  private readonly float movementLerp;
165 
166  private float cprAnimTimer, cprPumpTimer;
167 
168  private float fallingProneAnimTimer;
169  const float FallingProneAnimDuration = 1.0f;
170 
171  private bool swimming;
172  //time until the character can switch from walking to swimming or vice versa
173  //prevents rapid switches between swimming/walking if the water level is fluctuating around the minimum swimming depth
174  private float swimmingStateLockTimer;
175 
181 
182  public override Vector2 AimSourceSimPos
183  {
184  get
185  {
186  float shoulderHeight = Collider.Height / 2.0f;
187  if (inWater)
188  {
189  shoulderHeight += 0.4f;
190  }
191  else if (Crouching)
192  {
193  shoulderHeight -= 0.15f;
194  if (Crouching)
195  {
196  bool movingHorizontally = !MathUtils.NearlyEqual(TargetMovement.X, 0.0f);
197  if (!movingHorizontally)
198  {
200  }
201  }
202  }
203 
204  return Collider.SimPosition + new Vector2(
205  (float)Math.Sin(-Collider.Rotation),
206  (float)Math.Cos(-Collider.Rotation)) * shoulderHeight;
207  }
208  }
209 
210  public HumanoidAnimController(Character character, string seed, HumanRagdollParams ragdollParams = null) : base(character, seed, ragdollParams)
211  {
212  // TODO: load from the character info file?
213  movementLerp = RagdollParams?.MainElement?.GetAttributeFloat("movementlerp", 0.4f) ?? 0f;
214  }
215 
216  public override void Recreate(RagdollParams ragdollParams = null)
217  {
218  base.Recreate(ragdollParams);
219  CalculateLegLengths();
220  }
221 
222  private void CalculateLegLengths()
223  {
224  //calculate upper and lower leg length (atm this assumes that both legs are the same size)
225  LimbType upperLegType = LimbType.RightThigh;
226  LimbType lowerLegType = LimbType.RightLeg;
227  LimbType footType = LimbType.RightFoot;
228 
229  var waistJoint = GetJointBetweenLimbs(LimbType.Waist, upperLegType) ?? GetJointBetweenLimbs(LimbType.Torso, upperLegType);
230  Vector2 localAnchorWaist = Vector2.Zero;
231  Vector2 localAnchorKnee = Vector2.Zero;
232  if (waistJoint != null)
233  {
234  localAnchorWaist = waistJoint.LimbA.type == upperLegType ? waistJoint.LocalAnchorA : waistJoint.LocalAnchorB;
235  }
236  LimbJoint kneeJoint = GetJointBetweenLimbs(upperLegType, lowerLegType);
237  if (kneeJoint != null)
238  {
239  localAnchorKnee = kneeJoint.LimbA.type == upperLegType ? kneeJoint.LocalAnchorA : kneeJoint.LocalAnchorB;
240  }
241  upperLegLength = Vector2.Distance(localAnchorWaist, localAnchorKnee);
242 
243  LimbJoint ankleJoint = GetJointBetweenLimbs(lowerLegType, footType);
244  if (ankleJoint == null || kneeJoint == null) { return; }
245  lowerLegLength = Vector2.Distance(
246  kneeJoint.LimbA.type == lowerLegType ? kneeJoint.LocalAnchorA : kneeJoint.LocalAnchorB,
247  ankleJoint.LimbA.type == lowerLegType ? ankleJoint.LocalAnchorA : ankleJoint.LocalAnchorB);
248  lowerLegLength += Vector2.Distance(
249  ankleJoint.LimbA.type == footType ? ankleJoint.LocalAnchorA : ankleJoint.LocalAnchorB,
250  GetLimb(footType).PullJointLocalAnchorA);
251  }
252 
253  protected override void UpdateAnim(float deltaTime)
254  {
255  if (Frozen) { return; }
256  if (MainLimb == null) { return; }
257 
259  if (onGround && character.CanMove)
260  {
261  if ((character.SelectedItem?.GetComponent<Controller>()?.ControlCharacterPose ?? false) ||
262  (character.SelectedSecondaryItem?.GetComponent<Controller>()?.ControlCharacterPose ?? false) ||
263  character.SelectedSecondaryItem?.GetComponent<Ladder>() != null ||
265  {
266  Crouching = false;
267  }
268  ColliderIndex = Crouching && !swimming ? 1 : 0;
269  }
270 
271  //stun (= disable the animations) if the ragdoll receives a large enough impact
272  if (strongestImpact > 0.0f)
273  {
274  character.SetStun(MathHelper.Min(strongestImpact * 0.5f, 5.0f));
275  strongestImpact = 0.0f;
276  return;
277  }
278 
279  if (character.IsDead)
280  {
281  if (deathAnimTimer < deathAnimDuration)
282  {
283  deathAnimTimer += deltaTime;
284  //the force/torque used to move the limbs goes from 1 to 0 during the death anim duration
285  UpdateFallingProne(1.0f - deathAnimTimer / deathAnimDuration);
286  }
287  }
288  else
289  {
290  deathAnimTimer = 0.0f;
291  }
292 
293  if (!character.CanMove)
294  {
295  if (fallingProneAnimTimer < FallingProneAnimDuration && onGround)
296  {
297  fallingProneAnimTimer += deltaTime;
298  UpdateFallingProne(1.0f);
299  }
300  levitatingCollider = false;
301  Collider.FarseerBody.FixedRotation = false;
302  if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
303  {
304  if (Collider.Enabled)
305  {
306  //deactivating the collider -> make the main limb inherit the collider's velocity because it'll control the movement now
308  Collider.Enabled = false;
309  }
312  //reset pull joints to prevent the character from "hanging" mid-air if pull joints had been active when the character was still moving
313  //(except when dragging, then we need the pull joints)
314  if (!Draggable || character.SelectedBy == null)
315  {
316  ResetPullJoints();
317  }
318  }
319  return;
320  }
321  fallingProneAnimTimer = 0.0f;
322 
323  //re-enable collider
324  if (!Collider.Enabled)
325  {
326  var lowestLimb = FindLowestLimb();
327 
328  Collider.SetTransform(new Vector2(
330  Math.Max(lowestLimb.SimPosition.Y + (Collider.Radius + Collider.Height / 2), Collider.SimPosition.Y)),
332 
333  Collider.FarseerBody.ResetDynamics();
334  Collider.FarseerBody.LinearVelocity = MainLimb.LinearVelocity;
335  Collider.Enabled = true;
336  }
337 
338  if (swimming)
339  {
340  Collider.FarseerBody.FixedRotation = false;
341  }
342  else if (!Collider.FarseerBody.FixedRotation)
343  {
344  if (Math.Abs(MathUtils.GetShortestAngle(Collider.Rotation, 0.0f)) > 0.001f)
345  {
346  //rotate collider back upright
347  Collider.AngularVelocity = MathUtils.GetShortestAngle(Collider.Rotation, 0.0f) * 10.0f;
348  Collider.FarseerBody.FixedRotation = false;
349  }
350  else
351  {
352  Collider.FarseerBody.FixedRotation = true;
353  }
354  }
355  else
356  {
357  float angleDiff = MathUtils.GetShortestAngle(Collider.Rotation, 0.0f);
358  if (Math.Abs(angleDiff) > 0.001f)
359  {
361  }
362  }
363 
365  {
366  ApplyTestPose();
367  }
368  else if (character.SelectedBy == null)
369  {
370  if (character.LockHands)
371  {
372  var leftHand = GetLimb(LimbType.LeftHand);
373  var rightHand = GetLimb(LimbType.RightHand);
374 
375  var waist = GetLimb(LimbType.Waist) ?? GetLimb(LimbType.Torso);
376 
377  rightHand.Disabled = true;
378  leftHand.Disabled = true;
379 
380  Vector2 midPos = waist.SimPosition;
381  Matrix torsoTransform = Matrix.CreateRotationZ(waist.Rotation);
382 
383  midPos += Vector2.Transform(new Vector2(-0.3f * Dir, -0.2f), torsoTransform);
384  if (rightHand.PullJointEnabled) midPos = (midPos + rightHand.PullJointWorldAnchorB) / 2.0f;
387  }
388  if (Anim != Animation.UsingItem)
389  {
390  if (Anim != Animation.UsingItemWhileClimbing)
391  {
392  ResetPullJoints();
393  }
394  else
395  {
396  ResetPullJoints(l => l.IsLowerBody);
397  }
398  }
399  }
400 
402  {
403  UpdateStandingSimple();
407  return;
408  }
409 
410  if (character.SelectedCharacter != null)
411  {
413  }
414 
415  if (Anim != Animation.CPR)
416  {
417  cprAnimTimer = 0.0f;
418  cprPumpTimer = 0.0f;
419  }
420 
421  switch (Anim)
422  {
423  case Animation.Climbing:
424  case Animation.UsingItemWhileClimbing:
425  levitatingCollider = false;
426  UpdateClimbing();
427  UpdateUseItemTimer();
428  break;
429  case Animation.CPR:
430  UpdateCPR(deltaTime);
431  break;
432  case Animation.UsingItem:
433  default:
434  UpdateUseItemTimer();
435  swimmingStateLockTimer -= deltaTime;
437  {
438  swimming = false;
439  }
440  else if (swimming != inWater && swimmingStateLockTimer <= 0.0f)
441  {
442  //0.5 second delay for switching between swimming and walking
443  //prevents rapid switches between swimming/walking if the water level is fluctuating around the minimum swimming depth
444  swimming = inWater;
445  swimmingStateLockTimer = 0.5f;
446  }
447  if (character.SelectedItem?.Prefab is { GrabWhenSelected: true } &&
449  character.SelectedItem.body is not { Enabled: true } &&
451  {
452  bool moving = character.IsKeyDown(InputType.Left) || character.IsKeyDown(InputType.Right);
454  if (!moving)
455  {
456  Vector2 handPos = character.SelectedItem.WorldPosition - Vector2.UnitY * ConvertUnits.ToDisplayUnits(ArmLength / 2);
457  handPos.Y = Math.Max(handPos.Y, character.SelectedItem.WorldRect.Y - character.SelectedItem.WorldRect.Height);
459  allowMovement: false,
460  handPos);
461  }
462  }
463  if (swimming)
464  {
465  UpdateSwimming();
466  }
467  else if (character.SelectedItem == null || !(character.SelectedSecondaryItem?.GetComponent<Controller>() is { } controller) ||
468  !controller.ControlCharacterPose || !controller.UserInCorrectPosition)
469  {
470  UpdateStanding();
471  }
472  break;
473  }
474 
475  void UpdateUseItemTimer()
476  {
477  if (IsUsingItem)
478  {
479  useItemTimer -= deltaTime;
480  if (useItemTimer <= 0.0f)
481  {
482  StopUsingItem();
483  }
484  }
485  }
486 
487  if (Timing.TotalTime > FlipLockTime && TargetDir != dir && !IsStuck)
488  {
489  Flip();
490  }
491 
492  foreach (Limb limb in Limbs)
493  {
494  limb.Disabled = false;
495  }
496  wasAiming = aiming;
497  aiming = false;
499  aimingMelee = false;
500  if (!shouldHangWithRope)
501  {
503  }
504  if (!shouldHoldToRope)
505  {
507  }
509  {
511  }
512  shouldHoldToRope = false;
513  shouldHangWithRope = false;
514  shouldBeDraggedWithRope = false;
515  }
516 
517  void UpdateStanding()
518  {
519  var currentGroundedParams = CurrentGroundedParams;
520  if (currentGroundedParams == null) { return; }
521  Vector2 handPos;
522 
523  Limb leftFoot = GetLimb(LimbType.LeftFoot);
524  Limb rightFoot = GetLimb(LimbType.RightFoot);
525  Limb head = GetLimb(LimbType.Head);
526  Limb torso = GetLimb(LimbType.Torso);
527 
528  Limb waist = GetLimb(LimbType.Waist);
529 
530  Limb leftHand = GetLimb(LimbType.LeftHand);
531  Limb rightHand = GetLimb(LimbType.RightHand);
532 
533  Limb leftLeg = GetLimb(LimbType.LeftLeg);
534  Limb rightLeg = GetLimb(LimbType.RightLeg);
535 
536  bool onSlopeThatMakesSlow = Math.Abs(floorNormal.X) > SlowlyWalkableSlopeNormalX;
537  bool slowedDownBySlope = onSlopeThatMakesSlow && Math.Sign(floorNormal.X) == -Math.Sign(TargetMovement.X);
538  bool onSlopeTooSteepToClimb = Math.Abs(floorNormal.X) > SteepestWalkableSlopeNormalX;
539 
540  float walkCycleMultiplier = 1.0f;
541  if (Stairs != null || slowedDownBySlope)
542  {
543  TargetMovement = new Vector2(MathHelper.Clamp(TargetMovement.X, -MaxSpeedOnStairs, MaxSpeedOnStairs), TargetMovement.Y);
544  walkCycleMultiplier *= 1.5f;
545  }
546 
547  float getUpForce = currentGroundedParams.GetUpForce / RagdollParams.JointScale;
548 
549  Vector2 colliderPos = GetColliderBottom();
550  if (Math.Abs(TargetMovement.X) > 1.0f)
551  {
552  float slowdownAmount = 0.0f;
553  if (currentHull != null)
554  {
555  //TODO: take into account that the feet aren't necessarily in CurrentHull
556  //full slowdown (1.5f) when water is up to the torso
557  surfaceY = ConvertUnits.ToSimUnits(currentHull.Surface);
558  float bottomPos = Math.Max(colliderPos.Y, ConvertUnits.ToSimUnits(currentHull.Rect.Y - currentHull.Rect.Height));
559  slowdownAmount = MathHelper.Clamp((surfaceY - bottomPos) / TorsoPosition.Value, 0.0f, 1.0f) * 1.5f;
560  }
561 
562  float maxSpeed = Math.Max(TargetMovement.Length() - slowdownAmount, 1.0f);
563  TargetMovement = Vector2.Normalize(TargetMovement) * maxSpeed;
564  }
565 
566  float walkPosX = (float)Math.Cos(WalkPos);
567  float walkPosY = (float)Math.Sin(WalkPos);
568 
569  Vector2 stepSize = StepSize.Value;
570  stepSize.X *= walkPosX;
571  stepSize.Y *= walkPosY;
572 
573  float footMid = colliderPos.X;
574 
575  var herpes = character.CharacterHealth.GetAffliction("spaceherpes", false);
576  float herpesAmount = herpes == null ? 0 : herpes.Strength / herpes.Prefab.MaxStrength;
577  float legDamage = character.GetLegPenalty(startSum: -0.1f) * 1.1f;
578  float limpAmount = MathHelper.Lerp(0, 1, legDamage + herpesAmount);
579  if (limpAmount > 0.0f)
580  {
581  //make the footpos oscillate when limping
582  footMid += (Math.Max(Math.Abs(walkPosX) * limpAmount, 0.0f) * Math.Min(Math.Abs(TargetMovement.X), 0.3f)) * Dir;
583  }
584 
585  movement = overrideTargetMovement == Vector2.Zero ?
586  MathUtils.SmoothStep(movement, TargetMovement, movementLerp) :
588 
589  if (Math.Abs(movement.X) < 0.005f)
590  {
591  movement.X = 0.0f;
592  }
593 
594  movement.Y = 0.0f;
595 
596  if (head == null) { return; }
597  if (torso == null) { return; }
598 
599  bool isNotRemote = true;
600  if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { isNotRemote = !character.IsRemotelyControlled; }
601 
602  if (onGround && isNotRemote)
603  {
604  //move slower if collider isn't upright
605  float rotationFactor = (float)Math.Abs(Math.Cos(Collider.Rotation));
606 
607  Collider.LinearVelocity = new Vector2(
608  movement.X * rotationFactor,
610  }
611 
612  getUpForce *= Math.Max(head.SimPosition.Y - colliderPos.Y, 0.5f);
613 
614  torso.PullJointEnabled = true;
615  head.PullJointEnabled = true;
616  if (waist != null)
617  {
618  waist.PullJointEnabled = true;
619  }
620 
621  bool onSlope = Math.Abs(movement.X) > 0.01f && Math.Abs(floorNormal.X) > 0.1f && Math.Sign(floorNormal.X) != Math.Sign(movement.X);
622 
623  bool movingHorizontally = !MathUtils.NearlyEqual(TargetMovement.X, 0.0f);
624 
625  if (Stairs == null && onSlopeTooSteepToClimb)
626  {
627  if (Math.Sign(targetMovement.X) != Math.Sign(floorNormal.X))
628  {
629  targetMovement.X = Math.Sign(floorNormal.X) * SteepSlopePushMagnitude;
631  }
632  }
633 
634  if (Stairs != null || onSlope)
635  {
636  torso.PullJointWorldAnchorB = new Vector2(
637  MathHelper.SmoothStep(torso.SimPosition.X, footMid + movement.X * TorsoLeanAmount, getUpForce * 0.8f),
638  MathHelper.SmoothStep(torso.SimPosition.Y, colliderPos.Y + TorsoPosition.Value - Math.Abs(walkPosX * 0.05f), getUpForce * 2.0f));
639 
640  head.PullJointWorldAnchorB = new Vector2(
641  MathHelper.SmoothStep(head.SimPosition.X, footMid + movement.X * HeadLeanAmount, getUpForce * 0.8f),
642  MathHelper.SmoothStep(head.SimPosition.Y, colliderPos.Y + HeadPosition.Value - Math.Abs(walkPosX * 0.05f), getUpForce * 2.0f));
643 
644  if (waist != null)
645  {
646  waist.PullJointWorldAnchorB = waist.SimPosition - movement * 0.06f;
647  }
648  }
649  else
650  {
651  if (!onGround)
652  {
653  movement = Vector2.Zero;
654  }
655 
656  float stepLift = TargetMovement.X == 0.0f ? 0 :
657  (float)Math.Sin(WalkPos * currentGroundedParams.StepLiftFrequency + MathHelper.Pi * currentGroundedParams.StepLiftOffset) * (currentGroundedParams.StepLiftAmount / 100);
658 
659  float y = colliderPos.Y + stepLift;
660 
661  if (!torso.Disabled)
662  {
663  if (TorsoPosition.HasValue) { y += TorsoPosition.Value; }
664  if (Crouching && !movingHorizontally) { y -= HumanCrouchParams.MoveDownAmountWhenStationary; }
665  torso.PullJointWorldAnchorB =
666  MathUtils.SmoothStep(torso.SimPosition,
667  new Vector2(footMid + movement.X * TorsoLeanAmount, y), getUpForce);
668  }
669 
670  if (!head.Disabled)
671  {
672  y = colliderPos.Y + stepLift * currentGroundedParams.StepLiftHeadMultiplier;
673  if (HeadPosition.HasValue) { y += HeadPosition.Value; }
674  if (Crouching && !movingHorizontally) { y -= HumanCrouchParams.MoveDownAmountWhenStationary; }
675  head.PullJointWorldAnchorB =
676  MathUtils.SmoothStep(head.SimPosition,
677  new Vector2(footMid + movement.X * HeadLeanAmount, y), getUpForce * 1.2f);
678  }
679 
680  if (waist != null && !waist.Disabled)
681  {
682  waist.PullJointWorldAnchorB = waist.SimPosition + movement * 0.06f;
683  }
684  }
685 
686  if (TorsoAngle.HasValue && !torso.Disabled)
687  {
688  float torsoAngle = TorsoAngle.Value;
689  float herpesStrength = character.CharacterHealth.GetAfflictionStrengthByType(AfflictionPrefab.SpaceHerpesType);
690  if (Crouching && !movingHorizontally && !Aiming) { torsoAngle -= HumanCrouchParams.ExtraTorsoAngleWhenStationary; }
691  torsoAngle -= herpesStrength / 150.0f;
692  torso.body.SmoothRotate(torsoAngle * Dir, currentGroundedParams.TorsoTorque);
693  }
694  if (!head.Disabled)
695  {
696  if (!Aiming && currentGroundedParams.FixedHeadAngle && HeadAngle.HasValue)
697  {
698  float headAngle = HeadAngle.Value;
699  if (Crouching && !movingHorizontally) { headAngle -= HumanCrouchParams.ExtraHeadAngleWhenStationary; }
700  head.body.SmoothRotate(headAngle * Dir, currentGroundedParams.HeadTorque);
701  }
702  else
703  {
704  RotateHead(head);
705  }
706  }
707 
708  if (!onGround)
709  {
710  const float MaxFootVelocityDiff = 5.0f;
711  const float MaxFootVelocityDiffSqr = MaxFootVelocityDiff * MaxFootVelocityDiff;
712  //if the feet have a significantly different velocity from the main limb, try moving them back to a neutral pose below the torso
713  //this can happen e.g. when jumping over an obstacle: the feet can have a large upwards velocity during the walk/run animation,
714  //and just "letting go of the animations" would let them keep moving upwards, twisting the character to a weird pose
715  if ((leftFoot != null && (MainLimb.LinearVelocity - leftFoot.LinearVelocity).LengthSquared() > MaxFootVelocityDiffSqr) ||
716  (rightFoot != null && (MainLimb.LinearVelocity - rightFoot.LinearVelocity).LengthSquared() > MaxFootVelocityDiffSqr))
717  {
718  UpdateFallingProne(10.0f, moveHands: false, moveTorso: false, moveLegs: true);
719  }
720  return;
721  }
722 
723  Vector2 waistPos = waist != null ? waist.SimPosition : torso.SimPosition;
724 
725  if (movingHorizontally)
726  {
727  //progress the walking animation
728  WalkPos -= MathHelper.ToRadians(CurrentAnimationParams.CycleSpeed) * walkCycleMultiplier * movement.X;
729 
730  for (int i = -1; i < 2; i += 2)
731  {
732  Limb foot = i == -1 ? leftFoot : rightFoot;
733  if (foot == null) { continue; }
734 
735  Vector2 footPos = stepSize * -i;
736  footPos += new Vector2(Math.Sign(movement.X) * FootMoveOffset.X, FootMoveOffset.Y);
737 
738  if (footPos.Y < 0.0f) { footPos.Y = -0.15f; }
739 
740  //make the character limp if the feet are damaged
741  float footAfflictionStrength = character.CharacterHealth.GetAfflictionStrength(AfflictionPrefab.DamageType, foot, true);
742  footPos.X *= MathHelper.Lerp(1.0f, 0.75f, MathHelper.Clamp(footAfflictionStrength / 50.0f, 0.0f, 1.0f));
743 
744  if (currentGroundedParams.FootLiftHorizontalFactor > 0)
745  {
746  // Calculate the foot y dynamically based on the foot position relative to the waist,
747  // so that the foot aims higher when it's behind the waist and lower when it's in the front.
748  float xDiff = (foot.SimPosition.X - waistPos.X + FootMoveOffset.X) * Dir;
749  float min = MathUtils.InverseLerp(1, 0, currentGroundedParams.FootLiftHorizontalFactor);
750  float max = 1 + MathUtils.InverseLerp(0, 1, currentGroundedParams.FootLiftHorizontalFactor);
751  float xFactor = MathHelper.Lerp(min, max, MathUtils.InverseLerp(RagdollParams.JointScale, -RagdollParams.JointScale, xDiff));
752  footPos.Y *= xFactor;
753  }
754 
755  if (onSlope && Stairs == null)
756  {
757  footPos.Y *= 2.0f;
758  }
759  footPos.Y = Math.Min(waistPos.Y - colliderPos.Y - 0.4f, footPos.Y);
760 
761 #if CLIENT
762  if ((i == 1 && Math.Sign(Math.Sin(WalkPos)) > 0 && Math.Sign(walkPosY) < 0) ||
763  (i == -1 && Math.Sign(Math.Sin(WalkPos)) < 0 && Math.Sign(walkPosY) > 0))
764  {
765  PlayImpactSound(foot);
766  }
767 
768 #endif
769 
770  if (!foot.Disabled)
771  {
772  foot.DebugRefPos = colliderPos;
773  foot.DebugTargetPos = colliderPos + footPos;
774  MoveLimb(foot, colliderPos + footPos, currentGroundedParams.FootMoveStrength);
775  FootIK(foot, colliderPos + footPos,
776  currentGroundedParams.LegBendTorque, currentGroundedParams.FootTorque, currentGroundedParams.FootAngleInRadians);
777  }
778  }
779 
780  //calculate the positions of hands
781  handPos = torso.SimPosition;
782  handPos.X = -walkPosX * currentGroundedParams.HandMoveAmount.X;
783 
784  float lowerY = currentGroundedParams.HandClampY;
785 
786  handPos.Y = lowerY + (float)(Math.Abs(Math.Sin(WalkPos - Math.PI * 1.5f) * currentGroundedParams.HandMoveAmount.Y));
787 
788  Vector2 posAddition = new Vector2(Math.Sign(movement.X) * HandMoveOffset.X, HandMoveOffset.Y);
789 
790  if (rightHand != null && !rightHand.Disabled)
791  {
792  HandIK(rightHand,
793  torso.SimPosition + posAddition + new Vector2(-handPos.X, (Math.Sign(walkPosX) == Math.Sign(Dir)) ? handPos.Y : lowerY),
794  currentGroundedParams.ArmMoveStrength, currentGroundedParams.HandMoveStrength);
795  }
796  if (leftHand != null && !leftHand.Disabled)
797  {
798  HandIK(leftHand,
799  torso.SimPosition + posAddition + new Vector2(handPos.X, (Math.Sign(walkPosX) == Math.Sign(-Dir)) ? handPos.Y : lowerY),
800  currentGroundedParams.ArmMoveStrength, currentGroundedParams.HandMoveStrength);
801  }
802  }
803  else
804  {
805  for (int i = -1; i < 2; i += 2)
806  {
807  Vector2 footPos = colliderPos;
808  if (Crouching)
809  {
810  footPos = new Vector2(Math.Sign(stepSize.X * i) * Dir * 0.35f, colliderPos.Y);
811  if (Math.Sign(footPos.X) != Math.Sign(Dir))
812  {
813  //lift the foot at the back up a bit
814  footPos.Y += 0.15f;
815  }
816  footPos.X += colliderPos.X;
817  }
818  else
819  {
820  float footPosX = stepSize.X * i * 0.2f;
821  if (CurrentGroundedParams.StepSizeWhenStanding != Vector2.Zero)
822  {
823  footPosX = Math.Sign(stepSize.X) * CurrentGroundedParams.StepSizeWhenStanding.X * i;
824  }
825  footPos = new Vector2(colliderPos.X + footPosX, colliderPos.Y - 0.1f);
826  }
827  if (Stairs == null && !onSlopeThatMakesSlow)
828  {
829  footPos.Y = Math.Max(Math.Min(FloorY, footPos.Y + 0.5f), footPos.Y);
830  }
831  var foot = i == -1 ? rightFoot : leftFoot;
832  if (foot != null && !foot.Disabled)
833  {
834  foot.DebugRefPos = colliderPos;
835  foot.DebugTargetPos = footPos;
836  float footMoveForce = currentGroundedParams.FootMoveStrength;
837  float legBendTorque = currentGroundedParams.LegBendTorque;
838  if (Crouching)
839  {
840  // Keeps the pose
841  legBendTorque = 100;
842  footMoveForce *= 2;
843  }
844  MoveLimb(foot, footPos, footMoveForce);
845  FootIK(foot, footPos, legBendTorque, currentGroundedParams.FootTorque, currentGroundedParams.FootAngleInRadians);
846  }
847  }
848 
849  for (int i = 0; i < 2; i++)
850  {
851  var hand = i == 0 ? rightHand : leftHand;
852  if (hand == null || hand.Disabled) { continue; }
853 
854  var armType = i == 0 ? LimbType.RightArm : LimbType.LeftArm;
855  var foreArmType = i == 0 ? LimbType.RightForearm : LimbType.LeftForearm;
856 
857  //get the upper arm to point downwards
858  var arm = GetLimb(armType);
859  if (arm != null && Math.Abs(arm.body.AngularVelocity) < 10.0f)
860  {
861  arm.body.SmoothRotate(MathHelper.Clamp(-arm.body.AngularVelocity, -0.5f, 0.5f), arm.Mass * 50.0f * currentGroundedParams.ArmMoveStrength);
862  }
863 
864  //get the elbow to a neutral rotation
865  if (Math.Abs(hand.body.AngularVelocity) < 10.0f)
866  {
867  var forearm = GetLimb(foreArmType) ?? hand;
868  LimbJoint elbow = GetJointBetweenLimbs(armType, foreArmType) ?? GetJointBetweenLimbs(armType, hand.type);
869  if (elbow != null)
870  {
871  float diff = elbow.JointAngle - (Dir > 0 ? elbow.LowerLimit : elbow.UpperLimit);
872  forearm.body.ApplyTorque(MathHelper.Clamp(-diff, -MathHelper.PiOver2, MathHelper.PiOver2) * forearm.Mass * 100.0f * currentGroundedParams.ArmMoveStrength);
873  }
874  }
875  // Try to keep the wrist straight
876  LimbJoint wrist = GetJointBetweenLimbs(foreArmType, hand.type);
877  if (wrist != null)
878  {
879  hand.body.ApplyTorque(MathHelper.Clamp(-wrist.JointAngle, -MathHelper.PiOver2, MathHelper.PiOver2) * hand.Mass * 100f * currentGroundedParams.HandMoveStrength);
880  }
881  }
882  }
883  }
884 
885  void UpdateStandingSimple()
886  {
887  if (Math.Abs(movement.X) < 0.005f)
888  {
889  movement.X = 0.0f;
890  }
891  movement = MathUtils.SmoothStep(movement, TargetMovement, movementLerp);
892 
893  if (InWater)
894  {
896  }
897  else if (onGround && (!character.IsRemotelyControlled || (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)))
898  {
899  Collider.LinearVelocity = new Vector2(
900  movement.X,
902  }
903  }
904 
905  private float handCyclePos;
906  private float legCyclePos;
907  void UpdateSwimming()
908  {
909  if (CurrentSwimParams == null) { return; }
910  IgnorePlatforms = true;
911 
912  Vector2 footPos, handPos;
913 
914  float surfaceLimiter = 1.0f;
915 
916  Limb head = GetLimb(LimbType.Head);
917  Limb torso = GetLimb(LimbType.Torso);
918  if (head == null) { return; }
919  if (torso == null) { return; }
920 
921  if (currentHull != null && character.CurrentHull != null)
922  {
923  float surfacePos = GetSurfaceY();
924  float surfaceThreshold = ConvertUnits.ToDisplayUnits(Collider.SimPosition.Y + 1.0f);
925  surfaceLimiter = Math.Max(1.0f, surfaceThreshold - surfacePos);
926  }
927 
928  Limb leftHand = GetLimb(LimbType.LeftHand);
929  Limb rightHand = GetLimb(LimbType.RightHand);
930 
931  Limb leftFoot = GetLimb(LimbType.LeftFoot);
932  Limb rightFoot = GetLimb(LimbType.RightFoot);
933 
934  float rotation = MathHelper.WrapAngle(Collider.Rotation);
935  rotation = MathHelper.ToDegrees(rotation);
936  if (rotation < 0.0f)
937  {
938  rotation += 360;
939  }
940  float targetSpeed = TargetMovement.Length();
941  if (targetSpeed > 0.1f && !character.IsRemotelyControlled && !Aiming)
942  {
943  if (!IsUsingItem &&
944  !(character.SelectedItem?.GetComponent<Controller>()?.ControlCharacterPose ?? false) &&
945  !(character.SelectedSecondaryItem?.GetComponent<Controller>()?.ControlCharacterPose ?? false))
946  {
947  if (rotation > 20 && rotation < 170)
948  {
949  TargetDir = Direction.Left;
950  }
951  else if (rotation > 190 && rotation < 340)
952  {
953  TargetDir = Direction.Right;
954  }
955  }
956  }
957  if (Aiming)
958  {
959  Vector2 mousePos = ConvertUnits.ToSimUnits(character.CursorPosition);
960  Vector2 diff = (mousePos - torso.SimPosition) * Dir;
961  if (diff.LengthSquared() > MathUtils.Pow2(0.4f))
962  {
963  float newRotation = MathHelper.WrapAngle(MathUtils.VectorToAngle(diff) - MathHelper.PiOver4 * Dir);
965  }
966  }
967  else if (targetSpeed > 0.1f)
968  {
969  float newRotation = MathUtils.VectorToAngle(TargetMovement) - MathHelper.PiOver2;
971  }
972 
973  torso.body.MoveToPos(Collider.SimPosition + new Vector2((float)Math.Sin(-Collider.Rotation), (float)Math.Cos(-Collider.Rotation)) * 0.4f, 5.0f);
974 
975  movement = MathUtils.SmoothStep(movement, TargetMovement, 0.3f);
976 
977  if (TorsoAngle.HasValue)
978  {
979  torso.body.SmoothRotate(Collider.Rotation + TorsoAngle.Value * Dir, CurrentSwimParams.TorsoTorque);
980  }
981  else
982  {
983  torso.body.SmoothRotate(Collider.Rotation, CurrentSwimParams.TorsoTorque);
984  }
985 
986  if (!Aiming && CurrentSwimParams.FixedHeadAngle && HeadAngle.HasValue)
987  {
988  head.body.SmoothRotate(Collider.Rotation + HeadAngle.Value * Dir, CurrentSwimParams.HeadTorque);
989  }
990  else
991  {
992  RotateHead(head);
993  }
994 
995  const float DisableMovementAboveSurfaceThreshold = 50.0f;
996  //dont try to move upwards if head is already out of water
997  if (surfaceLimiter > 1.0f && TargetMovement.Y > 0.0f)
998  {
999  if (TargetMovement.X == 0.0f)
1000  {
1001  //pull head above water
1002  head.body.SmoothRotate(0.0f, 5.0f);
1003  WalkPos += 0.05f;
1004  }
1005  else
1006  {
1007  TargetMovement = new Vector2(
1008  (float)Math.Sqrt(targetSpeed * targetSpeed - TargetMovement.Y * TargetMovement.Y)
1009  * Math.Sign(TargetMovement.X),
1010  Math.Max(TargetMovement.Y, TargetMovement.Y * 0.2f));
1011 
1012  //turn head above the water
1013  head.body.ApplyTorque(Dir);
1014  }
1015  movement.Y *= Math.Max(0, 1.0f - ((surfaceLimiter - 1.0f) / DisableMovementAboveSurfaceThreshold));
1016 
1017  }
1018 
1019  bool isNotRemote = true;
1020  if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { isNotRemote = !character.IsRemotelyControlled; }
1021 
1022  if (isNotRemote)
1023  {
1024  float t = movementLerp;
1025  if (targetSpeed > 0.00001f && !SimplePhysicsEnabled)
1026  {
1027  Vector2 forward = VectorExtensions.Forward(Collider.Rotation + MathHelper.PiOver2);
1028  float dot = Vector2.Dot(forward, Vector2.Normalize(movement));
1029  if (dot < 0)
1030  {
1031  // Reduce the linear movement speed when not facing the movement direction
1032  t = MathHelper.Clamp((1 + dot) / 10, 0.01f, 0.1f);
1033  }
1034  }
1035  Vector2 targetVelocity = movement;
1036  //if we're too high above the surface, don't touch the vertical velocity of the collider unless we're heading down
1037  if (surfaceLimiter > DisableMovementAboveSurfaceThreshold)
1038  {
1039  targetVelocity.Y = Math.Min(Collider.LinearVelocity.Y, movement.Y);
1040  };
1041  Collider.LinearVelocity = Vector2.Lerp(Collider.LinearVelocity, targetVelocity, t);
1042  }
1043 
1044  WalkPos += movement.Length();
1045  legCyclePos += Math.Min(movement.LengthSquared() + Collider.AngularVelocity, 1.0f);
1046  handCyclePos += MathHelper.ToRadians(CurrentSwimParams.HandCycleSpeed) * Math.Sign(movement.X);
1047 
1048  float legMoveMultiplier = 1.0f;
1049  if (movement.LengthSquared() < 0.001f)
1050  {
1051  // Swimming in place (TODO: expose?)
1052  legMoveMultiplier = 0.3f;
1053  legCyclePos += 0.4f;
1054  handCyclePos += 0.1f;
1055  }
1056 
1057  var waist = GetLimb(LimbType.Waist) ?? GetLimb(LimbType.Torso);
1058  footPos = waist == null ? Vector2.Zero : waist.SimPosition - new Vector2((float)Math.Sin(-Collider.Rotation), (float)Math.Cos(-Collider.Rotation)) * (upperLegLength + lowerLegLength);
1059  Vector2 transformedFootPos = new Vector2((float)Math.Sin(legCyclePos / CurrentSwimParams.LegCycleLength) * CurrentSwimParams.LegMoveAmount * legMoveMultiplier, 0.0f);
1060  transformedFootPos = Vector2.Transform(transformedFootPos, Matrix.CreateRotationZ(Collider.Rotation));
1061 
1063  if (rightFoot != null && !rightFoot.Disabled)
1064  {
1065  FootIK(rightFoot, footPos - transformedFootPos, legTorque, CurrentSwimParams.FootTorque, CurrentSwimParams.FootAngleInRadians);
1066  }
1067  if (leftFoot != null && !leftFoot.Disabled)
1068  {
1069  FootIK(leftFoot, footPos + transformedFootPos, legTorque, CurrentSwimParams.FootTorque, CurrentSwimParams.FootAngleInRadians);
1070  }
1071 
1072  handPos = (torso.SimPosition + head.SimPosition) / 2.0f;
1073 
1074  //at the surface, not moving sideways OR not moving at all
1075  // -> hands just float around
1076  if ((!headInWater && TargetMovement.X == 0.0f && TargetMovement.Y > 0) || TargetMovement.LengthSquared() < 0.001f)
1077  {
1078  handPos += MathUtils.RotatePoint(Vector2.UnitX * Dir * 0.2f, torso.Rotation);
1079 
1080  float wobbleAmount = 0.1f;
1081 
1082  if (rightHand != null && !rightHand.Disabled)
1083  {
1084  MoveLimb(rightHand, new Vector2(
1085  handPos.X + (float)Math.Sin(handCyclePos / 1.5f) * wobbleAmount,
1086  handPos.Y + (float)Math.Sin(handCyclePos / 3.5f) * wobbleAmount - 0.25f), CurrentSwimParams.ArmMoveStrength);
1087  }
1088 
1089  if (leftHand != null && !leftHand.Disabled)
1090  {
1091  MoveLimb(leftHand, new Vector2(
1092  handPos.X + (float)Math.Sin(handCyclePos / 2.0f) * wobbleAmount,
1093  handPos.Y + (float)Math.Sin(handCyclePos / 3.0f) * wobbleAmount - 0.25f), CurrentSwimParams.ArmMoveStrength);
1094  }
1095 
1096  return;
1097  }
1098 
1099  handPos += head.LinearVelocity.ClampLength(1.0f) * 0.1f;
1100 
1101  // Not sure why the params has to be flipped, but it works.
1102  var handMoveAmount = CurrentSwimParams.HandMoveAmount.Flip();
1103  var handMoveOffset = CurrentSwimParams.HandMoveOffset.Flip();
1104  float handPosX = (float)Math.Cos(handCyclePos) * handMoveAmount.X * CurrentAnimationParams.CycleSpeed;
1105  float handPosY = (float)Math.Sin(handCyclePos) * handMoveAmount.Y * CurrentAnimationParams.CycleSpeed;
1106 
1107  Matrix rotationMatrix = Matrix.CreateRotationZ(torso.Rotation);
1108 
1109  if (rightHand != null && !rightHand.Disabled)
1110  {
1111  Vector2 rightHandPos = new Vector2(-handPosX, -handPosY) + handMoveOffset;
1112  rightHandPos.X = (Dir == 1.0f) ? Math.Max(0.3f, rightHandPos.X) : Math.Min(-0.3f, rightHandPos.X);
1113  rightHandPos = Vector2.Transform(rightHandPos, rotationMatrix);
1114  float speedMultiplier = Math.Min(character.SpeedMultiplier * (1 - Character.GetRightHandPenalty()), 1.0f);
1115  if (character.Inventory != null && character.Inventory.GetItemInLimbSlot(InvSlotType.RightHand) != null)
1116  {
1117  speedMultiplier = Math.Min(speedMultiplier, 0.1f);
1118  }
1119  HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier);
1120  // Try to keep the wrist straight
1121  LimbJoint wrist = GetJointBetweenLimbs(LimbType.RightForearm, LimbType.RightHand);
1122  if (wrist != null)
1123  {
1124  rightHand.body.ApplyTorque(MathHelper.Clamp(-wrist.JointAngle, -MathHelper.PiOver2, MathHelper.PiOver2) * rightHand.Mass * 100f * CurrentSwimParams.HandMoveStrength);
1125  }
1126  }
1127 
1128  if (leftHand != null && !leftHand.Disabled)
1129  {
1130  Vector2 leftHandPos = new Vector2(handPosX, handPosY) + handMoveOffset;
1131  leftHandPos.X = (Dir == 1.0f) ? Math.Max(0.3f, leftHandPos.X) : Math.Min(-0.3f, leftHandPos.X);
1132  leftHandPos = Vector2.Transform(leftHandPos, rotationMatrix);
1133  float speedMultiplier = Math.Min(character.SpeedMultiplier * (1 - Character.GetLeftHandPenalty()), 1.0f);
1134  if (character.Inventory != null && character.Inventory.GetItemInLimbSlot(InvSlotType.LeftHand) != null)
1135  {
1136  speedMultiplier = Math.Min(speedMultiplier, 0.1f);
1137  }
1138  HandIK(leftHand, handPos + leftHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier);
1139  // Try to keep the wrist straight
1140  LimbJoint wrist = GetJointBetweenLimbs(LimbType.LeftForearm, LimbType.LeftHand);
1141  if (wrist != null)
1142  {
1143  leftHand.body.ApplyTorque(MathHelper.Clamp(-wrist.JointAngle, -MathHelper.PiOver2, MathHelper.PiOver2) * leftHand.Mass * 100f * CurrentSwimParams.HandMoveStrength);
1144  }
1145  }
1146  }
1147 
1148  private float prevFootPos;
1149 
1150  void UpdateClimbing()
1151  {
1152  var ladder = character.SelectedSecondaryItem?.GetComponent<Ladder>();
1154  {
1155  Anim = Animation.None;
1156  return;
1157  }
1158  else if (ladder == null)
1159  {
1160  StopClimbing();
1161  return;
1162  }
1163 
1164  onGround = false;
1165  IgnorePlatforms = true;
1166 
1167  bool climbFast = targetMovement.Y > 3.0f;
1168  bool slide = targetMovement.Y < -1.1f;
1169  Vector2 tempTargetMovement = TargetMovement;
1170  tempTargetMovement.Y = climbFast ?
1171  Math.Min(tempTargetMovement.Y, 2.0f) :
1172  Math.Min(tempTargetMovement.Y, 1.0f);
1173 
1174  movement = MathUtils.SmoothStep(movement, tempTargetMovement, 0.3f);
1175 
1176  Limb leftFoot = GetLimb(LimbType.LeftFoot);
1177  Limb rightFoot = GetLimb(LimbType.RightFoot);
1178  Limb head = GetLimb(LimbType.Head);
1179  Limb torso = GetLimb(LimbType.Torso);
1180 
1181  Limb leftHand = GetLimb(LimbType.LeftHand);
1182  Limb rightHand = GetLimb(LimbType.RightHand);
1183 
1184  if (leftHand == null || rightHand == null || head == null || torso == null) { return; }
1185 
1186  Vector2 ladderSimPos = ConvertUnits.ToSimUnits(
1187  ladder.Item.Rect.X + ladder.Item.Rect.Width / 2.0f,
1188  ladder.Item.Rect.Y);
1189 
1190  Vector2 ladderSimSize = ConvertUnits.ToSimUnits(ladder.Item.Rect.Size.ToVector2());
1191 
1192  float lowestLadderSimPos = ladderSimPos.Y - ladderSimPos.Y;
1193  var lowestNearbyLadder = GetLowestNearbyLadder(ladder);
1194  if (lowestNearbyLadder != null && lowestNearbyLadder != ladder)
1195  {
1196  ladderSimSize.Y = ConvertUnits.ToSimUnits(ladder.Item.WorldRect.Y - (lowestNearbyLadder.Item.WorldRect.Y - lowestNearbyLadder.Item.Rect.Size.Y));
1197  }
1198 
1199  float stepHeight = ConvertUnits.ToSimUnits(30.0f);
1200  if (climbFast) { stepHeight *= 2; }
1201 
1202  if (currentHull == null && ladder.Item.Submarine != null)
1203  {
1204  ladderSimPos += ladder.Item.Submarine.SimPosition;
1205  }
1206  else if (currentHull?.Submarine != null && currentHull.Submarine != ladder.Item.Submarine && ladder.Item.Submarine != null)
1207  {
1208  ladderSimPos += ladder.Item.Submarine.SimPosition - currentHull.Submarine.SimPosition;
1209  }
1210  else if (currentHull?.Submarine != null && ladder.Item.Submarine == null)
1211  {
1212  ladderSimPos -= currentHull.Submarine.SimPosition;
1213  }
1214 
1215  float bottomPos = Collider.SimPosition.Y - ColliderHeightFromFloor - Collider.Radius - Collider.Height / 2.0f;
1216  float torsoPos = TorsoPosition ?? 0;
1217  MoveLimb(torso, new Vector2(ladderSimPos.X - 0.35f * Dir, bottomPos + torsoPos), 10.5f);
1218  float headPos = HeadPosition ?? 0;
1219  MoveLimb(head, new Vector2(ladderSimPos.X - 0.2f * Dir, bottomPos + headPos), 10.5f);
1220 
1221  Collider.MoveToPos(new Vector2(ladderSimPos.X - 0.1f * Dir, Collider.SimPosition.Y), 10.5f);
1222 
1223  Vector2 handPos = new Vector2(
1224  ladderSimPos.X,
1225  bottomPos + torsoPos + movement.Y * 0.1f - ladderSimPos.Y);
1226  if (climbFast) { handPos.Y -= stepHeight; }
1227 
1228  //prevent the hands from going above the top of the ladders
1229  handPos.Y = Math.Min(-0.5f, handPos.Y);
1230  if (!Aiming || !(character.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand)?.GetComponent<Holdable>()?.ControlPose ?? false) || Math.Abs(movement.Y) > 0.01f)
1231  {
1232  MoveLimb(rightHand,
1233  new Vector2(slide ? handPos.X + ladderSimSize.X * 0.5f : handPos.X,
1234  (slide ? handPos.Y : MathUtils.Round(handPos.Y, stepHeight * 2.0f)) + ladderSimPos.Y),
1235  5.2f);
1236  rightHand.body.ApplyTorque(Dir * 2.0f);
1237  }
1238  if (!Aiming || !(character.Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand)?.GetComponent<Holdable>()?.ControlPose ?? false) || Math.Abs(movement.Y) > 0.01f)
1239  {
1240  MoveLimb(leftHand,
1241  new Vector2(handPos.X - ladderSimSize.X * 0.5f,
1242  (slide ? handPos.Y : MathUtils.Round(handPos.Y - stepHeight, stepHeight * 2.0f) + stepHeight) + ladderSimPos.Y),
1243  5.2f); ;
1244  leftHand.body.ApplyTorque(Dir * 2.0f);
1245  }
1246 
1247  Vector2 footPos = new Vector2(
1248  handPos.X - Dir * 0.05f,
1249  bottomPos + ColliderHeightFromFloor - stepHeight * 2.7f - ladderSimPos.Y);
1250  if (climbFast) { footPos.Y += stepHeight; }
1251 
1252  //apply torque to the legs to make the knees bend
1253  Limb leftLeg = GetLimb(LimbType.LeftLeg);
1254  Limb rightLeg = GetLimb(LimbType.RightLeg);
1255 
1256  //only move the feet if they're above the bottom of the ladders
1257  //(if not, they'll just dangle in air, and the character holds itself up with it's arms)
1258  if (footPos.Y > -ladderSimSize.Y - 0.2f && leftFoot != null && rightFoot != null)
1259  {
1260  Limb refLimb = GetLimb(LimbType.Waist) ?? GetLimb(LimbType.Torso);
1261  bool leftLegBackwards = Math.Abs(leftLeg.body.Rotation - refLimb.body.Rotation) > MathHelper.Pi;
1262  bool rightLegBackwards = Math.Abs(rightLeg.body.Rotation - refLimb.body.Rotation) > MathHelper.Pi;
1263 
1264  if (slide)
1265  {
1266  if (!leftLegBackwards) { MoveLimb(leftFoot, new Vector2(footPos.X - ladderSimSize.X * 0.5f, footPos.Y + ladderSimPos.Y), 15.5f, true); }
1267  if (!rightLegBackwards) { MoveLimb(rightFoot, new Vector2(footPos.X, footPos.Y + ladderSimPos.Y), 15.5f, true); }
1268  }
1269  else
1270  {
1271  float leftFootPos = MathUtils.Round(footPos.Y + stepHeight, stepHeight * 2.0f) - stepHeight;
1272  float prevLeftFootPos = MathUtils.Round(prevFootPos + stepHeight, stepHeight * 2.0f) - stepHeight;
1273  if (!leftLegBackwards) { MoveLimb(leftFoot, new Vector2(footPos.X, leftFootPos + ladderSimPos.Y), 15.5f, true); }
1274 
1275  float rightFootPos = MathUtils.Round(footPos.Y, stepHeight * 2.0f);
1276  float prevRightFootPos = MathUtils.Round(prevFootPos, stepHeight * 2.0f);
1277  if (!rightLegBackwards) { MoveLimb(rightFoot, new Vector2(footPos.X, rightFootPos + ladderSimPos.Y), 15.5f, true); }
1278 #if CLIENT
1279  if (Math.Abs(leftFootPos - prevLeftFootPos) > stepHeight && leftFoot.LastImpactSoundTime < Timing.TotalTime - Limb.SoundInterval)
1280  {
1281  SoundPlayer.PlaySound("footstep_armor_heavy", leftFoot.WorldPosition, hullGuess: currentHull);
1282  leftFoot.LastImpactSoundTime = (float)Timing.TotalTime;
1283  }
1284  if (Math.Abs(rightFootPos - prevRightFootPos) > stepHeight && rightFoot.LastImpactSoundTime < Timing.TotalTime - Limb.SoundInterval)
1285  {
1286  SoundPlayer.PlaySound("footstep_armor_heavy", rightFoot.WorldPosition, hullGuess: currentHull);
1287  rightFoot.LastImpactSoundTime = (float)Timing.TotalTime;
1288  }
1289 #endif
1290  prevFootPos = footPos.Y;
1291  }
1292 
1293  if (!leftLegBackwards) { leftLeg.body.ApplyTorque(Dir * -8.0f); }
1294  if (!rightLegBackwards) { rightLeg.body.ApplyTorque(Dir * -8.0f); }
1295  }
1296 
1297  float movementFactor = (handPos.Y / stepHeight) * (float)Math.PI;
1298  movementFactor = 0.8f + (float)Math.Abs(Math.Sin(movementFactor));
1299 
1300  Vector2 subSpeed = currentHull != null || ladder.Item.Submarine == null
1301  ? Vector2.Zero : ladder.Item.Submarine.Velocity;
1302 
1303  //reached the top of the ladders -> can't go further up
1304  Vector2 climbForce = new Vector2(0.0f, movement.Y) * movementFactor;
1305 
1306  if (!InWater) { climbForce.Y += 0.3f * movementFactor; }
1307 
1308  if (character.SimPosition.Y > ladderSimPos.Y) { climbForce.Y = Math.Min(0.0f, climbForce.Y); }
1309  //reached the bottom -> can't go further down
1310  float minHeightFromFloor = ColliderHeightFromFloor / 2 + Collider.Height;
1311  if (floorFixture != null &&
1312  !floorFixture.CollisionCategories.HasFlag(Physics.CollisionStairs) &&
1313  !floorFixture.CollisionCategories.HasFlag(Physics.CollisionPlatform) &&
1314  character.SimPosition.Y < standOnFloorY + minHeightFromFloor)
1315  {
1316  climbForce.Y = MathHelper.Clamp((standOnFloorY + minHeightFromFloor - character.SimPosition.Y) * 5.0f, climbForce.Y, 1.0f);
1317  }
1318 
1319  //apply forces to the collider to move the Character up/down
1320  Collider.ApplyForce((climbForce * 20.0f + subSpeed * 50.0f) * Collider.Mass);
1321  if (Aiming)
1322  {
1323  RotateHead(head);
1324  }
1325  else if (Anim == Animation.UsingItemWhileClimbing && character.SelectedItem is { } selectedItem)
1326  {
1327  Vector2 diff = (selectedItem.WorldPosition - head.WorldPosition) * Dir;
1328  float targetRotation = MathHelper.WrapAngle(MathUtils.VectorToAngle(diff) - MathHelper.PiOver4 * Dir);
1329  head.body.SmoothRotate(targetRotation, force: WalkParams.HeadTorque);
1330  }
1331  else
1332  {
1333  float movementMultiplier = targetMovement.Y < 0 ? 0 : 1;
1334  head.body.SmoothRotate(MathHelper.PiOver4 * movementMultiplier * Dir, force: WalkParams.HeadTorque);
1335  }
1336 
1337  if (ladder.Item.Prefab.Triggers.None())
1338  {
1340  return;
1341  }
1342 
1343  Rectangle trigger = ladder.Item.Prefab.Triggers.FirstOrDefault();
1344  trigger = ladder.Item.TransformTrigger(trigger);
1345 
1346  bool isRemote = false;
1347  bool isClimbing = true;
1348  if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
1349  {
1350  isRemote = character.IsRemotelyControlled;
1351  }
1352  if (isRemote)
1353  {
1354  if (Math.Abs(targetMovement.X) > 0.05f ||
1355  (TargetMovement.Y < 0.0f && ConvertUnits.ToSimUnits(trigger.Height) + handPos.Y < HeadPosition) ||
1356  (TargetMovement.Y > 0.0f && handPos.Y > 0.1f))
1357  {
1358  isClimbing = false;
1359  }
1360  }
1361  else if ((character.IsKeyDown(InputType.Left) || character.IsKeyDown(InputType.Right)) &&
1363  {
1364  isClimbing = false;
1365  }
1366 
1367  if (!isClimbing)
1368  {
1370  IgnorePlatforms = false;
1371  }
1372 
1373  Ladder GetLowestNearbyLadder(Ladder currentLadder, float threshold = 16.0f)
1374  {
1375  foreach (Ladder ladder in Ladder.List)
1376  {
1377  if (ladder == currentLadder || !ladder.Item.IsInteractable(character)) { continue; }
1378  if (Math.Abs(ladder.Item.WorldPosition.X - currentLadder.Item.WorldPosition.X) > threshold) { continue; }
1379  if (ladder.Item.WorldPosition.Y > currentLadder.Item.WorldPosition.Y) { continue; }
1380  if ((currentLadder.Item.WorldRect.Y - currentLadder.Item.Rect.Height) - ladder.Item.WorldRect.Y > threshold) { continue; }
1381  return ladder;
1382  }
1383  return null;
1384  }
1385  }
1386 
1387  void UpdateFallingProne(float strength, bool moveHands = true, bool moveTorso = true, bool moveLegs = true)
1388  {
1389  if (strength <= 0.0f) { return; }
1390 
1391  Limb head = GetLimb(LimbType.Head);
1392  Limb torso = GetLimb(LimbType.Torso);
1393 
1394  if (moveHands && head != null && head.LinearVelocity.LengthSquared() > 1.0f && !head.IsSevered)
1395  {
1396  //if the head is moving, try to protect it with the hands
1397  Limb leftHand = GetLimb(LimbType.LeftHand);
1398  Limb rightHand = GetLimb(LimbType.RightHand);
1399 
1400  //move hands in front of the head in the direction of the movement
1401  Vector2 protectPos = head.SimPosition + Vector2.Normalize(head.LinearVelocity);
1402  if (rightHand != null && !rightHand.IsSevered)
1403  {
1404  HandIK(rightHand, protectPos, strength * 0.1f);
1405  }
1406  if (leftHand != null && !leftHand.IsSevered)
1407  {
1408  HandIK(leftHand, protectPos, strength * 0.1f);
1409  }
1410  }
1411 
1412  if (torso == null) { return; }
1413 
1414  //make the torso tip over
1415  //otherwise it tends to just drop straight down, pinning the characters legs in a weird pose
1416  if (moveTorso && !InWater)
1417  {
1418  //prefer tipping over in the same direction the torso is rotating
1419  //or moving
1420  //or lastly, in the direction it's facing if it's not moving/rotating
1421  float fallDirection = Math.Sign(torso.body.AngularVelocity - torso.body.LinearVelocity.X - Dir * 0.01f);
1422  float torque = MathF.Cos(torso.Rotation) * fallDirection * 5.0f * strength;
1423  torso.body.ApplyTorque(torque * torso.body.Mass);
1424  }
1425 
1426  //attempt to make legs stay in a straight line with the torso to prevent the character from doing a split
1427  if (moveLegs)
1428  {
1429  for (int i = 0; i < 2; i++)
1430  {
1431  var thigh = i == 0 ? GetLimb(LimbType.LeftThigh) : GetLimb(LimbType.RightThigh);
1432  if (thigh == null) { continue; }
1433  if (thigh.IsSevered) { continue; }
1434  float thighDiff = Math.Abs(MathUtils.GetShortestAngle(torso.Rotation, thigh.Rotation));
1435  float diff = torso.Rotation - thigh.Rotation;
1436  if (MathUtils.IsValid(diff))
1437  {
1438  float thighTorque = thighDiff * thigh.Mass * Math.Sign(diff) * 5.0f;
1439  thigh.body.ApplyTorque(thighTorque * strength);
1440  }
1441 
1442  var leg = i == 0 ? GetLimb(LimbType.LeftLeg) : GetLimb(LimbType.RightLeg);
1443  if (leg == null || leg.IsSevered) { continue; }
1444  float legDiff = Math.Abs(MathUtils.GetShortestAngle(torso.Rotation, leg.Rotation));
1445  diff = torso.Rotation - leg.Rotation;
1446  if (MathUtils.IsValid(diff))
1447  {
1448  float legTorque = legDiff * leg.Mass * Math.Sign(diff) * 5.0f;
1449  leg.body.ApplyTorque(legTorque * strength);
1450  }
1451  }
1452  }
1453  }
1454 
1455  private float lastReviveTime;
1456 
1457  private void UpdateCPR(float deltaTime)
1458  {
1459  if (character.SelectedCharacter == null ||
1461  {
1462  Anim = Animation.None;
1463  return;
1464  }
1465 
1467 
1468  Crouching = true;
1469 
1470  Vector2 offset = Vector2.UnitX * -Dir * 0.75f;
1471  Vector2 diff = (target.SimPosition + offset) - character.SimPosition;
1472  Limb targetHead = target.AnimController.GetLimb(LimbType.Head);
1473  Limb targetTorso = target.AnimController.GetLimb(LimbType.Torso);
1474  if (targetTorso == null)
1475  {
1476  Anim = Animation.None;
1477  return;
1478  }
1479 
1480  Limb head = GetLimb(LimbType.Head);
1481  Limb torso = GetLimb(LimbType.Torso);
1482 
1483  Vector2 headDiff = targetHead == null ? diff : targetHead.SimPosition - character.SimPosition;
1484  targetMovement = new Vector2(diff.X, 0.0f);
1485  const float CloseEnough = 0.1f;
1486  if (Math.Abs(targetMovement.X) < CloseEnough)
1487  {
1488  targetMovement.X = 0.0f;
1489  }
1490 
1491  TargetDir = headDiff.X > 0.0f ? Direction.Right : Direction.Left;
1492  //if the target's in some weird pose, we may not be able to flip it so it's facing up,
1493  //so let's only try it once so we don't end up constantly flipping it
1494  if (cprAnimTimer <= 0.0f && target.AnimController.Direction == TargetDir)
1495  {
1496  target.AnimController.Flip();
1497  }
1498  (target.AnimController as HumanoidAnimController)?.UpdateFallingProne(strength: 1.0f, moveHands: false, moveTorso: false);
1499 
1500  head.Disabled = true;
1501  torso.Disabled = true;
1502 
1503  UpdateStanding();
1504 
1505  Vector2 handPos = targetTorso.SimPosition + Vector2.UnitY * 0.2f;
1506 
1507  Grab(handPos, handPos);
1508 
1509  Vector2 colliderPos = GetColliderBottom();
1510 
1511  float prevVitality = target.Vitality;
1512  bool wasCritical = prevVitality < 0.0f;
1513 
1514  if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) //Serverside code
1515  {
1516  target.Oxygen += deltaTime * 0.5f; //Stabilize them
1517  }
1518 
1519  float cprBoost = character.GetStatValue(StatTypes.CPRBoost);
1520 
1521  int skill = (int)character.GetSkillLevel("medical");
1522 
1523  if (GameMain.NetworkMember is not { IsClient: true })
1524  {
1525  if (cprBoost >= 1f)
1526  {
1527  //prevent the patient from suffocating no matter how fast their oxygen level is dropping
1528  target.Oxygen = Math.Max(target.Oxygen, -10.0f);
1529  }
1530  }
1531 
1532  //Serverside code
1533  if (GameMain.NetworkMember is not { IsClient: true })
1534  {
1535  if (target.Oxygen < -10.0f)
1536  {
1537  //stabilize the oxygen level but don't allow it to go positive and revive the character yet
1538  float stabilizationAmount = skill * CPRSettings.Active.StabilizationPerSkill;
1539  stabilizationAmount = MathHelper.Clamp(stabilizationAmount, CPRSettings.Active.StabilizationMin, CPRSettings.Active.StabilizationMax);
1540  character.Oxygen -= 1.0f / stabilizationAmount * deltaTime; //Worse skill = more oxygen required
1541  if (character.Oxygen > 0.0f) { target.Oxygen += stabilizationAmount * deltaTime; } //we didn't suffocate yet did we
1542  }
1543  }
1544 
1545  if (targetHead != null && head != null)
1546  {
1547  head.PullJointWorldAnchorB = new Vector2(targetHead.SimPosition.X, targetHead.SimPosition.Y + 0.8f);
1548  head.PullJointEnabled = true;
1549  }
1550 
1551  torso.PullJointWorldAnchorB = new Vector2(torso.SimPosition.X, colliderPos.Y + (TorsoPosition.Value - 0.1f));
1552  torso.PullJointEnabled = true;
1553 
1554  if (cprPumpTimer >= 1)
1555  {
1556  torso.body.ApplyLinearImpulse(new Vector2(0, -20f), maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
1557  targetTorso.body.ApplyLinearImpulse(new Vector2(0, -20f), maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
1558  //the pumping animation can sometimes cause impact damage, prevent that by briefly disabling it
1559  target.DisableImpactDamageTimer = 0.15f;
1560  cprPumpTimer = 0;
1561 
1562  if (skill < CPRSettings.Active.DamageSkillThreshold)
1563  {
1564  target.LastDamageSource = null;
1565  target.DamageLimb(
1566  targetTorso.WorldPosition, targetTorso,
1567  new[] { CPRSettings.Active.InsufficientSkillAffliction.Instantiate((CPRSettings.Active.DamageSkillThreshold - skill) * CPRSettings.Active.DamageSkillMultiplier, source: character) },
1568  stun: 0.0f,
1569  playSound: true,
1570  attackImpulse: Vector2.Zero,
1571  attacker: null);
1572  }
1573  //need to CPR for at least a couple of seconds before the target can be revived
1574  //(reviving the target when the CPR has barely started looks strange)
1575  if (cprAnimTimer > 2.0f && GameMain.NetworkMember is not { IsClient: true })
1576  {
1577  float reviveChance = skill * CPRSettings.Active.ReviveChancePerSkill;
1578  reviveChance = (float)Math.Pow(reviveChance, CPRSettings.Active.ReviveChanceExponent);
1579  reviveChance = MathHelper.Clamp(reviveChance, CPRSettings.Active.ReviveChanceMin, CPRSettings.Active.ReviveChanceMax);
1580  reviveChance *= 1f + cprBoost;
1581 
1582  if (Rand.Range(0.0f, 1.0f, Rand.RandSync.ServerAndClient) <= reviveChance)
1583  {
1584  //increase oxygen and clamp it above zero
1585  // -> the character should be revived if there are no major afflictions in addition to lack of oxygen
1586  target.Oxygen = Math.Max(target.Oxygen + 10.0f, 10.0f);
1587  GameMain.LuaCs.Hook.Call("human.CPRSuccess", this);
1588  }
1589  else
1590  {
1591  GameMain.LuaCs.Hook.Call("human.CPRFailed", this);
1592  }
1593  }
1594  }
1595  cprPumpTimer += deltaTime;
1596  cprAnimTimer += deltaTime;
1597 
1598  //got the character back into a non-critical state, increase medical skill
1599  //BUT only if it has been more than 10 seconds since the character revived someone
1600  //otherwise it's easy to abuse the system by repeatedly reviving in a low-oxygen room
1601  if (!target.IsDead)
1602  {
1603  target.CharacterHealth.CalculateVitality();
1604  if (wasCritical && target.Vitality > 0.0f && Timing.TotalTime > lastReviveTime + 10.0f)
1605  {
1606  character.Info?.ApplySkillGain(Tags.MedicalSkill, SkillSettings.Current.SkillIncreasePerCprRevive);
1607  AchievementManager.OnCharacterRevived(target, character);
1608  lastReviveTime = (float)Timing.TotalTime;
1609 #if SERVER
1610  GameMain.Server?.KarmaManager?.OnCharacterHealthChanged(target, character, damage: Math.Min(prevVitality - target.Vitality, 0.0f), stun: 0.0f);
1611 #endif
1612  //reset attacker, we don't want the character to start attacking us
1613  //because we caused a bit of damage to them during CPR
1614  target.ForgiveAttacker(character);
1615  }
1616  }
1617  }
1618 
1619  public override void DragCharacter(Character target, float deltaTime)
1620  {
1621  if (target == null) { return; }
1622 
1623  Limb torso = GetLimb(LimbType.Torso);
1624  Limb leftHand = GetLimb(LimbType.LeftHand);
1625  Limb rightHand = GetLimb(LimbType.RightHand);
1626 
1627  Limb targetLeftHand =
1628  target.AnimController.GetLimb(LimbType.LeftForearm) ??
1629  target.AnimController.GetLimb(LimbType.Torso) ??
1630  target.AnimController.MainLimb;
1631 
1632  Limb targetRightHand =
1633  target.AnimController.GetLimb(LimbType.RightForearm) ??
1634  target.AnimController.GetLimb(LimbType.Torso) ??
1635  target.AnimController.MainLimb;
1636 
1637  if (!target.AllowInput)
1638  {
1640  }
1641 
1642  bool targetPoseControlled =
1643  target.SelectedItem?.GetComponent<Controller>() is { ControlCharacterPose: true } ||
1644  target.SelectedSecondaryItem?.GetComponent<Controller>() is { ControlCharacterPose: true };
1645 
1646  if (IsClimbing)
1647  {
1648  //cannot drag up ladders if the character is conscious
1649  if (target.AllowInput && (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient))
1650  {
1651  character.DeselectCharacter();
1652  return;
1653  }
1654  Limb targetTorso = target.AnimController.GetLimb(LimbType.Torso);
1655  targetTorso ??= target.AnimController.MainLimb;
1656  if (target.AnimController.Dir != Dir)
1657  {
1658  target.AnimController.Flip();
1659  }
1660  Vector2 transformedTorsoPos = torso.SimPosition;
1661  if (character.Submarine == null && target.Submarine != null)
1662  {
1663  transformedTorsoPos -= target.Submarine.SimPosition;
1664  }
1665  else if (character.Submarine != null && target.Submarine == null)
1666  {
1667  transformedTorsoPos += character.Submarine.SimPosition;
1668  }
1669  else if (character.Submarine != null && target.Submarine != null && character.Submarine != target.Submarine)
1670  {
1671  transformedTorsoPos += character.Submarine.SimPosition;
1672  transformedTorsoPos -= target.Submarine.SimPosition;
1673  }
1674 
1675  targetTorso.PullJointEnabled = true;
1676  targetTorso.PullJointWorldAnchorB = transformedTorsoPos + (Vector2.UnitX * -Dir) * 0.2f;
1677  targetTorso.PullJointMaxForce = 5000.0f;
1678 
1679  if (!targetLeftHand.IsSevered)
1680  {
1681  targetLeftHand.PullJointEnabled = true;
1682  targetLeftHand.PullJointWorldAnchorB = transformedTorsoPos + (new Vector2(1 * Dir, 1)) * 0.2f;
1683  targetLeftHand.PullJointMaxForce = 5000.0f;
1684  }
1685  if (!targetRightHand.IsSevered)
1686  {
1687  targetRightHand.PullJointEnabled = true;
1688  targetRightHand.PullJointWorldAnchorB = transformedTorsoPos + (new Vector2(1 * Dir, 1)) * 0.2f;
1689  targetRightHand.PullJointMaxForce = 5000.0f;
1690  }
1691 
1692  if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
1693  {
1694  Collider.ResetDynamics();
1695  }
1696 
1697  target.AnimController.IgnorePlatforms = true;
1698  }
1699  else
1700  {
1701  //only grab with one hand when swimming
1702  leftHand.Disabled = true;
1703  if (!inWater)
1704  {
1705  rightHand.Disabled = true;
1706  }
1707 
1708  for (int i = 0; i < 2; i++)
1709  {
1710  Limb targetLimb = target.AnimController.GetLimb(LimbType.Torso);
1711  if (i == 0)
1712  {
1713  if (!targetLeftHand.IsSevered)
1714  {
1715  targetLimb = targetLeftHand;
1716  }
1717  else if (!targetRightHand.IsSevered)
1718  {
1719  targetLimb = targetRightHand;
1720  }
1721  }
1722  else
1723  {
1724  if (!targetRightHand.IsSevered)
1725  {
1726  targetLimb = targetRightHand;
1727  }
1728  else if (!targetLeftHand.IsSevered)
1729  {
1730  targetLimb = targetLeftHand;
1731  }
1732  }
1733 
1734  Limb pullLimb = i == 0 ? leftHand : rightHand;
1735 
1736  if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
1737  {
1738  //stop dragging if there's something between the pull limb and the target limb
1739  Vector2 sourceSimPos = pullLimb.SimPosition;
1740  Vector2 targetSimPos = targetLimb.SimPosition;
1741  if (character.Submarine != null && character.SelectedCharacter.Submarine == null)
1742  {
1743  targetSimPos -= character.Submarine.SimPosition;
1744  }
1745  else if (character.Submarine == null && character.SelectedCharacter.Submarine != null)
1746  {
1747  sourceSimPos -= character.SelectedCharacter.Submarine.SimPosition;
1748  }
1749  else if (character.Submarine != null && character.SelectedCharacter.Submarine != null && character.Submarine != character.SelectedCharacter.Submarine)
1750  {
1751  targetSimPos += character.SelectedCharacter.Submarine.SimPosition;
1752  targetSimPos -= character.Submarine.SimPosition;
1753  }
1754  var body = Submarine.CheckVisibility(sourceSimPos, targetSimPos, ignoreSubs: true);
1755  if (body != null)
1756  {
1757  character.DeselectCharacter();
1758  return;
1759  }
1760  }
1761 
1762  //only pull with one hand when swimming
1763  if (i > 0 && inWater) { continue; }
1764 
1765  Vector2 diff = ConvertUnits.ToSimUnits(targetLimb.WorldPosition - pullLimb.WorldPosition);
1766 
1767  Vector2 targetAnchor;
1768  float targetForce;
1769  pullLimb.PullJointEnabled = true;
1770  if (targetLimb.type == LimbType.Torso || targetLimb == target.AnimController.MainLimb)
1771  {
1772  pullLimb.PullJointMaxForce = 5000.0f;
1773  if (!character.CanRunWhileDragging())
1774  {
1775  targetMovement *= MathHelper.Clamp(Mass / target.Mass, 0.5f, 1.0f);
1776  }
1777 
1778  Vector2 shoulderPos = rightShoulder.WorldAnchorA;
1779  float targetDist = Vector2.Distance(targetLimb.SimPosition, shoulderPos);
1780  Vector2 dragDir = (targetLimb.SimPosition - shoulderPos) / targetDist;
1781  if (!MathUtils.IsValid(dragDir)) { dragDir = -Vector2.UnitY; }
1782  if (!InWater)
1783  {
1784  //lerp the arm downwards when not swimming
1785  dragDir = Vector2.Lerp(dragDir, -Vector2.UnitY, 0.5f);
1786  }
1787 
1788  Vector2 pullLimbAnchor = shoulderPos + dragDir * Math.Min(targetDist, (upperArmLength + forearmLength) * 2);
1789  targetAnchor = shoulderPos + dragDir * (upperArmLength + forearmLength);
1790  targetForce = 200.0f;
1791  if (target.Submarine != character.Submarine)
1792  {
1793  if (character.Submarine == null)
1794  {
1795  pullLimbAnchor += target.Submarine.SimPosition;
1796  targetAnchor -= target.Submarine.SimPosition;
1797  }
1798  else if (target.Submarine == null)
1799  {
1800  pullLimbAnchor -= character.Submarine.SimPosition;
1801  targetAnchor += character.Submarine.SimPosition;
1802  }
1803  else
1804  {
1805  pullLimbAnchor -= target.Submarine.SimPosition;
1806  pullLimbAnchor += character.Submarine.SimPosition;
1807  targetAnchor -= character.Submarine.SimPosition;
1808  targetAnchor += target.Submarine.SimPosition;
1809  }
1810  }
1811  if (Vector2.DistanceSquared(pullLimb.PullJointWorldAnchorA, pullLimbAnchor) > 50.0f * 50.0f)
1812  {
1813  //there's a similar error check in the PullJointWorldAnchorB setter, but we seem to be getting quite a lot of
1814  //errors specifically from this method, so let's use a more consistent error message here to prevent clogging GA with
1815  //different error messages that all include a different coordinate
1816  string errorMsg =
1817  $"Attempted to move the anchor B of a limb's pull joint extremely far from the limb in {nameof(DragCharacter)}. " +
1818  $"Character in sub: {character.Submarine != null}, target in sub: {target.Submarine != null}.";
1819  GameAnalyticsManager.AddErrorEventOnce("DragCharacter:PullJointTooFar", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
1820 #if DEBUG
1821  DebugConsole.ThrowError(errorMsg);
1822 #endif
1823  }
1824  else
1825  {
1826  pullLimb.PullJointWorldAnchorB = pullLimbAnchor;
1827  }
1828  }
1829  else
1830  {
1831  pullLimb.PullJointWorldAnchorB = pullLimb.SimPosition + diff;
1832  pullLimb.PullJointMaxForce = 5000.0f;
1833  targetAnchor = targetLimb.SimPosition - diff;
1834  targetForce = 5000.0f;
1835  }
1836 
1837  if (!targetPoseControlled)
1838  {
1839  targetLimb.PullJointEnabled = true;
1840  targetLimb.PullJointMaxForce = targetForce;
1841  targetLimb.PullJointWorldAnchorB = targetAnchor;
1842  targetLimb.Disabled = true;
1843  target.AnimController.movement = -diff;
1844  }
1845  }
1846 
1847  float dist = ConvertUnits.ToSimUnits(Vector2.Distance(target.WorldPosition, WorldPosition));
1848  //let the target break free if it's moving away and gets far enough
1849  if ((GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) && dist > BreakFromGrabDistance && target.AllowInput &&
1850  Vector2.Dot(target.WorldPosition - WorldPosition, target.AnimController.TargetMovement) > 0)
1851  {
1852  character.DeselectCharacter();
1853  return;
1854  }
1855 
1856  //limit movement if moving away from the target
1857  if (!character.CanRunWhileDragging() && Vector2.Dot(target.WorldPosition - WorldPosition, targetMovement) < 0)
1858  {
1859  targetMovement *= MathHelper.Clamp(1.5f - dist, 0.0f, 1.0f);
1860  }
1861 
1862  if (!target.AllowInput)
1863  {
1864  target.AnimController.Stairs = Stairs;
1865  target.AnimController.IgnorePlatforms = IgnorePlatforms;
1866  target.AnimController.TargetMovement = TargetMovement;
1867  }
1868  else if (target is AICharacter && target != Character.Controlled && !targetPoseControlled)
1869  {
1870  if (target.AnimController.Dir > 0 == WorldPosition.X > target.WorldPosition.X)
1871  {
1872  target.AnimController.LockFlipping(0.5f);
1873  }
1874  else
1875  {
1876  target.AnimController.TargetDir = WorldPosition.X > target.WorldPosition.X ? Direction.Right : Direction.Left;
1877  }
1878  //make the target stand 0.5 meters away from this character, on the side they're currently at
1879  Vector2 movement = (character.SimPosition + Vector2.UnitX * 0.5f * Math.Sign(target.SimPosition.X - character.SimPosition.X)) - target.SimPosition;
1880  target.AnimController.TargetMovement = movement.LengthSquared() > 0.01f ? movement : Vector2.Zero;
1881  }
1882  }
1883  }
1884 
1885  private void RotateHead(Limb head)
1886  {
1887  Vector2 mousePos = ConvertUnits.ToSimUnits(character.CursorPosition);
1888  Vector2 dir = (mousePos - head.SimPosition) * Dir;
1889  float rot = MathUtils.VectorToAngle(dir);
1890  var neckJoint = GetJointBetweenLimbs(LimbType.Head, LimbType.Torso);
1891  if (neckJoint != null)
1892  {
1893  float offset = MathUtils.WrapAnglePi(GetLimb(LimbType.Torso).body.Rotation);
1894  float lowerLimit = neckJoint.LowerLimit + offset;
1895  float upperLimit = neckJoint.UpperLimit + offset;
1896  float min = Math.Min(lowerLimit, upperLimit);
1897  float max = Math.Max(lowerLimit, upperLimit);
1898  rot = Math.Clamp(rot, min, max);
1899  }
1900  head.body.SmoothRotate(rot, CurrentAnimationParams.HeadTorque);
1901  }
1902 
1903  private void FootIK(Limb foot, Vector2 pos, float legTorque, float footTorque, float footAngle)
1904  {
1905  if (!MathUtils.IsValid(pos))
1906  {
1907  string errorMsg = "Invalid foot position in FootIK (" + pos + ")\n" + Environment.StackTrace.CleanupStackTrace();
1908 #if DEBUG
1909  DebugConsole.ThrowError(errorMsg);
1910 #endif
1911  GameAnalyticsManager.AddErrorEventOnce("FootIK:InvalidPos", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
1912  return;
1913  }
1914 
1915  Limb upperLeg, lowerLeg;
1916  if (foot.type == LimbType.LeftFoot)
1917  {
1918  upperLeg = GetLimb(LimbType.LeftThigh);
1919  lowerLeg = GetLimb(LimbType.LeftLeg);
1920  }
1921  else
1922  {
1923  upperLeg = GetLimb(LimbType.RightThigh);
1924  lowerLeg = GetLimb(LimbType.RightLeg);
1925  }
1926  Limb torso = GetLimb(LimbType.Torso);
1927  LimbJoint waistJoint = GetJointBetweenLimbs(LimbType.Waist, upperLeg.type) ?? GetJointBetweenLimbs(LimbType.Torso, upperLeg.type);
1928  Vector2 waistPos = Vector2.Zero;
1929  if (waistJoint != null)
1930  {
1931  waistPos = waistJoint.LimbA == upperLeg ? waistJoint.WorldAnchorA : waistJoint.WorldAnchorB;
1932  }
1933 
1934  //distance from waist joint to the target position
1935  float c = Vector2.Distance(pos, waistPos);
1936  c = Math.Max(c, Math.Abs(upperLegLength - lowerLegLength));
1937 
1938  float legAngle = MathUtils.VectorToAngle(pos - waistPos) + MathHelper.PiOver2;
1939  if (!MathUtils.IsValid(legAngle))
1940  {
1941  string errorMsg = "Invalid leg angle (" + legAngle + ") in FootIK. Waist pos: " + waistPos + ", target pos: " + pos + "\n" + Environment.StackTrace.CleanupStackTrace();
1942 #if DEBUG
1943  DebugConsole.ThrowError(errorMsg);
1944 #endif
1945  GameAnalyticsManager.AddErrorEventOnce("FootIK:InvalidAngle", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
1946  return;
1947  }
1948 
1949  //make sure the angle "has the same number of revolutions" as the torso
1950  //(e.g. we don't want to rotate the legs to 0 if the torso is at 360, because that'd blow up the hip joints)
1951  while (torso.Rotation - legAngle > MathHelper.Pi)
1952  {
1953  legAngle += MathHelper.TwoPi;
1954  }
1955  while (torso.Rotation - legAngle < -MathHelper.Pi)
1956  {
1957  legAngle -= MathHelper.TwoPi;
1958  }
1959 
1960  //if the distance is longer than the length of the upper and lower leg, we'll just have to extend them directly towards the target
1961  float upperLegAngle = c >= upperLegLength + lowerLegLength ? 0.0f : MathUtils.SolveTriangleSSS(lowerLegLength, upperLegLength, c);
1962  float lowerLegAngle = c >= upperLegLength + lowerLegLength ? 0.0f : MathUtils.SolveTriangleSSS(upperLegLength, lowerLegLength, c);
1963 
1964  upperLeg.body.SmoothRotate((legAngle + upperLegAngle * Dir), upperLeg.Mass * legTorque, wrapAngle: false);
1965  lowerLeg.body.SmoothRotate((legAngle - lowerLegAngle * Dir), lowerLeg.Mass * legTorque, wrapAngle: false);
1966  foot.body.SmoothRotate((legAngle - (lowerLegAngle + footAngle) * Dir), foot.Mass * footTorque, wrapAngle: false);
1967  }
1968 
1969  public override void Flip()
1970  {
1971  if (Character == null || Character.Removed)
1972  {
1973  LogAccessedRemovedCharacterError();
1974  return;
1975  }
1976 
1977  base.Flip();
1978 
1979  WalkPos = -WalkPos;
1980 
1981  Limb torso = GetLimb(LimbType.Torso);
1982  if (torso == null) { return; }
1983 
1984  Matrix torsoTransform = Matrix.CreateRotationZ(torso.Rotation);
1985 
1986  Vector2 difference;
1987  foreach (Item heldItem in character.HeldItems)
1988  {
1989  if (heldItem?.body != null && !heldItem.Removed && heldItem.GetComponent<Holdable>() != null)
1990  {
1991  heldItem.FlipX(relativeToSub: false);
1992  }
1993  }
1994 
1995  foreach (Limb limb in Limbs)
1996  {
1997  if (limb.IsSevered) { continue; }
1998 
1999  bool mirror = false;
2000  bool flipAngle = false;
2001  bool wrapAngle = false;
2002 
2003  switch (limb.type)
2004  {
2005  case LimbType.LeftHand:
2006  case LimbType.LeftArm:
2007  case LimbType.LeftForearm:
2008  case LimbType.RightHand:
2009  case LimbType.RightArm:
2010  case LimbType.RightForearm:
2011  flipAngle = true;
2012  break;
2013  case LimbType.LeftThigh:
2014  case LimbType.LeftLeg:
2015  case LimbType.LeftFoot:
2016  case LimbType.RightThigh:
2017  case LimbType.RightLeg:
2018  case LimbType.RightFoot:
2019  mirror = Crouching && !inWater;
2020  flipAngle = (limb.DoesFlip || Crouching) && !inWater;
2021  wrapAngle = !inWater;
2022  break;
2023  default:
2024  flipAngle = limb.DoesFlip && !inWater;
2025  wrapAngle = !inWater;
2026  break;
2027  }
2028 
2029  Vector2 position = limb.SimPosition;
2030 
2031  if (!limb.PullJointEnabled && mirror)
2032  {
2033  difference = limb.body.SimPosition - torso.SimPosition;
2034  difference = Vector2.Transform(difference, torsoTransform);
2035  difference.Y = -difference.Y;
2036 
2037  position = torso.SimPosition + Vector2.Transform(difference, -torsoTransform);
2038 
2039  //TrySetLimbPosition(limb, limb.SimPosition, );
2040  }
2041 
2042  float angle = flipAngle ? -limb.body.Rotation : limb.body.Rotation;
2043  if (wrapAngle) { angle = MathUtils.WrapAnglePi(angle); }
2044 
2045  TrySetLimbPosition(limb, Collider.SimPosition, position, angle);
2046  }
2047  }
2048 
2049  public override float GetSpeed(AnimationType type)
2050  {
2051  if (type == AnimationType.Crouch)
2052  {
2053  if (!CanWalk)
2054  {
2055  DebugConsole.ThrowError($"{character.SpeciesName} cannot crouch!");
2056  return 0;
2057  }
2059  }
2060  return base.GetSpeed(type);
2061  }
2062  }
2063 }
virtual float Strength
Definition: Affliction.cs:31
void HandIK(Limb hand, Vector2 pos, float armTorque=1.0f, float handTorque=1.0f, float maxAngularVelocity=float.PositiveInfinity)
AnimationType ForceSelectAnimationType
override? float HeadPosition
virtual ? Vector2 StepSize
AnimationParams? CurrentAnimationParams
override? float TorsoAngle
LimbJoint GetJointBetweenLimbs(LimbType limbTypeA, LimbType limbTypeB)
override? float TorsoPosition
void UpdateUseItem(bool allowMovement, Vector2 handWorldPos)
void LockFlipping(float time=0.2f)
void Grab(Vector2 rightHandPos, Vector2 leftHandPos)
float GetAfflictionStrength(Identifier afflictionType, Limb limb, bool requireLimbSpecific)
Get the total strength of the afflictions of a specific type attached to a specific limb
float GetAfflictionStrengthByType(Identifier afflictionType, bool allowLimbAfflictions=true)
Affliction GetAffliction(string identifier, bool allowLimbAfflictions=true)
float GetSkillLevel(string skillIdentifier)
bool IsRemotelyControlled
Is the character controlled remotely (either by another player, or a server-side AIController)
void SetStun(float newStun, bool allowStunDecrease=false, bool isNetworkMessage=false)
Item????????? SelectedItem
The primary selected item. It can be any device that character interacts with. This excludes items li...
float GetStatValue(StatTypes statType, bool includeSaved=true)
float SpeedMultiplier
Can be used to modify the character's speed via StatusEffects
float GetLeftHandPenalty()
float GetRightHandPenalty()
Item SelectedSecondaryItem
The secondary selected item. It's an item other than a device (see SelectedItem), e....
float GetAttributeFloat(string key, float def)
virtual ContentXElement? MainElement
virtual Vector2 WorldPosition
Definition: Entity.cs:49
Submarine Submarine
Definition: Entity.cs:53
static NetworkMember NetworkMember
Definition: GameMain.cs:190
static HumanCrouchParams GetDefaultAnimParams(Character character)
static HumanRagdollParams GetDefaultRagdollParams(Character character)
static HumanRunParams GetDefaultAnimParams(Character character)
static HumanSwimFastParams GetDefaultAnimParams(Character character)
static HumanSwimSlowParams GetDefaultAnimParams(Character character)
static HumanWalkParams GetDefaultAnimParams(Character character)
HumanoidAnimController(Character character, string seed, HumanRagdollParams ragdollParams=null)
override void Recreate(RagdollParams ragdollParams=null)
Call this to create the ragdoll from the RagdollParams.
override void DragCharacter(Character target, float deltaTime)
override GroundedMovementParams RunParams
override GroundedMovementParams WalkParams
override void UpdateAnim(float deltaTime)
override float GetSpeed(AnimationType type)
new HumanGroundedParams CurrentGroundedParams
override void FlipX(bool relativeToSub)
Flip the entity horizontally
bool IsInteractable(Character character)
Returns interactibility based on whether the character is on a player team
void ApplyForce(Vector2 force, float maxVelocity=NetConfig.MaxPhysicsBodyVelocity)
bool SetTransform(Vector2 simPosition, float rotation, bool setPrevTransform=true)
void MoveToPos(Vector2 simPosition, float force, Vector2? pullPos=null)
bool SetTransformIgnoreContacts(Vector2 simPosition, float rotation, bool setPrevTransform=true)
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...
void ResetPullJoints(Func< Limb, bool > condition=null)
bool IsHangingWithRope
Is hanging to something with a rope, so that can reel towards it. Currently only possible in water.
Limb GetLimb(LimbType limbType, bool excludeSevered=true)
Note that if there are multiple limbs of the same type, only the first (valid) limb is returned.
float GetSurfaceY()
Get the position of the surface of water at the position of the character, in display units (taking i...
float ColliderHeightFromFloor
In sim units. Joint scale applied.
void MoveLimb(Limb limb, Vector2 pos, float amount, bool pullFromCenter=false)
if false, force is applied to the position of pullJoint
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).
@ InWater
Executes continuously when the entity is submerged. Valid for items and characters.
StatTypes
StatTypes are used to alter several traits of a character. They are mostly used by talents.
Definition: Enums.cs:180