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