Client LuaCsForBarotrauma
AnimController.cs
2 using FarseerPhysics;
3 using Microsoft.Xna.Framework;
4 using System;
5 using System.Collections.Generic;
6 using System.Linq;
8 
9 namespace Barotrauma
10 {
11  abstract class AnimController : Ragdoll
12  {
13  public Vector2 RightHandIKPos { get; protected set; }
14  public Vector2 LeftHandIKPos { get; protected set; }
15 
16  protected LimbJoint rightShoulder, leftShoulder;
17  protected float upperArmLength, forearmLength;
18  protected float useItemTimer;
19  protected bool aiming;
20  protected bool wasAiming;
21  protected bool aimingMelee;
22  protected bool wasAimingMelee;
23 
24  public bool IsAiming => wasAiming;
25  public bool IsAimingMelee => wasAimingMelee;
26 
27  protected bool Aiming => aiming || aimingMelee || FlipLockTime > Timing.TotalTime && character.IsKeyDown(InputType.Aim);
28 
29  public float ArmLength => upperArmLength + forearmLength;
30 
31  public abstract GroundedMovementParams WalkParams { get; set; }
32  public abstract GroundedMovementParams RunParams { get; set; }
33  public abstract SwimParams SwimSlowParams { get; set; }
34  public abstract SwimParams SwimFastParams { get; set; }
35 
36  protected class AnimSwap
37  {
38  public readonly AnimationType AnimationType;
40  public readonly float Priority;
41  public bool IsActive;
42 
43  public AnimSwap(AnimationParams temporaryAnimation, float priority)
44  {
45  AnimationType = temporaryAnimation.AnimationType;
46  TemporaryAnimation = temporaryAnimation;
47  Priority = priority;
48  IsActive = true;
49  }
50  }
51 
52  protected readonly Dictionary<AnimationType, AnimSwap> tempAnimations = new Dictionary<AnimationType, AnimSwap>();
53  protected readonly HashSet<AnimationType> expiredAnimations = new HashSet<AnimationType>();
54 
56  {
57  get
58  {
59  if (ForceSelectAnimationType == AnimationType.NotDefined)
60  {
62  }
63  else
64  {
66  }
67  }
68  }
71  {
72  get
73  {
74  if (ForceSelectAnimationType != AnimationType.NotDefined)
75  {
77  }
78  if (!CanWalk)
79  {
80  //DebugConsole.ThrowError($"{character.SpeciesName} cannot walk!");
81  return null;
82  }
83  else
84  {
85  if (this is HumanoidAnimController humanAnimController && humanAnimController.Crouching)
86  {
87  return humanAnimController.HumanCrouchParams;
88  }
90  }
91  }
92  }
94  {
95  get
96  {
97  if (ForceSelectAnimationType != AnimationType.NotDefined)
98  {
100  }
101  else
102  {
104  }
105  }
106  }
107 
108  public bool CanWalk => RagdollParams.CanWalk;
109  public bool IsMovingBackwards =>
110  !InWater &&
111  Math.Sign(targetMovement.X) == -Math.Sign(Dir) &&
112  CurrentAnimationParams is not FishGroundedParams { Flip: false } &&
113  Anim != Animation.Climbing;
114 
115  // TODO: define death anim duration in XML
116  protected float deathAnimTimer, deathAnimDuration = 5.0f;
117 
121  public bool IsMovingFast
122  {
123  get
124  {
125  if (InWater || !CanWalk)
126  {
127  return TargetMovement.LengthSquared() > MathUtils.Pow2(SwimSlowParams.MovementSpeed + 0.0001f);
128  }
129  else
130  {
131  float movementSpeed = IsClimbing ? TargetMovement.Y : TargetMovement.X;
132  return Math.Abs(movementSpeed) > (WalkParams.MovementSpeed + RunParams.MovementSpeed) / 2.0f;
133  }
134  }
135  }
136 
141  public List<AnimationParams> AllAnimParams
142  {
143  get
144  {
145  if (CanWalk)
146  {
147  var anims = new List<AnimationParams> { WalkParams, RunParams, SwimSlowParams, SwimFastParams };
148  if (this is HumanoidAnimController humanAnimController)
149  {
150  anims.Add(humanAnimController.HumanCrouchParams);
151  }
152  return anims;
153  }
154  else
155  {
156  return new List<AnimationParams> { SwimSlowParams, SwimFastParams };
157  }
158  }
159  }
160 
161  public enum Animation { None, Climbing, UsingItem, Struggle, CPR, UsingItemWhileClimbing };
162  public Animation Anim;
163 
164  public bool IsUsingItem => Anim == Animation.UsingItem || Anim == Animation.UsingItemWhileClimbing;
165  public bool IsClimbing => Anim == Animation.Climbing || Anim == Animation.UsingItemWhileClimbing;
166 
167  public Vector2 AimSourceWorldPos
168  {
169  get
170  {
171  Vector2 sourcePos = character.AnimController.AimSourcePos;
172  if (character.Submarine != null) { sourcePos += character.Submarine.Position; }
173  return sourcePos;
174  }
175  }
176 
177  public Vector2 AimSourcePos => ConvertUnits.ToDisplayUnits(AimSourceSimPos);
178  public virtual Vector2 AimSourceSimPos => Collider.SimPosition;
179 
180  protected float? GetValidOrNull(AnimationParams p, float? v)
181  {
182  if (p == null) { return null; }
183  if (v == null) { return null; }
184  if (!MathUtils.IsValid(v.Value)) { return null; }
185  return v.Value;
186  }
187  protected Vector2? GetValidOrNull(AnimationParams p, Vector2 v)
188  {
189  if (p == null) { return null; }
190  return v;
191  }
192 
195  public override float? HeadAngle => GetValidOrNull(CurrentAnimationParams, CurrentAnimationParams?.HeadAngleInRadians);
196  public override float? TorsoAngle => GetValidOrNull(CurrentAnimationParams, CurrentAnimationParams?.TorsoAngleInRadians);
198 
199  public bool AnimationTestPose { get; set; }
200 
201  public float WalkPos { get; protected set; }
202 
203  public AnimController(Character character, string seed, RagdollParams ragdollParams = null) : base(character, seed, ragdollParams) { }
204 
205  public void UpdateAnimations(float deltaTime)
206  {
207  UpdateTemporaryAnimations();
208  UpdateAnim(deltaTime);
209  }
210 
211  protected abstract void UpdateAnim(float deltaTime);
212 
213  public abstract void DragCharacter(Character target, float deltaTime);
214 
215  public virtual float GetSpeed(AnimationType type)
216  {
217  GroundedMovementParams movementParams;
218  switch (type)
219  {
220  case AnimationType.Walk:
221  if (!CanWalk)
222  {
223  DebugConsole.ThrowError($"{character.SpeciesName} cannot walk!");
224  return 0;
225  }
226  movementParams = WalkParams;
227  break;
228  case AnimationType.Run:
229  if (!CanWalk)
230  {
231  DebugConsole.ThrowError($"{character.SpeciesName} cannot run!");
232  return 0;
233  }
234  movementParams = RunParams;
235  break;
236  case AnimationType.SwimSlow:
238  case AnimationType.SwimFast:
240  default:
241  throw new NotImplementedException(type.ToString());
242  }
243  return IsMovingBackwards ? movementParams.MovementSpeed * movementParams.BackwardsMovementMultiplier : movementParams.MovementSpeed;
244  }
245 
246  public float GetCurrentSpeed(bool useMaxSpeed)
247  {
248  AnimationType animType;
249  if (InWater || !CanWalk)
250  {
251  if (useMaxSpeed)
252  {
253  animType = AnimationType.SwimFast;
254  }
255  else
256  {
257  animType = AnimationType.SwimSlow;
258  }
259  }
260  else
261  {
262  if (useMaxSpeed)
263  {
264  animType = AnimationType.Run;
265  }
266  else
267  {
268  if (this is HumanoidAnimController humanAnimController && humanAnimController.Crouching)
269  {
270  animType = AnimationType.Crouch;
271  }
272  else
273  {
274  animType = AnimationType.Walk;
275  }
276  }
277  }
278  return GetSpeed(animType);
279  }
280 
282  {
283  switch (type)
284  {
285  case AnimationType.Walk:
286  return CanWalk ? WalkParams : null;
287  case AnimationType.Run:
288  return CanWalk ? RunParams : null;
289  case AnimationType.Crouch:
290  if (this is HumanoidAnimController humanAnimController)
291  {
292  return humanAnimController.HumanCrouchParams;
293  }
294  else
295  {
296  DebugConsole.ThrowError($"Animation params of type {type} not implemented for non-humanoids!");
297  return null;
298  }
299  case AnimationType.SwimSlow:
300  return SwimSlowParams;
301  case AnimationType.SwimFast:
302  return SwimFastParams;
303  case AnimationType.NotDefined:
304  default:
305  return null;
306  }
307  }
308 
309  public float GetHeightFromFloor() => GetColliderBottom().Y - FloorY;
310 
311  // We need some margin, because if a hatch has closed, it's possible that the height from floor is slightly negative.
312  public bool IsAboveFloor => GetHeightFromFloor() > -0.1f;
313 
314  public float FlipLockTime { get; private set; }
315  public void LockFlipping(float time = 0.2f)
316  {
317  FlipLockTime = (float)Timing.TotalTime + time;
318  }
319 
320  protected void UpdateConstantTorque(float deltaTime)
321  {
322  foreach (var limb in Limbs)
323  {
324  if (limb.IsSevered) { continue; }
325  if (Math.Abs(limb.Params.ConstantTorque) > 0)
326  {
327  // TODO: not sure if this works on ground
328  float movementFactor = Math.Max(character.AnimController.Collider.LinearVelocity.Length() * 0.5f, 1);
329  limb.body.SmoothRotate(MainLimb.Rotation + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Mass * limb.Params.ConstantTorque * movementFactor, wrapAngle: true);
330  }
331  }
332  }
333 
334  protected void UpdateBlink(float deltaTime)
335  {
336  foreach (var limb in Limbs)
337  {
338  if (limb.IsSevered) { continue; }
339  if (limb.Params.BlinkFrequency <= 0) { continue; }
340  if (!limb.InWater && limb.Params.OnlyBlinkInWater) { continue; }
341  limb.UpdateBlink(deltaTime, MainLimb.Rotation);
342  }
343  }
344 
345  public void UpdateUseItem(bool allowMovement, Vector2 handWorldPos)
346  {
347  useItemTimer = 0.05f;
348  StartUsingItem();
349 
350  if (!allowMovement)
351  {
352  TargetMovement = Vector2.Zero;
353  TargetDir = handWorldPos.X > character.WorldPosition.X ? Direction.Right : Direction.Left;
354  if (InWater)
355  {
356  float sqrDist = Vector2.DistanceSquared(character.WorldPosition, handWorldPos);
357  if (sqrDist > MathUtils.Pow(ConvertUnits.ToDisplayUnits(upperArmLength + forearmLength), 2))
358  {
359  TargetMovement = GetTargetMovement(Vector2.Normalize(handWorldPos - character.WorldPosition));
360  }
361  }
362  else
363  {
364  float distX = Math.Abs(handWorldPos.X - character.WorldPosition.X);
365  if (distX > ConvertUnits.ToDisplayUnits(upperArmLength + forearmLength))
366  {
367  TargetMovement = GetTargetMovement(Vector2.UnitX * Math.Sign(handWorldPos.X - character.WorldPosition.X));
368  }
369  }
370  Vector2 GetTargetMovement(Vector2 dir)
371  {
372  return dir * GetCurrentSpeed(false) * Math.Max(character.SpeedMultiplier, 1);
373  }
374  }
375 
376  if (!character.Enabled) { return; }
377 
378  Vector2 handSimPos = ConvertUnits.ToSimUnits(handWorldPos);
379  if (character.Submarine != null)
380  {
381  handSimPos -= character.Submarine.SimPosition;
382  }
383 
384  Vector2 refPos = rightShoulder?.WorldAnchorA ?? leftShoulder?.WorldAnchorA ?? MainLimb.SimPosition;
385  Vector2 diff = handSimPos - refPos;
386  float dist = diff.Length();
387  float maxDist = ArmLength * 0.9f;
388  if (dist > maxDist)
389  {
390  handSimPos = refPos + diff / dist * maxDist;
391  }
392 
393  var leftHand = GetLimb(LimbType.LeftHand);
394  if (leftHand != null)
395  {
396  leftHand.Disabled = true;
397  leftHand.PullJointEnabled = true;
398  leftHand.PullJointWorldAnchorB = handSimPos;
399  }
400 
401  var rightHand = GetLimb(LimbType.RightHand);
402  if (rightHand != null)
403  {
404  rightHand.Disabled = true;
405  rightHand.PullJointEnabled = true;
406  rightHand.PullJointWorldAnchorB = handSimPos;
407  }
408 
409  //make the character crouch if using an item some distance below them (= on the floor)
410  if (!inWater &&
411  character.WorldPosition.Y - handWorldPos.Y > ConvertUnits.ToDisplayUnits(CurrentGroundedParams.TorsoPosition) / 4 &&
412  this is HumanoidAnimController humanoidAnimController)
413  {
414  humanoidAnimController.Crouch();
415  // TODO: is this redundant/required?
416  humanoidAnimController.ForceSelectAnimationType = AnimationType.Crouch;
417  }
418  }
419 
420  public void Grab(Vector2 rightHandPos, Vector2 leftHandPos)
421  {
422  for (int i = 0; i < 2; i++)
423  {
424  Limb pullLimb = (i == 0) ? GetLimb(LimbType.LeftHand) : GetLimb(LimbType.RightHand);
425 
426  pullLimb.Disabled = true;
427 
428  pullLimb.PullJointEnabled = true;
429  pullLimb.PullJointWorldAnchorB = (i == 0) ? rightHandPos : leftHandPos;
430  pullLimb.PullJointMaxForce = 500.0f;
431  }
432  }
433 
434  private Direction previousDirection;
435  private readonly Vector2[] transformedHandlePos = new Vector2[2];
436  //TODO: refactor this method, it's way too convoluted
437  public void HoldItem(float deltaTime, Item item, Vector2[] handlePos, Vector2 itemPos, bool aim, float holdAngle, float itemAngleRelativeToHoldAngle = 0.0f, bool aimMelee = false, Vector2? targetPos = null)
438  {
439  aimingMelee = aimMelee;
440  if (character.Stun > 0.0f || character.IsIncapacitated)
441  {
442  aim = false;
443  }
444 
445  //calculate the handle positions
446  Matrix itemTransform = Matrix.CreateRotationZ(item.body.Rotation);
447  transformedHandlePos[0] = Vector2.Transform(handlePos[0], itemTransform);
448  transformedHandlePos[1] = Vector2.Transform(handlePos[1], itemTransform);
449 
450  Limb torso = GetLimb(LimbType.Torso) ?? MainLimb;
451  Limb leftHand = GetLimb(LimbType.LeftHand);
452  Limb rightHand = GetLimb(LimbType.RightHand);
453 
454  var controller = character.SelectedItem?.GetComponent<Controller>();
455  bool usingController = controller is { AllowAiming: false };
456  if (!usingController)
457  {
458  controller = character.SelectedSecondaryItem?.GetComponent<Controller>();
459  usingController = controller is { AllowAiming: false };
460  }
461  bool isClimbing = character.IsClimbing && Math.Abs(character.AnimController.TargetMovement.Y) > 0.01f;
462  float itemAngle;
463  Holdable holdable = item.GetComponent<Holdable>();
464  float torsoRotation = torso.Rotation;
465 
466  Item rightHandItem = character.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand);
467  bool equippedInRightHand = rightHandItem == item && rightHand is { IsSevered: false };
468  Item leftHandItem = character.Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand);
469  bool equippedInLeftHand = leftHandItem == item && leftHand is { IsSevered: false };
470  if (aim && !isClimbing && !usingController && character.Stun <= 0.0f && itemPos != Vector2.Zero && !character.IsIncapacitated)
471  {
472  targetPos ??= ConvertUnits.ToSimUnits(character.SmoothedCursorPosition);
473 
474  Vector2 diff = holdable.Aimable ?
475  (targetPos.Value - AimSourceSimPos) * Dir :
476  MathUtils.RotatePoint(Vector2.UnitX, torsoRotation);
477 
478  holdAngle = MathUtils.VectorToAngle(new Vector2(diff.X, diff.Y * Dir)) - torsoRotation * Dir;
479  holdAngle += GetAimWobble(rightHand, leftHand, item);
480  itemAngle = torsoRotation + holdAngle * Dir;
481 
482  if (holdable.ControlPose)
483  {
484  //if holding two items that should control the characters' pose, let the item in the right hand do it
485  bool anotherItemControlsPose = equippedInLeftHand && rightHandItem != item && (rightHandItem?.GetComponent<Holdable>()?.ControlPose ?? false);
486  if (!anotherItemControlsPose && TargetMovement == Vector2.Zero && inWater)
487  {
488  torso.body.AngularVelocity -= torso.body.AngularVelocity * 0.1f;
489  torso.body.ApplyForce(torso.body.LinearVelocity * -0.5f);
490  }
491  aiming = true;
492  }
493  }
494  else
495  {
496  if (holdable.UseHandRotationForHoldAngle)
497  {
498  if (equippedInRightHand)
499  {
500  itemAngle = rightHand.Rotation + holdAngle * Dir;
501  }
502  else if (equippedInLeftHand)
503  {
504  itemAngle = leftHand.Rotation + holdAngle * Dir;
505  }
506  else
507  {
508  itemAngle = torsoRotation + holdAngle * Dir;
509  }
510  }
511  else
512  {
513  itemAngle = torsoRotation + holdAngle * Dir;
514  }
515  }
516 
517  if (rightShoulder == null) { return; }
518  Vector2 transformedHoldPos = rightShoulder.WorldAnchorA;
519  if (itemPos == Vector2.Zero || isClimbing || usingController)
520  {
521  if (equippedInRightHand)
522  {
523  transformedHoldPos = rightHand.PullJointWorldAnchorA - transformedHandlePos[0];
524  itemAngle = rightHand.Rotation + (holdAngle - rightHand.Params.GetSpriteOrientation() + MathHelper.PiOver2) * Dir;
525  }
526  else if (equippedInLeftHand)
527  {
528  transformedHoldPos = leftHand.PullJointWorldAnchorA - transformedHandlePos[1];
529  itemAngle = leftHand.Rotation + (holdAngle - leftHand.Params.GetSpriteOrientation() + MathHelper.PiOver2) * Dir;
530  }
531  }
532  else
533  {
534  if (equippedInRightHand)
535  {
536  transformedHoldPos = rightShoulder.WorldAnchorA;
537  rightHand.Disabled = true;
538  }
539  if (equippedInLeftHand)
540  {
541  if (leftShoulder == null) { return; }
542  transformedHoldPos = leftShoulder.WorldAnchorA;
543  leftHand.Disabled = true;
544  }
545  itemPos.X *= Dir;
546  transformedHoldPos += Vector2.Transform(itemPos, Matrix.CreateRotationZ(itemAngle));
547  }
548 
549  item.body.ResetDynamics();
550 
551  Vector2 currItemPos = equippedInRightHand ?
552  rightHand.PullJointWorldAnchorA - transformedHandlePos[0] :
553  leftHand.PullJointWorldAnchorA - transformedHandlePos[1];
554 
555  if (!MathUtils.IsValid(currItemPos))
556  {
557  string errorMsg = "Attempted to move the item \"" + item + "\" to an invalid position in HumanidAnimController.HoldItem: " +
558  currItemPos + ", rightHandPos: " + rightHand.PullJointWorldAnchorA + ", leftHandPos: " + leftHand.PullJointWorldAnchorA +
559  ", handlePos[0]: " + handlePos[0] + ", handlePos[1]: " + handlePos[1] +
560  ", transformedHandlePos[0]: " + transformedHandlePos[0] + ", transformedHandlePos[1]:" + transformedHandlePos[1] +
561  ", item pos: " + item.SimPosition + ", itemAngle: " + itemAngle +
562  ", collider pos: " + character.SimPosition;
563  DebugConsole.Log(errorMsg);
564  GameAnalyticsManager.AddErrorEventOnce(
565  "HumanoidAnimController.HoldItem:InvalidPos:" + character.Name + item.Name,
566  GameAnalyticsManager.ErrorSeverity.Error,
567  errorMsg);
568 
569  return;
570  }
571 
572  float targetAngle = MathUtils.WrapAngleTwoPi(itemAngle + itemAngleRelativeToHoldAngle * Dir);
573  float currentRotation = MathUtils.WrapAngleTwoPi(item.body.Rotation);
574  float itemRotation = MathHelper.SmoothStep(currentRotation, targetAngle, deltaTime * 25);
575  if (previousDirection != dir || Math.Abs(targetAngle - currentRotation) > MathHelper.Pi)
576  {
577  itemRotation = targetAngle;
578  }
579  item.SetTransform(currItemPos, itemRotation, setPrevTransform: false);
580  previousDirection = dir;
581 
582  if (holdable.Pusher != null)
583  {
584  if (character.Stun > 0.0f || character.IsIncapacitated)
585  {
586  holdable.Pusher.Enabled = false;
587  }
588  else
589  {
590  if (!holdable.Pusher.Enabled)
591  {
592  holdable.Pusher.Enabled = true;
593  holdable.Pusher.ResetDynamics();
594  holdable.Pusher.SetTransform(currItemPos, itemAngle);
595  }
596  else
597  {
598  holdable.Pusher.TargetPosition = currItemPos;
599  holdable.Pusher.TargetRotation = itemRotation;
600  holdable.Pusher.MoveToTargetPosition(true);
601  }
602  }
603  }
604 
605  if (!isClimbing && !character.IsIncapacitated && itemPos != Vector2.Zero && (aim || !holdable.UseHandRotationForHoldAngle))
606  {
607  for (int i = 0; i < 2; i++)
608  {
609  if (!character.Inventory.IsInLimbSlot(item, i == 0 ? InvSlotType.RightHand : InvSlotType.LeftHand)) { continue; }
610 #if DEBUG
611  if (handlePos[i].LengthSquared() > ArmLength)
612  {
613  DebugConsole.AddWarning($"Aim position for the item {item.Name} may be incorrect (further than the length of the character's arm)",
614  item.Prefab.ContentPackage);
615  }
616 #endif
617  HandIK(
618  i == 0 ? rightHand : leftHand, transformedHoldPos + transformedHandlePos[i],
621  maxAngularVelocity: 15.0f);
622  }
623  }
624  }
625 
626  private float GetAimWobble(Limb rightHand, Limb leftHand, Item heldItem)
627  {
628  float wobbleStrength = 0.0f;
629  if (character.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand) == heldItem)
630  {
631  wobbleStrength += Character.CharacterHealth.GetLimbDamage(rightHand, afflictionType: AfflictionPrefab.DamageType);
632  }
633  if (character.Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand) == heldItem)
634  {
635  wobbleStrength += Character.CharacterHealth.GetLimbDamage(leftHand, afflictionType: AfflictionPrefab.DamageType);
636  }
637  if (wobbleStrength <= 0.1f) { return 0.0f; }
638  wobbleStrength = (float)Math.Min(wobbleStrength, 1.0f);
639 
640  float lowFreqNoise = PerlinNoise.GetPerlin((float)Timing.TotalTime / 320.0f, (float)Timing.TotalTime / 240.0f) - 0.5f;
641  float highFreqNoise = PerlinNoise.GetPerlin((float)Timing.TotalTime / 40.0f, (float)Timing.TotalTime / 50.0f) - 0.5f;
642 
643  return (lowFreqNoise * 1.0f + highFreqNoise * 0.1f) * wobbleStrength;
644  }
645 
646  public void HandIK(Limb hand, Vector2 pos, float armTorque = 1.0f, float handTorque = 1.0f, float maxAngularVelocity = float.PositiveInfinity)
647  {
648  Vector2 shoulderPos;
649 
650  Limb arm, forearm;
651  if (hand.type == LimbType.LeftHand)
652  {
653  if (leftShoulder == null) { return; }
654  shoulderPos = leftShoulder.WorldAnchorA;
655  arm = GetLimb(LimbType.LeftArm);
656  forearm = GetLimb(LimbType.LeftForearm);
657  LeftHandIKPos = pos;
658  }
659  else
660  {
661  if (rightShoulder == null) { return; }
662  shoulderPos = rightShoulder.WorldAnchorA;
663  arm = GetLimb(LimbType.RightArm);
664  forearm = GetLimb(LimbType.RightForearm);
665  RightHandIKPos = pos;
666  }
667  if (arm == null) { return; }
668 
669  //distance from shoulder to holdpos
670  float c = Vector2.Distance(pos, shoulderPos);
671  c = MathHelper.Clamp(c, Math.Abs(upperArmLength - forearmLength), forearmLength + upperArmLength - 0.01f);
672 
673  float armAngle = MathUtils.VectorToAngle(pos - shoulderPos) + arm.Params.GetSpriteOrientation() - MathHelper.PiOver2;
674  float upperArmAngle = MathUtils.SolveTriangleSSS(forearmLength, upperArmLength, c) * Dir;
675  float lowerArmAngle = MathUtils.SolveTriangleSSS(upperArmLength, forearmLength, c) * Dir;
676 
677  //make sure the arm angle "has the same number of revolutions" as the arm
678  while (arm.Rotation - armAngle > MathHelper.Pi)
679  {
680  armAngle += MathHelper.TwoPi;
681  }
682  while (arm.Rotation - armAngle < -MathHelper.Pi)
683  {
684  armAngle -= MathHelper.TwoPi;
685  }
686 
687  if (arm?.body != null && Math.Abs(arm.body.AngularVelocity) < maxAngularVelocity)
688  {
689  arm.body.SmoothRotate(armAngle - upperArmAngle, 100.0f * armTorque * arm.Mass, wrapAngle: false);
690  }
691  float forearmAngle = armAngle + lowerArmAngle;
692  if (forearm?.body != null && Math.Abs(forearm.body.AngularVelocity) < maxAngularVelocity)
693  {
694  forearm.body.SmoothRotate(forearmAngle, 100.0f * handTorque * forearm.Mass, wrapAngle: false);
695  }
696  if (hand?.body != null && Math.Abs(hand.body.AngularVelocity) < maxAngularVelocity)
697  {
698  float handAngle = forearm != null ? forearmAngle : armAngle;
699  hand.body.SmoothRotate(handAngle, 10.0f * handTorque * hand.Mass, wrapAngle: false);
700  }
701  }
702 
703  private float prevFootPos;
704  protected void UpdateClimbing()
705  {
706  var ladder = character.SelectedSecondaryItem?.GetComponent<Ladder>();
708  {
709  Anim = Animation.None;
710  return;
711  }
712  else if (ladder == null)
713  {
714  StopClimbing();
715  return;
716  }
717 
718  onGround = false;
719  IgnorePlatforms = true;
720 
722  var animParams = climbFast ? RunParams : WalkParams;
723  // Don't slide if we can climb faster than slide.
724  bool slide = animParams.SlideSpeed > animParams.ClimbSpeed && targetMovement.Y < -0.1f && climbFast;
725  float maxClimbingSpeed = climbFast && !character.Params.ForceSlowClimbing ? RunParams.ClimbSpeed : WalkParams.ClimbSpeed;
726  Vector2 tempTargetMovement = TargetMovement;
727  tempTargetMovement.Y = Math.Clamp(tempTargetMovement.Y, slide ? -animParams.SlideSpeed : -maxClimbingSpeed, maxClimbingSpeed);
728 
729  movement = MathUtils.SmoothStep(movement, tempTargetMovement, 0.3f);
730 
731  Limb leftFoot = GetClimbingLimb(LimbType.LeftFoot);
732  Limb rightFoot = GetClimbingLimb(LimbType.RightFoot);
733  Limb head = GetClimbingLimb(LimbType.Head);
734  Limb torso = GetClimbingLimb(LimbType.Torso);
735 
736  Limb leftHand = GetClimbingLimb(LimbType.LeftHand);
737  Limb rightHand = GetClimbingLimb(LimbType.RightHand);
738 
739  Vector2 ladderSimPos = ConvertUnits.ToSimUnits(
740  ladder.Item.Rect.X + ladder.Item.Rect.Width / 2.0f,
741  ladder.Item.Rect.Y);
742 
743  Vector2 ladderSimSize = ConvertUnits.ToSimUnits(ladder.Item.Rect.Size.ToVector2());
744 
745  var lowestNearbyLadder = GetLowestNearbyLadder(ladder);
746  if (lowestNearbyLadder != null && lowestNearbyLadder != ladder)
747  {
748  ladderSimSize.Y = ConvertUnits.ToSimUnits(ladder.Item.WorldRect.Y - (lowestNearbyLadder.Item.WorldRect.Y - lowestNearbyLadder.Item.Rect.Size.Y));
749  }
750 
751  float stepHeight = ConvertUnits.ToSimUnits(animParams.ClimbStepHeight);
752 
753  if (currentHull == null && ladder.Item.Submarine != null)
754  {
755  ladderSimPos += ladder.Item.Submarine.SimPosition;
756  }
757  else if (currentHull?.Submarine != null && currentHull.Submarine != ladder.Item.Submarine && ladder.Item.Submarine != null)
758  {
759  ladderSimPos += ladder.Item.Submarine.SimPosition - currentHull.Submarine.SimPosition;
760  }
761  else if (currentHull?.Submarine != null && ladder.Item.Submarine == null)
762  {
763  ladderSimPos -= currentHull.Submarine.SimPosition;
764  }
765 
766  float bottomPos = Collider.SimPosition.Y - ColliderHeightFromFloor - Collider.Radius - Collider.Height / 2.0f;
767  float torsoPos = TorsoPosition ?? 0;
768  float bodyMoveForce = animParams.ClimbBodyMoveForce;
769  if (torso != null)
770  {
771  MoveLimb(torso, new Vector2(ladderSimPos.X - 0.35f * Dir, bottomPos + torsoPos), bodyMoveForce);
772  }
773  if (head != null)
774  {
775  float headPos = HeadPosition ?? 0;
776  MoveLimb(head, new Vector2(ladderSimPos.X - 0.2f * Dir, bottomPos + headPos), bodyMoveForce);
777  }
778 
779  Collider.MoveToPos(new Vector2(ladderSimPos.X - 0.1f * Dir, Collider.SimPosition.Y), bodyMoveForce);
780 
781  Vector2 handPos = new Vector2(
782  ladderSimPos.X,
783  bottomPos + torsoPos + movement.Y * 0.1f - ladderSimPos.Y);
784  if (climbFast) { handPos.Y -= stepHeight; }
785 
786  float handMoveForce = animParams.ClimbHandMoveForce;
787 
788  //prevent the hands from going above the top of the ladders
789  handPos.Y = Math.Min(-0.5f, handPos.Y);
790  if (!Aiming || !(character.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand)?.GetComponent<Holdable>()?.ControlPose ?? false) || Math.Abs(movement.Y) > 0.01f)
791  {
792  if (rightHand != null)
793  {
794  MoveLimb(rightHand,
795  new Vector2(slide ? handPos.X + ladderSimSize.X * 0.75f : handPos.X,
796  (slide ? handPos.Y + stepHeight : MathUtils.Round(handPos.Y, stepHeight * 2.0f)) + ladderSimPos.Y),
797  handMoveForce);
798  rightHand.body.ApplyTorque(Dir * 2.0f);
799  }
800  }
801  if (!Aiming || !(character.Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand)?.GetComponent<Holdable>()?.ControlPose ?? false) || Math.Abs(movement.Y) > 0.01f)
802  {
803  if (leftHand != null)
804  {
805  MoveLimb(leftHand,
806  new Vector2(handPos.X - ladderSimSize.X * (slide ? 1.0f : 0.5f),
807  (slide ? handPos.Y + stepHeight : MathUtils.Round(handPos.Y - stepHeight, stepHeight * 2.0f) + stepHeight) + ladderSimPos.Y),
808  handMoveForce); ;
809  leftHand.body.ApplyTorque(Dir * 2.0f);
810  }
811  }
812 
813  float stepHeightAdjustment = stepHeight * 2.7f;
814  Vector2 footPos = new Vector2(
815  handPos.X - Dir * 0.05f,
816  bottomPos + ColliderHeightFromFloor - stepHeightAdjustment - ladderSimPos.Y);
817  if (climbFast) { footPos.Y += stepHeight; }
818 
819  //apply torque to the legs to make the knees bend
820  Limb leftLeg = GetClimbingLimb(LimbType.LeftLeg);
821  Limb rightLeg = GetClimbingLimb(LimbType.RightLeg);
822 
823  //only move the feet if they're above the bottom of the ladders
824  //(if not, they'll just dangle in air, and the character holds itself up with its arms)
825  if (footPos.Y > -ladderSimSize.Y - 0.2f && leftFoot != null && rightFoot != null && leftLeg != null && rightLeg != null)
826  {
827  Limb refLimb = GetClimbingLimb(LimbType.Waist) ?? GetClimbingLimb(LimbType.Torso) ?? MainLimb;
828  bool leftLegBackwards = Math.Abs(leftLeg.body.Rotation - refLimb.body.Rotation) > MathHelper.Pi;
829  bool rightLegBackwards = Math.Abs(rightLeg.body.Rotation - refLimb.body.Rotation) > MathHelper.Pi;
830  float footMoveForce = animParams.ClimbFootMoveForce;
831  if (slide)
832  {
833  if (!leftLegBackwards) { MoveLimb(leftFoot, new Vector2(footPos.X - ladderSimSize.X * 0.5f, footPos.Y + ladderSimPos.Y), footMoveForce, pullFromCenter: true); }
834  if (!rightLegBackwards) { MoveLimb(rightFoot, new Vector2(footPos.X, footPos.Y + ladderSimPos.Y), footMoveForce, pullFromCenter: true); }
835  }
836  else
837  {
838  float leftFootPos = MathUtils.Round(footPos.Y + stepHeight, stepHeight * 2.0f) - stepHeight;
839  float prevLeftFootPos = MathUtils.Round(prevFootPos + stepHeight, stepHeight * 2.0f) - stepHeight;
840  if (!leftLegBackwards) { MoveLimb(leftFoot, new Vector2(footPos.X, leftFootPos + ladderSimPos.Y), footMoveForce, pullFromCenter: true); }
841 
842  float rightFootPos = MathUtils.Round(footPos.Y, stepHeight * 2.0f);
843  float prevRightFootPos = MathUtils.Round(prevFootPos, stepHeight * 2.0f);
844  if (!rightLegBackwards) { MoveLimb(rightFoot, new Vector2(footPos.X, rightFootPos + ladderSimPos.Y), footMoveForce, pullFromCenter: true); }
845 #if CLIENT
846  if (Math.Abs(leftFootPos - prevLeftFootPos) > stepHeight && leftFoot.LastImpactSoundTime < Timing.TotalTime - Limb.SoundInterval)
847  {
848  SoundPlayer.PlaySound("footstep_armor_heavy", leftFoot.WorldPosition, hullGuess: currentHull);
849  leftFoot.LastImpactSoundTime = (float)Timing.TotalTime;
850  }
851  if (Math.Abs(rightFootPos - prevRightFootPos) > stepHeight && rightFoot.LastImpactSoundTime < Timing.TotalTime - Limb.SoundInterval)
852  {
853  SoundPlayer.PlaySound("footstep_armor_heavy", rightFoot.WorldPosition, hullGuess: currentHull);
854  rightFoot.LastImpactSoundTime = (float)Timing.TotalTime;
855  }
856 #endif
857  prevFootPos = footPos.Y;
858  }
859 
860  if (!leftLegBackwards) { leftLeg.body.ApplyTorque(Dir * -8.0f); } // TODO: expose?
861  if (!rightLegBackwards) { rightLeg.body.ApplyTorque(Dir * -8.0f); }
862  }
863 
864  float movementFactor = (handPos.Y / stepHeight) * (float)Math.PI;
865  movementFactor = 0.8f + (float)Math.Abs(Math.Sin(movementFactor));
866 
867  Vector2 subSpeed = currentHull != null || ladder.Item.Submarine == null
868  ? Vector2.Zero : ladder.Item.Submarine.Velocity;
869 
870  //reached the top of the ladders -> can't go further up
871  Vector2 climbForce = new Vector2(0.0f, movement.Y) * movementFactor;
872 
873  if (!InWater) { climbForce.Y += 0.3f * movementFactor; }
874 
875  if (character.SimPosition.Y > ladderSimPos.Y) { climbForce.Y = Math.Min(0.0f, climbForce.Y); }
876  //reached the bottom -> can't go further down
877  float minHeightFromFloor = ColliderHeightFromFloor / 2 + Collider.Height;
878  if (floorFixture != null &&
879  !floorFixture.CollisionCategories.HasFlag(Physics.CollisionStairs) &&
880  !floorFixture.CollisionCategories.HasFlag(Physics.CollisionPlatform) &&
881  character.SimPosition.Y < standOnFloorY + minHeightFromFloor)
882  {
883  climbForce.Y = MathHelper.Clamp((standOnFloorY + minHeightFromFloor - character.SimPosition.Y) * 5.0f, climbForce.Y, 1.0f);
884  }
885 
886  //apply forces to the collider to move the Character up/down
887  Collider.ApplyForce((climbForce * 20.0f + subSpeed * 50.0f) * Collider.Mass);
888  // Don't rotate the head on non-humanoids, because it can cause issues with some ragdolls.
889  // E.g. the head might not actually be head, or it's not where we expect it to be.
890  if (head != null && character.IsHumanoid)
891  {
892  if (Aiming)
893  {
894  RotateHead(head);
895  }
896  else if (Anim == Animation.UsingItemWhileClimbing && character.SelectedItem is { } selectedItem)
897  {
898  Vector2 diff = (selectedItem.WorldPosition - head.WorldPosition) * Dir;
899  float targetRotation = MathHelper.WrapAngle(MathUtils.VectorToAngle(diff) - MathHelper.PiOver4 * Dir);
900  head.body.SmoothRotate(targetRotation, force: animParams.HeadTorque);
901  }
902  else
903  {
904  float movementMultiplier = targetMovement.Y < 0 ? 0 : 1;
905  head.body.SmoothRotate(MathHelper.PiOver4 * movementMultiplier * Dir, force: animParams.HeadTorque);
906  }
907  }
908 
909  if (ladder.Item.Prefab.Triggers.None())
910  {
912  return;
913  }
914 
915  Rectangle trigger = ladder.Item.Prefab.Triggers.FirstOrDefault();
916  trigger = ladder.Item.TransformTrigger(trigger);
917 
918  bool isRemote = false;
919  bool isClimbing = true;
920  if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
921  {
922  isRemote = character.IsRemotelyControlled;
923  }
924  if (isRemote)
925  {
926  if (Math.Abs(targetMovement.X) > 0.05f ||
927  (TargetMovement.Y < 0.0f && ConvertUnits.ToSimUnits(trigger.Height) + handPos.Y < HeadPosition) ||
928  (TargetMovement.Y > 0.0f && handPos.Y > 0.1f))
929  {
930  isClimbing = false;
931  }
932  }
933  else if ((character.IsKeyDown(InputType.Left) || character.IsKeyDown(InputType.Right)) &&
935  {
936  isClimbing = false;
937  }
938 
939  if (!isClimbing)
940  {
942  IgnorePlatforms = false;
943  }
944 
945  Ladder GetLowestNearbyLadder(Ladder currentLadder, float threshold = 16.0f)
946  {
947  foreach (Ladder ladder in Ladder.List)
948  {
949  if (ladder == currentLadder || !ladder.Item.IsInteractable(character)) { continue; }
950  if (Math.Abs(ladder.Item.WorldPosition.X - currentLadder.Item.WorldPosition.X) > threshold) { continue; }
951  if (ladder.Item.WorldPosition.Y > currentLadder.Item.WorldPosition.Y) { continue; }
952  if ((currentLadder.Item.WorldRect.Y - currentLadder.Item.Rect.Height) - ladder.Item.WorldRect.Y > threshold) { continue; }
953  return ladder;
954  }
955  return null;
956  }
957 
958  Limb GetClimbingLimb(LimbType limbType)
959  {
961  {
962  // First try to find a match using the secondary type, if that fails, use the primary type and exclude all the limbs with the secondary type.
963  // Secondary limbs are first excluded and then targeted, because some feet are meant to be used as hands in this context, which means we don't want to get them when seeking the feet.
964  return GetLimb(limbType, useSecondaryType: true) ?? GetLimb(limbType, excludeLimbsWithSecondaryType: true);
965  }
966  else
967  {
968  return GetLimb(limbType);
969  }
970  }
971  }
972 
973  protected void RotateHead(Limb head)
974  {
975  Vector2 mousePos = ConvertUnits.ToSimUnits(character.CursorPosition);
976  Vector2 dir = (mousePos - head.SimPosition) * Dir;
977  float rot = MathUtils.VectorToAngle(dir);
978  var neckJoint = GetJointBetweenLimbs(LimbType.Head, LimbType.Torso);
979  if (neckJoint != null)
980  {
981  float offset = MathUtils.WrapAnglePi(GetLimb(LimbType.Torso).body.Rotation);
982  float lowerLimit = neckJoint.LowerLimit + offset;
983  float upperLimit = neckJoint.UpperLimit + offset;
984  float min = Math.Min(lowerLimit, upperLimit);
985  float max = Math.Max(lowerLimit, upperLimit);
986  rot = Math.Clamp(rot, min, max);
987  }
989  }
990 
991  public void ApplyPose(Vector2 leftHandPos, Vector2 rightHandPos, Vector2 leftFootPos, Vector2 rightFootPos, float footMoveForce = 10)
992  {
993  var leftHand = GetLimb(LimbType.LeftHand);
994  var rightHand = GetLimb(LimbType.RightHand);
995  var waist = GetLimb(LimbType.Waist) ?? GetLimb(LimbType.Torso);
996  if (waist == null) { return; }
997  Vector2 midPos = waist.SimPosition;
998  if (leftHand != null)
999  {
1000  leftHand.Disabled = true;
1001  leftHandPos.X *= Dir;
1002  leftHandPos += midPos;
1003  HandIK(leftHand, leftHandPos);
1004  }
1005  if (rightHand != null)
1006  {
1007  rightHand.Disabled = true;
1008  rightHandPos.X *= Dir;
1009  rightHandPos += midPos;
1010  HandIK(rightHand, rightHandPos);
1011  }
1012  var leftFoot = GetLimb(LimbType.LeftFoot);
1013  if (leftFoot != null)
1014  {
1015  leftFoot.Disabled = true;
1016  leftFootPos = new Vector2(waist.SimPosition.X + leftFootPos.X * Dir, GetColliderBottom().Y + leftFootPos.Y);
1017  MoveLimb(leftFoot, leftFootPos, Math.Abs(leftFoot.SimPosition.X - leftFootPos.X) * footMoveForce * leftFoot.Mass, true);
1018  }
1019  var rightFoot = GetLimb(LimbType.RightFoot);
1020  if (rightFoot != null)
1021  {
1022  rightFoot.Disabled = true;
1023  rightFootPos = new Vector2(waist.SimPosition.X + rightFootPos.X * Dir, GetColliderBottom().Y + rightFootPos.Y);
1024  MoveLimb(rightFoot, rightFootPos, Math.Abs(rightFoot.SimPosition.X - rightFootPos.X) * footMoveForce * rightFoot.Mass, true);
1025  }
1026  }
1027 
1028  public void ApplyTestPose()
1029  {
1030  var waist = GetLimb(LimbType.Waist) ?? GetLimb(LimbType.Torso);
1031  if (waist != null)
1032  {
1033  ApplyPose(
1034  new Vector2(-0.75f, -0.2f),
1035  new Vector2(0.75f, -0.2f),
1036  new Vector2(-WalkParams.StepSize.X * 0.5f, -0.1f * RagdollParams.JointScale),
1037  new Vector2(WalkParams.StepSize.X * 0.5f, -0.1f * RagdollParams.JointScale));
1038  }
1039  }
1040 
1041  protected void CalculateArmLengths()
1042  {
1043  //calculate arm and forearm length (atm this assumes that both arms are the same size)
1044  Limb rightForearm = GetLimb(LimbType.RightForearm);
1045  Limb rightHand = GetLimb(LimbType.RightHand);
1046  if (rightHand == null) { return; }
1047 
1048  rightShoulder = GetJointBetweenLimbs(LimbType.Torso, LimbType.RightArm) ?? GetJointBetweenLimbs(LimbType.Head, LimbType.RightArm) ?? GetJoint(LimbType.RightArm, new LimbType[] { LimbType.RightHand, LimbType.RightForearm });
1049  leftShoulder = GetJointBetweenLimbs(LimbType.Torso, LimbType.LeftArm) ?? GetJointBetweenLimbs(LimbType.Head, LimbType.LeftArm) ?? GetJoint(LimbType.LeftArm, new LimbType[] { LimbType.LeftHand, LimbType.LeftForearm });
1050 
1051  Vector2 localAnchorShoulder = Vector2.Zero;
1052  Vector2 localAnchorElbow = Vector2.Zero;
1053  if (rightShoulder != null)
1054  {
1055  localAnchorShoulder = rightShoulder.LimbA.type == LimbType.RightArm ? rightShoulder.LocalAnchorA : rightShoulder.LocalAnchorB;
1056  }
1057  LimbJoint rightElbow = rightForearm == null ?
1058  GetJointBetweenLimbs(LimbType.RightArm, LimbType.RightHand) :
1059  GetJointBetweenLimbs(LimbType.RightArm, LimbType.RightForearm);
1060  if (rightElbow != null)
1061  {
1062  localAnchorElbow = rightElbow.LimbA.type == LimbType.RightArm ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB;
1063  }
1064  upperArmLength = Vector2.Distance(localAnchorShoulder, localAnchorElbow);
1065  if (rightElbow != null)
1066  {
1067  if (rightForearm == null)
1068  {
1069  forearmLength = Vector2.Distance(
1070  rightHand.PullJointLocalAnchorA,
1071  rightElbow.LimbA.type == LimbType.RightHand ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB);
1072  }
1073  else
1074  {
1075  LimbJoint rightWrist = GetJointBetweenLimbs(LimbType.RightForearm, LimbType.RightHand);
1076  if (rightWrist != null)
1077  {
1078  forearmLength = Vector2.Distance(
1079  rightElbow.LimbA.type == LimbType.RightForearm ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB,
1080  rightWrist.LimbA.type == LimbType.RightForearm ? rightWrist.LocalAnchorA : rightWrist.LocalAnchorB);
1081 
1082  forearmLength += Vector2.Distance(
1083  rightHand.PullJointLocalAnchorA,
1084  rightWrist.LimbA.type == LimbType.RightHand ? rightWrist.LocalAnchorA : rightWrist.LocalAnchorB);
1085  }
1086  }
1087  }
1088  }
1089 
1090  protected LimbJoint GetJointBetweenLimbs(LimbType limbTypeA, LimbType limbTypeB)
1091  {
1092  return LimbJoints.FirstOrDefault(lj =>
1093  (lj.LimbA.type == limbTypeA && lj.LimbB.type == limbTypeB) ||
1094  (lj.LimbB.type == limbTypeA && lj.LimbA.type == limbTypeB));
1095  }
1096 
1097  protected LimbJoint GetJoint(LimbType matchingType, IEnumerable<LimbType> ignoredTypes)
1098  {
1099  return LimbJoints.FirstOrDefault(lj =>
1100  lj.LimbA.type == matchingType && ignoredTypes.None(t => lj.LimbB.type == t) ||
1101  lj.LimbB.type == matchingType && ignoredTypes.None(t => lj.LimbB.type == t));
1102  }
1103 
1104  public override void Recreate(RagdollParams ragdollParams = null)
1105  {
1106  base.Recreate(ragdollParams);
1108  {
1110  }
1111  }
1112 
1113  public void RecreateAndRespawn(RagdollParams ragdollParams = null)
1114  {
1115  Vector2 pos = character.WorldPosition;
1116  Recreate(ragdollParams);
1117  character.TeleportTo(pos);
1118  }
1119 
1120  private void StartAnimation(Animation animation)
1121  {
1122  if (animation == Animation.UsingItem)
1123  {
1124  Anim = IsClimbing ? Animation.UsingItemWhileClimbing : Animation.UsingItem;
1125  }
1126  else if (animation == Animation.Climbing)
1127  {
1128  Anim = IsUsingItem ? Animation.UsingItemWhileClimbing : Animation.Climbing;
1129  }
1130  else
1131  {
1132  Anim = animation;
1133  }
1134  }
1135 
1136  private void StopAnimation(Animation animation)
1137  {
1138  if (animation == Animation.UsingItem)
1139  {
1140  Anim = IsClimbing ? Animation.Climbing : Animation.None;
1141  }
1142  else if (animation == Animation.Climbing)
1143  {
1144  Anim = IsUsingItem ? Animation.UsingItem : Animation.None;
1145  }
1146  else
1147  {
1148  Anim = Animation.None;
1149  }
1150  }
1151 
1152  public void StartUsingItem() => StartAnimation(Animation.UsingItem);
1153 
1154  public void StartClimbing() => StartAnimation(Animation.Climbing);
1155 
1156  public void StopUsingItem() => StopAnimation(Animation.UsingItem);
1157 
1158  public void StopClimbing() => StopAnimation(Animation.Climbing);
1159 
1160  private readonly Dictionary<AnimationType, AnimationParams> defaultAnimations = new Dictionary<AnimationType, AnimationParams>();
1161 
1166  public bool TryLoadTemporaryAnimation(StatusEffect.AnimLoadInfo animLoadInfo, bool throwErrors)
1167  {
1168  AnimationType animType = animLoadInfo.Type;
1169  if (tempAnimations.TryGetValue(animType, out AnimSwap animSwap))
1170  {
1171  if (animLoadInfo.File.TryGet(out string fileName) && animSwap.TemporaryAnimation.FileNameWithoutExtension.Equals(fileName, StringComparison.OrdinalIgnoreCase))
1172  {
1173  // Already loaded, keep active
1174  animSwap.IsActive = true;
1175  return true;
1176  }
1177  else if (animLoadInfo.File.TryGet(out ContentPath contentPath) && animSwap.TemporaryAnimation.Path == contentPath)
1178  {
1179  // Already loaded, keep active
1180  animSwap.IsActive = true;
1181  return true;
1182  }
1183  else
1184  {
1185  if (animSwap.Priority >= animLoadInfo.Priority)
1186  {
1187  // If the priority of the current animation is higher than the new animation, just return and do nothing.
1188  // Returning false would tell the status effect to not try again, which is not what we want here, which is why we fake a bit with the return value.
1189  return true;
1190  }
1191  else
1192  {
1193  // Override any previous animations of the same type.
1194  tempAnimations.Remove(animType);
1195  }
1196  }
1197  }
1198  AnimationParams defaultAnimation = GetAnimationParamsFromType(animType);
1199  if (defaultAnimation == null) { return false; }
1200  if (!TryLoadAnimation(animType, animLoadInfo.File, out AnimationParams tempParams, throwErrors)) { return false; }
1201  // Store the default animation, if not yet stored. There should always be just one of the same type.
1202  defaultAnimations.TryAdd(animType, defaultAnimation);
1203  tempAnimations.Add(animType, new AnimSwap(tempParams, animLoadInfo.Priority));
1204  return true;
1205  }
1206 
1207  private void UpdateTemporaryAnimations()
1208  {
1209  if (tempAnimations.None()) { return; }
1210  foreach ((AnimationType animationType, AnimSwap animSwap) in tempAnimations)
1211  {
1212  if (!animSwap.IsActive)
1213  {
1214  if (defaultAnimations.TryGetValue(animSwap.AnimationType, out AnimationParams defaultAnimation))
1215  {
1216  TrySwapAnimParams(defaultAnimation);
1217  expiredAnimations.Add(animationType);
1218  }
1219  else
1220  {
1221  DebugConsole.ThrowError($"[AnimController] Failed to find the default animation parameters of type {animSwap.AnimationType}. Cannot swap back the default animations!");
1222  tempAnimations.Clear();
1223  }
1224  }
1225  }
1226  foreach (AnimationType anim in expiredAnimations)
1227  {
1228  tempAnimations.Remove(anim);
1229  }
1230  expiredAnimations.Clear();
1231  foreach (AnimSwap animSwap in tempAnimations.Values)
1232  {
1233  // Will be removed on the next frame, unless something keeps it alive.
1234  animSwap.IsActive = false;
1235  }
1236  }
1237 
1241  public bool TryLoadAnimation(AnimationType animationType, Either<string, ContentPath> file, out AnimationParams animParams, bool throwErrors)
1242  {
1243  animParams = null;
1244  if (character.IsHumanoid && this is HumanoidAnimController humanAnimController)
1245  {
1246  switch (animationType)
1247  {
1248  case AnimationType.Walk:
1249  humanAnimController.WalkParams = HumanWalkParams.GetAnimParams(character, file, throwErrors);
1250  animParams = humanAnimController.WalkParams;
1251  break;
1252  case AnimationType.Run:
1253  humanAnimController.RunParams = HumanRunParams.GetAnimParams(character, file, throwErrors);
1254  animParams = humanAnimController.RunParams;
1255  break;
1256  case AnimationType.Crouch:
1257  humanAnimController.HumanCrouchParams = HumanCrouchParams.GetAnimParams(character, file, throwErrors);
1258  animParams = humanAnimController.HumanCrouchParams;
1259  break;
1260  case AnimationType.SwimSlow:
1261  humanAnimController.SwimSlowParams = HumanSwimSlowParams.GetAnimParams(character, file, throwErrors);
1262  animParams = humanAnimController.SwimSlowParams;
1263  break;
1264  case AnimationType.SwimFast:
1265  humanAnimController.SwimFastParams = HumanSwimFastParams.GetAnimParams(character, file, throwErrors);
1266  animParams = humanAnimController.SwimFastParams;
1267  break;
1268  default:
1269  DebugConsole.ThrowError($"[AnimController] Animation of type {animationType} not implemented!");
1270  break;
1271  }
1272  }
1273  else
1274  {
1275  switch (animationType)
1276  {
1277  case AnimationType.Walk:
1278  if (CanWalk)
1279  {
1281  animParams = character.AnimController.WalkParams;
1282  }
1283  break;
1284  case AnimationType.Run:
1285  if (CanWalk)
1286  {
1288  animParams = character.AnimController.RunParams;
1289  }
1290  break;
1291  case AnimationType.SwimSlow:
1294  break;
1295  case AnimationType.SwimFast:
1298  break;
1299  default:
1300  DebugConsole.ThrowError($"[AnimController] Animation of type {animationType} not implemented!");
1301  break;
1302  }
1303  }
1304 
1305  bool success = animParams != null;
1306  if (!file.TryGet(out string fileName))
1307  {
1308  if (file.TryGet(out ContentPath contentPath))
1309  {
1310  fileName = contentPath.Value;
1311  if (success)
1312  {
1313  success = contentPath == animParams.Path;
1314  }
1315  }
1316  }
1317  else
1318  {
1319  if (success)
1320  {
1321  success = animParams.FileNameWithoutExtension.Equals(fileName, StringComparison.OrdinalIgnoreCase);
1322  }
1323  }
1324  if (success)
1325  {
1326  DebugConsole.NewMessage($"Animation {fileName} successfully loaded for {character.DisplayName}", Color.LightGreen, debugOnly: true);
1327  }
1328  else if (throwErrors)
1329  {
1330  DebugConsole.ThrowError($"Animation {fileName} for {character.DisplayName} could not be loaded!");
1331  }
1332  return success;
1333  }
1334 
1338  protected bool TrySwapAnimParams(AnimationParams newParams)
1339  {
1340  AnimationType animationType = newParams.AnimationType;
1341  if (character.IsHumanoid && this is HumanoidAnimController humanAnimController)
1342  {
1343  switch (animationType)
1344  {
1345  case AnimationType.Walk:
1346  if (newParams is HumanWalkParams newWalkParams)
1347  {
1348  humanAnimController.WalkParams = newWalkParams;
1349  }
1350  return true;
1351  case AnimationType.Run:
1352  if (newParams is HumanRunParams newRunParams)
1353  {
1354  humanAnimController.HumanRunParams = newRunParams;
1355  }
1356  break;
1357  case AnimationType.Crouch:
1358  if (newParams is HumanCrouchParams newCrouchParams)
1359  {
1360  humanAnimController.HumanCrouchParams = newCrouchParams;
1361  }
1362  return true;
1363  case AnimationType.SwimSlow:
1364  if (newParams is HumanSwimSlowParams newSwimSlowParams)
1365  {
1366  humanAnimController.HumanSwimSlowParams = newSwimSlowParams;
1367  }
1368  return true;
1369  case AnimationType.SwimFast:
1370  if (newParams is HumanSwimFastParams newSwimFastParams)
1371  {
1372  humanAnimController.HumanSwimFastParams = newSwimFastParams;
1373  }
1374  return true;
1375  default:
1376  DebugConsole.ThrowError($"[AnimController] Animation of type {animationType} not implemented!");
1377  return false;
1378  }
1379  }
1380  else
1381  {
1382  switch (animationType)
1383  {
1384  case AnimationType.Walk:
1385  if (newParams is FishWalkParams walkParams)
1386  {
1387  character.AnimController.WalkParams = walkParams;
1388  }
1389  return true;
1390  case AnimationType.Run:
1391  if (newParams is FishRunParams runParams)
1392  {
1393  character.AnimController.RunParams = runParams;
1394  }
1395  return true;
1396  case AnimationType.SwimSlow:
1397  if (newParams is FishSwimSlowParams swimSlowParams)
1398  {
1399  character.AnimController.SwimSlowParams = swimSlowParams;
1400  }
1401  return true;
1402  case AnimationType.SwimFast:
1403  if (newParams is FishSwimFastParams swimFastParams)
1404  {
1405  character.AnimController.SwimFastParams = swimFastParams;
1406  }
1407  return true;
1408  default:
1409  DebugConsole.ThrowError($"[AnimController] Animation of type {animationType} not implemented!");
1410  break;
1411  }
1412  }
1413  return false;
1414  }
1415  }
1416 }
AfflictionPrefab is a prefab that defines a type of affliction that can be applied to a character....
static readonly Identifier DamageType
readonly AnimationType AnimationType
AnimSwap(AnimationParams temporaryAnimation, float priority)
readonly AnimationParams TemporaryAnimation
List< AnimationParams > AllAnimParams
Note: creates a new list every time, because the params might have changed. If there is a need to acc...
void HandIK(Limb hand, Vector2 pos, float armTorque=1.0f, float handTorque=1.0f, float maxAngularVelocity=float.PositiveInfinity)
AnimationType ForceSelectAnimationType
abstract void UpdateAnim(float deltaTime)
Vector2? GetValidOrNull(AnimationParams p, Vector2 v)
override? float HeadPosition
float? GetValidOrNull(AnimationParams p, float? v)
virtual ? Vector2 StepSize
float GetCurrentSpeed(bool useMaxSpeed)
override void Recreate(RagdollParams ragdollParams=null)
Call this to create the ragdoll from the RagdollParams.
AnimationParams? CurrentAnimationParams
override? float TorsoAngle
GroundedMovementParams? CurrentGroundedParams
abstract void DragCharacter(Character target, float deltaTime)
readonly Dictionary< AnimationType, AnimSwap > tempAnimations
abstract SwimParams SwimFastParams
bool TrySwapAnimParams(AnimationParams newParams)
Simply swaps existing animation parameters as current parameters.
void UpdateBlink(float deltaTime)
void UpdateAnimations(float deltaTime)
void UpdateConstantTorque(float deltaTime)
readonly HashSet< AnimationType > expiredAnimations
abstract GroundedMovementParams RunParams
bool TryLoadAnimation(AnimationType animationType, Either< string, ContentPath > file, out AnimationParams animParams, bool throwErrors)
Loads animations. Non-permanent (= resets on load).
AnimationParams GetAnimationParamsFromType(AnimationType type)
bool? IsMovingFast
Note: Presupposes that the slow speed is lower than the high speed. Otherwise will give invalid resul...
void HoldItem(float deltaTime, Item item, Vector2[] handlePos, Vector2 itemPos, bool aim, float holdAngle, float itemAngleRelativeToHoldAngle=0.0f, bool aimMelee=false, Vector2? targetPos=null)
bool TryLoadTemporaryAnimation(StatusEffect.AnimLoadInfo animLoadInfo, bool throwErrors)
Loads an animation (variation) that automatically resets in 0.1s, unless triggered again....
LimbJoint GetJoint(LimbType matchingType, IEnumerable< LimbType > ignoredTypes)
AnimController(Character character, string seed, RagdollParams ragdollParams=null)
virtual Vector2 AimSourceSimPos
void RecreateAndRespawn(RagdollParams ragdollParams=null)
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)
abstract GroundedMovementParams WalkParams
abstract SwimParams SwimSlowParams
void ApplyPose(Vector2 leftHandPos, Vector2 rightHandPos, Vector2 leftFootPos, Vector2 rightFootPos, float footMoveForce=10)
virtual float GetSpeed(AnimationType type)
void Grab(Vector2 rightHandPos, Vector2 leftHandPos)
virtual AnimationType AnimationType
bool IsRemotelyControlled
Is the character controlled remotely (either by another player, or a server-side AIController)
Item????????? SelectedItem
The primary selected item. It can be any device that character interacts with. This excludes items li...
float SpeedMultiplier
Can be used to modify the character's speed via StatusEffects
Item SelectedSecondaryItem
The secondary selected item. It's an item other than a device (see SelectedItem), e....
bool Equals(ContentPath other)
Definition: ContentPath.cs:136
virtual Vector2 WorldPosition
Definition: Entity.cs:49
Submarine Submarine
Definition: Entity.cs:53
static FishRunParams GetAnimParams(Character character, Either< string, ContentPath > file, bool throwErrors=true)
static FishSwimFastParams GetAnimParams(Character character, Either< string, ContentPath > file, bool throwErrors=true)
static FishSwimSlowParams GetAnimParams(Character character, Either< string, ContentPath > file, bool throwErrors=true)
static FishWalkParams GetAnimParams(Character character, Either< string, ContentPath > file, bool throwErrors=true)
static NetworkMember NetworkMember
Definition: GameMain.cs:190
static HumanCrouchParams GetAnimParams(Character character, Either< string, ContentPath > file, bool throwErrors=true)
static HumanRunParams GetAnimParams(Character character, Either< string, ContentPath > file, bool throwErrors=true)
static HumanSwimFastParams GetAnimParams(Character character, Either< string, ContentPath > file, bool throwErrors=true)
static HumanSwimSlowParams GetAnimParams(Character character, Either< string, ContentPath > file, bool throwErrors=true)
static HumanWalkParams GetAnimParams(Character character, Either< string, ContentPath > file, bool throwErrors=true)
void SetTransform(Vector2 simPosition, float rotation, bool findNewHull=true, bool setPrevTransform=true)
bool IsInteractable(Character character)
Returns interactibility based on whether the character is on a player team
override string Name
Note that this is not a LocalizedString instance, just the current name of the item as a string....
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)
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...
ContentPackage? ContentPackage
Definition: Prefab.cs:37
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 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
StatusEffects can be used to execute various kinds of effects: modifying the state of some entity in ...
readonly record struct AnimLoadInfo(AnimationType Type, Either< string, ContentPath > File, float Priority, ImmutableArray< Identifier > ExpectedSpeciesNames)
Submarine(SubmarineInfo info, bool showErrorMessages=true, Func< Submarine, List< MapEntity >> loadEntities=null, IdRemap linkedRemap=null)