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  get { return _isActive; }
44  set
45  {
46  if (value)
47  {
48  expirationTimer = expirationTime;
49  }
50  _isActive = value;
51  }
52  }
53  private bool _isActive;
54  private float expirationTimer;
55  private const float expirationTime = 0.1f;
56 
57  public AnimSwap(AnimationParams temporaryAnimation, float priority)
58  {
59  AnimationType = temporaryAnimation.AnimationType;
60  TemporaryAnimation = temporaryAnimation;
61  Priority = priority;
62  IsActive = true;
63  }
64 
65  public void Update(float deltaTime)
66  {
67  expirationTimer -= deltaTime;
68  if (expirationTimer <= 0)
69  {
70  IsActive = false;
71  }
72  }
73  }
74 
75  protected readonly Dictionary<AnimationType, AnimSwap> tempAnimations = new Dictionary<AnimationType, AnimSwap>();
76  protected readonly HashSet<AnimationType> expiredAnimations = new HashSet<AnimationType>();
77 
79  {
80  get
81  {
82  if (ForceSelectAnimationType == AnimationType.NotDefined)
83  {
85  }
86  else
87  {
89  }
90  }
91  }
94  {
95  get
96  {
97  if (ForceSelectAnimationType != AnimationType.NotDefined)
98  {
100  }
101  if (!CanWalk)
102  {
103  //DebugConsole.ThrowError($"{character.SpeciesName} cannot walk!");
104  return null;
105  }
106  else
107  {
108  if (this is HumanoidAnimController humanAnimController && humanAnimController.Crouching)
109  {
110  return humanAnimController.HumanCrouchParams;
111  }
112  return IsMovingFast ? RunParams : WalkParams;
113  }
114  }
115  }
117  {
118  get
119  {
120  if (ForceSelectAnimationType != AnimationType.NotDefined)
121  {
123  }
124  else
125  {
127  }
128  }
129  }
130 
131  public bool CanWalk => RagdollParams.CanWalk;
132  public bool IsMovingBackwards =>
133  !InWater &&
134  Math.Sign(targetMovement.X) == -Math.Sign(Dir) &&
135  CurrentAnimationParams is not FishGroundedParams { Flip: false } &&
136  Anim != Animation.Climbing;
137 
138  // TODO: define death anim duration in XML
139  protected float deathAnimTimer, deathAnimDuration = 5.0f;
140 
144  public bool IsMovingFast
145  {
146  get
147  {
148  if (InWater || !CanWalk)
149  {
150  return TargetMovement.LengthSquared() > MathUtils.Pow2(SwimSlowParams.MovementSpeed + 0.0001f);
151  }
152  else
153  {
154  return Math.Abs(TargetMovement.X) > (WalkParams.MovementSpeed + RunParams.MovementSpeed) / 2.0f;
155  }
156  }
157  }
158 
163  public List<AnimationParams> AllAnimParams
164  {
165  get
166  {
167  if (CanWalk)
168  {
169  var anims = new List<AnimationParams> { WalkParams, RunParams, SwimSlowParams, SwimFastParams };
170  if (this is HumanoidAnimController humanAnimController)
171  {
172  anims.Add(humanAnimController.HumanCrouchParams);
173  }
174  return anims;
175  }
176  else
177  {
178  return new List<AnimationParams> { SwimSlowParams, SwimFastParams };
179  }
180  }
181  }
182 
183  public enum Animation { None, Climbing, UsingItem, Struggle, CPR, UsingItemWhileClimbing };
184  public Animation Anim;
185 
186  public bool IsUsingItem => Anim == Animation.UsingItem || Anim == Animation.UsingItemWhileClimbing;
187  public bool IsClimbing => Anim == Animation.Climbing || Anim == Animation.UsingItemWhileClimbing;
188 
189  public Vector2 AimSourceWorldPos
190  {
191  get
192  {
193  Vector2 sourcePos = character.AnimController.AimSourcePos;
194  if (character.Submarine != null) { sourcePos += character.Submarine.Position; }
195  return sourcePos;
196  }
197  }
198 
199  public Vector2 AimSourcePos => ConvertUnits.ToDisplayUnits(AimSourceSimPos);
200  public virtual Vector2 AimSourceSimPos => Collider.SimPosition;
201 
202  protected float? GetValidOrNull(AnimationParams p, float? v)
203  {
204  if (p == null) { return null; }
205  if (v == null) { return null; }
206  if (!MathUtils.IsValid(v.Value)) { return null; }
207  return v.Value;
208  }
209  protected Vector2? GetValidOrNull(AnimationParams p, Vector2 v)
210  {
211  if (p == null) { return null; }
212  return v;
213  }
214 
217  public override float? HeadAngle => GetValidOrNull(CurrentAnimationParams, CurrentAnimationParams?.HeadAngleInRadians);
218  public override float? TorsoAngle => GetValidOrNull(CurrentAnimationParams, CurrentAnimationParams?.TorsoAngleInRadians);
220 
221  public bool AnimationTestPose { get; set; }
222 
223  public float WalkPos { get; protected set; }
224 
225  public AnimController(Character character, string seed, RagdollParams ragdollParams = null) : base(character, seed, ragdollParams) { }
226 
227  public void UpdateAnimations(float deltaTime)
228  {
229  UpdateTemporaryAnimations(deltaTime);
230  UpdateAnim(deltaTime);
231  }
232 
233  protected abstract void UpdateAnim(float deltaTime);
234 
235  public abstract void DragCharacter(Character target, float deltaTime);
236 
237  public virtual float GetSpeed(AnimationType type)
238  {
239  GroundedMovementParams movementParams;
240  switch (type)
241  {
242  case AnimationType.Walk:
243  if (!CanWalk)
244  {
245  DebugConsole.ThrowError($"{character.SpeciesName} cannot walk!");
246  return 0;
247  }
248  movementParams = WalkParams;
249  break;
250  case AnimationType.Run:
251  if (!CanWalk)
252  {
253  DebugConsole.ThrowError($"{character.SpeciesName} cannot run!");
254  return 0;
255  }
256  movementParams = RunParams;
257  break;
258  case AnimationType.SwimSlow:
260  case AnimationType.SwimFast:
262  default:
263  throw new NotImplementedException(type.ToString());
264  }
265  return IsMovingBackwards ? movementParams.MovementSpeed * movementParams.BackwardsMovementMultiplier : movementParams.MovementSpeed;
266  }
267 
268  public float GetCurrentSpeed(bool useMaxSpeed)
269  {
270  AnimationType animType;
271  if (InWater || !CanWalk)
272  {
273  if (useMaxSpeed)
274  {
275  animType = AnimationType.SwimFast;
276  }
277  else
278  {
279  animType = AnimationType.SwimSlow;
280  }
281  }
282  else
283  {
284  if (useMaxSpeed)
285  {
286  animType = AnimationType.Run;
287  }
288  else
289  {
290  if (this is HumanoidAnimController humanAnimController && humanAnimController.Crouching)
291  {
292  animType = AnimationType.Crouch;
293  }
294  else
295  {
296  animType = AnimationType.Walk;
297  }
298  }
299  }
300  return GetSpeed(animType);
301  }
302 
304  {
305  switch (type)
306  {
307  case AnimationType.Walk:
308  return CanWalk ? WalkParams : null;
309  case AnimationType.Run:
310  return CanWalk ? RunParams : null;
311  case AnimationType.Crouch:
312  if (this is HumanoidAnimController humanAnimController)
313  {
314  return humanAnimController.HumanCrouchParams;
315  }
316  else
317  {
318  DebugConsole.ThrowError($"Animation params of type {type} not implemented for non-humanoids!");
319  return null;
320  }
321  case AnimationType.SwimSlow:
322  return SwimSlowParams;
323  case AnimationType.SwimFast:
324  return SwimFastParams;
325  case AnimationType.NotDefined:
326  default:
327  return null;
328  }
329  }
330 
331  public float GetHeightFromFloor() => GetColliderBottom().Y - FloorY;
332 
333  // We need some margin, because if a hatch has closed, it's possible that the height from floor is slightly negative.
334  public bool IsAboveFloor => GetHeightFromFloor() > -0.1f;
335 
336  public float FlipLockTime { get; private set; }
337  public void LockFlipping(float time = 0.2f)
338  {
339  FlipLockTime = (float)Timing.TotalTime + time;
340  }
341 
342  public void UpdateUseItem(bool allowMovement, Vector2 handWorldPos)
343  {
344  useItemTimer = 0.05f;
345  StartUsingItem();
346 
347  if (!allowMovement)
348  {
349  TargetMovement = Vector2.Zero;
350  TargetDir = handWorldPos.X > character.WorldPosition.X ? Direction.Right : Direction.Left;
351  if (InWater)
352  {
353  float sqrDist = Vector2.DistanceSquared(character.WorldPosition, handWorldPos);
354  if (sqrDist > MathUtils.Pow(ConvertUnits.ToDisplayUnits(upperArmLength + forearmLength), 2))
355  {
356  TargetMovement = GetTargetMovement(Vector2.Normalize(handWorldPos - character.WorldPosition));
357  }
358  }
359  else
360  {
361  float distX = Math.Abs(handWorldPos.X - character.WorldPosition.X);
362  if (distX > ConvertUnits.ToDisplayUnits(upperArmLength + forearmLength))
363  {
364  TargetMovement = GetTargetMovement(Vector2.UnitX * Math.Sign(handWorldPos.X - character.WorldPosition.X));
365  }
366  }
367  Vector2 GetTargetMovement(Vector2 dir)
368  {
369  return dir * GetCurrentSpeed(false) * Math.Max(character.SpeedMultiplier, 1);
370  }
371  }
372 
373  if (!character.Enabled) { return; }
374 
375  Vector2 handSimPos = ConvertUnits.ToSimUnits(handWorldPos);
376  if (character.Submarine != null)
377  {
378  handSimPos -= character.Submarine.SimPosition;
379  }
380 
381  Vector2 refPos = rightShoulder?.WorldAnchorA ?? leftShoulder?.WorldAnchorA ?? MainLimb.SimPosition;
382  Vector2 diff = handSimPos - refPos;
383  float dist = diff.Length();
384  float maxDist = ArmLength * 0.9f;
385  if (dist > maxDist)
386  {
387  handSimPos = refPos + diff / dist * maxDist;
388  }
389 
390  var leftHand = GetLimb(LimbType.LeftHand);
391  if (leftHand != null)
392  {
393  leftHand.Disabled = true;
394  leftHand.PullJointEnabled = true;
395  leftHand.PullJointWorldAnchorB = handSimPos;
396  }
397 
398  var rightHand = GetLimb(LimbType.RightHand);
399  if (rightHand != null)
400  {
401  rightHand.Disabled = true;
402  rightHand.PullJointEnabled = true;
403  rightHand.PullJointWorldAnchorB = handSimPos;
404  }
405 
406  //make the character crouch if using an item some distance below them (= on the floor)
407  if (!inWater &&
408  character.WorldPosition.Y - handWorldPos.Y > ConvertUnits.ToDisplayUnits(CurrentGroundedParams.TorsoPosition) / 4 &&
409  this is HumanoidAnimController humanoidAnimController)
410  {
411  humanoidAnimController.Crouching = true;
412  humanoidAnimController.ForceSelectAnimationType = AnimationType.Crouch;
413  character.SetInput(InputType.Crouch, hit: false, held: true);
414  }
415  }
416 
417  public void Grab(Vector2 rightHandPos, Vector2 leftHandPos)
418  {
419  for (int i = 0; i < 2; i++)
420  {
421  Limb pullLimb = (i == 0) ? GetLimb(LimbType.LeftHand) : GetLimb(LimbType.RightHand);
422 
423  pullLimb.Disabled = true;
424 
425  pullLimb.PullJointEnabled = true;
426  pullLimb.PullJointWorldAnchorB = (i == 0) ? rightHandPos : leftHandPos;
427  pullLimb.PullJointMaxForce = 500.0f;
428  }
429  }
430 
431  private Direction previousDirection;
432  private readonly Vector2[] transformedHandlePos = new Vector2[2];
433  //TODO: refactor this method, it's way too convoluted
434  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)
435  {
436  aimingMelee = aimMelee;
437  if (character.Stun > 0.0f || character.IsIncapacitated)
438  {
439  aim = false;
440  }
441 
442  //calculate the handle positions
443  Matrix itemTransform = Matrix.CreateRotationZ(item.body.Rotation);
444  transformedHandlePos[0] = Vector2.Transform(handlePos[0], itemTransform);
445  transformedHandlePos[1] = Vector2.Transform(handlePos[1], itemTransform);
446 
447  Limb torso = GetLimb(LimbType.Torso) ?? MainLimb;
448  Limb leftHand = GetLimb(LimbType.LeftHand);
449  Limb rightHand = GetLimb(LimbType.RightHand);
450 
451  var controller = character.SelectedItem?.GetComponent<Controller>();
452  bool usingController = controller is { AllowAiming: false };
453  if (!usingController)
454  {
455  controller = character.SelectedSecondaryItem?.GetComponent<Controller>();
456  usingController = controller is { AllowAiming: false };
457  }
458  bool isClimbing = character.IsClimbing && Math.Abs(character.AnimController.TargetMovement.Y) > 0.01f;
459  float itemAngle;
460  Holdable holdable = item.GetComponent<Holdable>();
461  float torsoRotation = torso.Rotation;
462 
463  Item rightHandItem = character.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand);
464  bool equippedInRightHand = rightHandItem == item && rightHand is { IsSevered: false };
465  Item leftHandItem = character.Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand);
466  bool equippedInLeftHand = leftHandItem == item && leftHand is { IsSevered: false };
467  if (aim && !isClimbing && !usingController && character.Stun <= 0.0f && itemPos != Vector2.Zero && !character.IsIncapacitated)
468  {
469  targetPos ??= ConvertUnits.ToSimUnits(character.SmoothedCursorPosition);
470 
471  Vector2 diff = holdable.Aimable ?
472  (targetPos.Value - AimSourceSimPos) * Dir :
473  MathUtils.RotatePoint(Vector2.UnitX, torsoRotation);
474 
475  holdAngle = MathUtils.VectorToAngle(new Vector2(diff.X, diff.Y * Dir)) - torsoRotation * Dir;
476  holdAngle += GetAimWobble(rightHand, leftHand, item);
477  itemAngle = torsoRotation + holdAngle * Dir;
478 
479  if (holdable.ControlPose)
480  {
481  //if holding two items that should control the characters' pose, let the item in the right hand do it
482  bool anotherItemControlsPose = equippedInLeftHand && rightHandItem != item && (rightHandItem?.GetComponent<Holdable>()?.ControlPose ?? false);
483  if (!anotherItemControlsPose && TargetMovement == Vector2.Zero && inWater)
484  {
485  torso.body.AngularVelocity -= torso.body.AngularVelocity * 0.1f;
486  torso.body.ApplyForce(torso.body.LinearVelocity * -0.5f);
487  }
488  aiming = true;
489  }
490  }
491  else
492  {
493  if (holdable.UseHandRotationForHoldAngle)
494  {
495  if (equippedInRightHand)
496  {
497  itemAngle = rightHand.Rotation + holdAngle * Dir;
498  }
499  else if (equippedInLeftHand)
500  {
501  itemAngle = leftHand.Rotation + holdAngle * Dir;
502  }
503  else
504  {
505  itemAngle = torsoRotation + holdAngle * Dir;
506  }
507  }
508  else
509  {
510  itemAngle = torsoRotation + holdAngle * Dir;
511  }
512  }
513 
514  if (rightShoulder == null) { return; }
515  Vector2 transformedHoldPos = rightShoulder.WorldAnchorA;
516  if (itemPos == Vector2.Zero || isClimbing || usingController)
517  {
518  if (equippedInRightHand)
519  {
520  transformedHoldPos = rightHand.PullJointWorldAnchorA - transformedHandlePos[0];
521  itemAngle = rightHand.Rotation + (holdAngle - rightHand.Params.GetSpriteOrientation() + MathHelper.PiOver2) * Dir;
522  }
523  else if (equippedInLeftHand)
524  {
525  transformedHoldPos = leftHand.PullJointWorldAnchorA - transformedHandlePos[1];
526  itemAngle = leftHand.Rotation + (holdAngle - leftHand.Params.GetSpriteOrientation() + MathHelper.PiOver2) * Dir;
527  }
528  }
529  else
530  {
531  if (equippedInRightHand)
532  {
533  transformedHoldPos = rightShoulder.WorldAnchorA;
534  rightHand.Disabled = true;
535  }
536  if (equippedInLeftHand)
537  {
538  if (leftShoulder == null) { return; }
539  transformedHoldPos = leftShoulder.WorldAnchorA;
540  leftHand.Disabled = true;
541  }
542  itemPos.X *= Dir;
543  transformedHoldPos += Vector2.Transform(itemPos, Matrix.CreateRotationZ(itemAngle));
544  }
545 
546  item.body.ResetDynamics();
547 
548  Vector2 currItemPos = equippedInRightHand ?
549  rightHand.PullJointWorldAnchorA - transformedHandlePos[0] :
550  leftHand.PullJointWorldAnchorA - transformedHandlePos[1];
551 
552  if (!MathUtils.IsValid(currItemPos))
553  {
554  string errorMsg = "Attempted to move the item \"" + item + "\" to an invalid position in HumanidAnimController.HoldItem: " +
555  currItemPos + ", rightHandPos: " + rightHand.PullJointWorldAnchorA + ", leftHandPos: " + leftHand.PullJointWorldAnchorA +
556  ", handlePos[0]: " + handlePos[0] + ", handlePos[1]: " + handlePos[1] +
557  ", transformedHandlePos[0]: " + transformedHandlePos[0] + ", transformedHandlePos[1]:" + transformedHandlePos[1] +
558  ", item pos: " + item.SimPosition + ", itemAngle: " + itemAngle +
559  ", collider pos: " + character.SimPosition;
560  DebugConsole.Log(errorMsg);
561  GameAnalyticsManager.AddErrorEventOnce(
562  "HumanoidAnimController.HoldItem:InvalidPos:" + character.Name + item.Name,
563  GameAnalyticsManager.ErrorSeverity.Error,
564  errorMsg);
565 
566  return;
567  }
568 
569  float targetAngle = MathUtils.WrapAngleTwoPi(itemAngle + itemAngleRelativeToHoldAngle * Dir);
570  float currentRotation = MathUtils.WrapAngleTwoPi(item.body.Rotation);
571  float itemRotation = MathHelper.SmoothStep(currentRotation, targetAngle, deltaTime * 25);
572  if (previousDirection != dir || Math.Abs(targetAngle - currentRotation) > MathHelper.Pi)
573  {
574  itemRotation = targetAngle;
575  }
576  item.SetTransform(currItemPos, itemRotation, setPrevTransform: false);
577  previousDirection = dir;
578 
579  if (holdable.Pusher != null)
580  {
581  if (character.Stun > 0.0f || character.IsIncapacitated)
582  {
583  holdable.Pusher.Enabled = false;
584  }
585  else
586  {
587  if (!holdable.Pusher.Enabled)
588  {
589  holdable.Pusher.Enabled = true;
590  holdable.Pusher.ResetDynamics();
591  holdable.Pusher.SetTransform(currItemPos, itemAngle);
592  }
593  else
594  {
595  holdable.Pusher.TargetPosition = currItemPos;
596  holdable.Pusher.TargetRotation = itemRotation;
597  holdable.Pusher.MoveToTargetPosition(true);
598  }
599  }
600  }
601 
602  if (!isClimbing && !character.IsIncapacitated && itemPos != Vector2.Zero && (aim || !holdable.UseHandRotationForHoldAngle))
603  {
604  for (int i = 0; i < 2; i++)
605  {
606  if (!character.Inventory.IsInLimbSlot(item, i == 0 ? InvSlotType.RightHand : InvSlotType.LeftHand)) { continue; }
607 #if DEBUG
608  if (handlePos[i].LengthSquared() > ArmLength)
609  {
610  DebugConsole.AddWarning($"Aim position for the item {item.Name} may be incorrect (further than the length of the character's arm)",
611  item.Prefab.ContentPackage);
612  }
613 #endif
614  HandIK(
615  i == 0 ? rightHand : leftHand, transformedHoldPos + transformedHandlePos[i],
618  maxAngularVelocity: 15.0f);
619  }
620  }
621  }
622 
623  private float GetAimWobble(Limb rightHand, Limb leftHand, Item heldItem)
624  {
625  float wobbleStrength = 0.0f;
626  if (character.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand) == heldItem)
627  {
628  wobbleStrength += Character.CharacterHealth.GetLimbDamage(rightHand, afflictionType: AfflictionPrefab.DamageType);
629  }
630  if (character.Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand) == heldItem)
631  {
632  wobbleStrength += Character.CharacterHealth.GetLimbDamage(leftHand, afflictionType: AfflictionPrefab.DamageType);
633  }
634  if (wobbleStrength <= 0.1f) { return 0.0f; }
635  wobbleStrength = (float)Math.Min(wobbleStrength, 1.0f);
636 
637  float lowFreqNoise = PerlinNoise.GetPerlin((float)Timing.TotalTime / 320.0f, (float)Timing.TotalTime / 240.0f) - 0.5f;
638  float highFreqNoise = PerlinNoise.GetPerlin((float)Timing.TotalTime / 40.0f, (float)Timing.TotalTime / 50.0f) - 0.5f;
639 
640  return (lowFreqNoise * 1.0f + highFreqNoise * 0.1f) * wobbleStrength;
641  }
642 
643  public void HandIK(Limb hand, Vector2 pos, float armTorque = 1.0f, float handTorque = 1.0f, float maxAngularVelocity = float.PositiveInfinity)
644  {
645  Vector2 shoulderPos;
646 
647  Limb arm, forearm;
648  if (hand.type == LimbType.LeftHand)
649  {
650  if (leftShoulder == null) { return; }
651  shoulderPos = leftShoulder.WorldAnchorA;
652  arm = GetLimb(LimbType.LeftArm);
653  forearm = GetLimb(LimbType.LeftForearm);
654  LeftHandIKPos = pos;
655  }
656  else
657  {
658  if (rightShoulder == null) { return; }
659  shoulderPos = rightShoulder.WorldAnchorA;
660  arm = GetLimb(LimbType.RightArm);
661  forearm = GetLimb(LimbType.RightForearm);
662  RightHandIKPos = pos;
663  }
664  if (arm == null) { return; }
665 
666  //distance from shoulder to holdpos
667  float c = Vector2.Distance(pos, shoulderPos);
668  c = MathHelper.Clamp(c, Math.Abs(upperArmLength - forearmLength), forearmLength + upperArmLength - 0.01f);
669 
670  float armAngle = MathUtils.VectorToAngle(pos - shoulderPos) + arm.Params.GetSpriteOrientation() - MathHelper.PiOver2;
671  float upperArmAngle = MathUtils.SolveTriangleSSS(forearmLength, upperArmLength, c) * Dir;
672  float lowerArmAngle = MathUtils.SolveTriangleSSS(upperArmLength, forearmLength, c) * Dir;
673 
674  //make sure the arm angle "has the same number of revolutions" as the arm
675  while (arm.Rotation - armAngle > MathHelper.Pi)
676  {
677  armAngle += MathHelper.TwoPi;
678  }
679  while (arm.Rotation - armAngle < -MathHelper.Pi)
680  {
681  armAngle -= MathHelper.TwoPi;
682  }
683 
684  if (arm?.body != null && Math.Abs(arm.body.AngularVelocity) < maxAngularVelocity)
685  {
686  arm.body.SmoothRotate(armAngle - upperArmAngle, 100.0f * armTorque * arm.Mass, wrapAngle: false);
687  }
688  float forearmAngle = armAngle + lowerArmAngle;
689  if (forearm?.body != null && Math.Abs(forearm.body.AngularVelocity) < maxAngularVelocity)
690  {
691  forearm.body.SmoothRotate(forearmAngle, 100.0f * handTorque * forearm.Mass, wrapAngle: false);
692  }
693  if (hand?.body != null && Math.Abs(hand.body.AngularVelocity) < maxAngularVelocity)
694  {
695  float handAngle = forearm != null ? forearmAngle : armAngle;
696  hand.body.SmoothRotate(handAngle, 10.0f * handTorque * hand.Mass, wrapAngle: false);
697  }
698  }
699 
700  public void ApplyPose(Vector2 leftHandPos, Vector2 rightHandPos, Vector2 leftFootPos, Vector2 rightFootPos, float footMoveForce = 10)
701  {
702  var leftHand = GetLimb(LimbType.LeftHand);
703  var rightHand = GetLimb(LimbType.RightHand);
704  var waist = GetLimb(LimbType.Waist) ?? GetLimb(LimbType.Torso);
705  if (waist == null) { return; }
706  Vector2 midPos = waist.SimPosition;
707  if (leftHand != null)
708  {
709  leftHand.Disabled = true;
710  leftHandPos.X *= Dir;
711  leftHandPos += midPos;
712  HandIK(leftHand, leftHandPos);
713  }
714  if (rightHand != null)
715  {
716  rightHand.Disabled = true;
717  rightHandPos.X *= Dir;
718  rightHandPos += midPos;
719  HandIK(rightHand, rightHandPos);
720  }
721  var leftFoot = GetLimb(LimbType.LeftFoot);
722  if (leftFoot != null)
723  {
724  leftFoot.Disabled = true;
725  leftFootPos = new Vector2(waist.SimPosition.X + leftFootPos.X * Dir, GetColliderBottom().Y + leftFootPos.Y);
726  MoveLimb(leftFoot, leftFootPos, Math.Abs(leftFoot.SimPosition.X - leftFootPos.X) * footMoveForce * leftFoot.Mass, true);
727  }
728  var rightFoot = GetLimb(LimbType.RightFoot);
729  if (rightFoot != null)
730  {
731  rightFoot.Disabled = true;
732  rightFootPos = new Vector2(waist.SimPosition.X + rightFootPos.X * Dir, GetColliderBottom().Y + rightFootPos.Y);
733  MoveLimb(rightFoot, rightFootPos, Math.Abs(rightFoot.SimPosition.X - rightFootPos.X) * footMoveForce * rightFoot.Mass, true);
734  }
735  }
736 
737  public void ApplyTestPose()
738  {
739  var waist = GetLimb(LimbType.Waist) ?? GetLimb(LimbType.Torso);
740  if (waist != null)
741  {
742  ApplyPose(
743  new Vector2(-0.75f, -0.2f),
744  new Vector2(0.75f, -0.2f),
745  new Vector2(-WalkParams.StepSize.X * 0.5f, -0.1f * RagdollParams.JointScale),
746  new Vector2(WalkParams.StepSize.X * 0.5f, -0.1f * RagdollParams.JointScale));
747  }
748  }
749 
750  protected void CalculateArmLengths()
751  {
752  //calculate arm and forearm length (atm this assumes that both arms are the same size)
753  Limb rightForearm = GetLimb(LimbType.RightForearm);
754  Limb rightHand = GetLimb(LimbType.RightHand);
755  if (rightHand == null) { return; }
756 
757  rightShoulder = GetJointBetweenLimbs(LimbType.Torso, LimbType.RightArm) ?? GetJointBetweenLimbs(LimbType.Head, LimbType.RightArm) ?? GetJoint(LimbType.RightArm, new LimbType[] { LimbType.RightHand, LimbType.RightForearm });
758  leftShoulder = GetJointBetweenLimbs(LimbType.Torso, LimbType.LeftArm) ?? GetJointBetweenLimbs(LimbType.Head, LimbType.LeftArm) ?? GetJoint(LimbType.LeftArm, new LimbType[] { LimbType.LeftHand, LimbType.LeftForearm });
759 
760  Vector2 localAnchorShoulder = Vector2.Zero;
761  Vector2 localAnchorElbow = Vector2.Zero;
762  if (rightShoulder != null)
763  {
765  }
766  LimbJoint rightElbow = rightForearm == null ?
767  GetJointBetweenLimbs(LimbType.RightArm, LimbType.RightHand) :
768  GetJointBetweenLimbs(LimbType.RightArm, LimbType.RightForearm);
769  if (rightElbow != null)
770  {
771  localAnchorElbow = rightElbow.LimbA.type == LimbType.RightArm ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB;
772  }
773  upperArmLength = Vector2.Distance(localAnchorShoulder, localAnchorElbow);
774  if (rightElbow != null)
775  {
776  if (rightForearm == null)
777  {
778  forearmLength = Vector2.Distance(
779  rightHand.PullJointLocalAnchorA,
780  rightElbow.LimbA.type == LimbType.RightHand ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB);
781  }
782  else
783  {
784  LimbJoint rightWrist = GetJointBetweenLimbs(LimbType.RightForearm, LimbType.RightHand);
785  if (rightWrist != null)
786  {
787  forearmLength = Vector2.Distance(
788  rightElbow.LimbA.type == LimbType.RightForearm ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB,
789  rightWrist.LimbA.type == LimbType.RightForearm ? rightWrist.LocalAnchorA : rightWrist.LocalAnchorB);
790 
791  forearmLength += Vector2.Distance(
792  rightHand.PullJointLocalAnchorA,
793  rightWrist.LimbA.type == LimbType.RightHand ? rightWrist.LocalAnchorA : rightWrist.LocalAnchorB);
794  }
795  }
796  }
797  }
798 
799  protected LimbJoint GetJointBetweenLimbs(LimbType limbTypeA, LimbType limbTypeB)
800  {
801  return LimbJoints.FirstOrDefault(lj =>
802  (lj.LimbA.type == limbTypeA && lj.LimbB.type == limbTypeB) ||
803  (lj.LimbB.type == limbTypeA && lj.LimbA.type == limbTypeB));
804  }
805 
806  protected LimbJoint GetJoint(LimbType matchingType, IEnumerable<LimbType> ignoredTypes)
807  {
808  return LimbJoints.FirstOrDefault(lj =>
809  lj.LimbA.type == matchingType && ignoredTypes.None(t => lj.LimbB.type == t) ||
810  lj.LimbB.type == matchingType && ignoredTypes.None(t => lj.LimbB.type == t));
811  }
812 
813  public override void Recreate(RagdollParams ragdollParams = null)
814  {
815  base.Recreate(ragdollParams);
817  {
819  }
820  }
821 
822  private void StartAnimation(Animation animation)
823  {
824  if (animation == Animation.UsingItem)
825  {
826  Anim = IsClimbing ? Animation.UsingItemWhileClimbing : Animation.UsingItem;
827  }
828  else if (animation == Animation.Climbing)
829  {
830  Anim = IsUsingItem ? Animation.UsingItemWhileClimbing : Animation.Climbing;
831  }
832  else
833  {
834  Anim = animation;
835  }
836  }
837 
838  private void StopAnimation(Animation animation)
839  {
840  if (animation == Animation.UsingItem)
841  {
842  Anim = IsClimbing ? Animation.Climbing : Animation.None;
843  }
844  else if (animation == Animation.Climbing)
845  {
846  Anim = IsUsingItem ? Animation.UsingItem : Animation.None;
847  }
848  else
849  {
850  Anim = Animation.None;
851  }
852  }
853 
854  public void StartUsingItem() => StartAnimation(Animation.UsingItem);
855 
856  public void StartClimbing() => StartAnimation(Animation.Climbing);
857 
858  public void StopUsingItem() => StopAnimation(Animation.UsingItem);
859 
860  public void StopClimbing() => StopAnimation(Animation.Climbing);
861 
862  private readonly Dictionary<AnimationType, AnimationParams> defaultAnimations = new Dictionary<AnimationType, AnimationParams>();
863 
868  public bool TryLoadTemporaryAnimation(StatusEffect.AnimLoadInfo animLoadInfo, bool throwErrors)
869  {
870  AnimationType animType = animLoadInfo.Type;
871  if (tempAnimations.TryGetValue(animType, out AnimSwap animSwap))
872  {
873  if (animLoadInfo.File.TryGet(out string fileName) && animSwap.TemporaryAnimation.FileNameWithoutExtension.Equals(fileName, StringComparison.OrdinalIgnoreCase))
874  {
875  // Already loaded, keep active
876  animSwap.IsActive = true;
877  return true;
878  }
879  else if (animLoadInfo.File.TryGet(out ContentPath contentPath) && animSwap.TemporaryAnimation.Path == contentPath)
880  {
881  // Already loaded, keep active
882  animSwap.IsActive = true;
883  return true;
884  }
885  else
886  {
887  if (animSwap.Priority >= animLoadInfo.Priority)
888  {
889  // If the priority of the current animation is higher than the new animation, just return and do nothing.
890  // 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.
891  return true;
892  }
893  else
894  {
895  // Override any previous animations of the same type.
896  tempAnimations.Remove(animType);
897  }
898  }
899  }
900  AnimationParams defaultAnimation = GetAnimationParamsFromType(animType);
901  if (defaultAnimation == null) { return false; }
902  if (!TryLoadAnimation(animType, animLoadInfo.File, out AnimationParams tempParams, throwErrors)) { return false; }
903  // Store the default animation, if not yet stored. There should always be just one of the same type.
904  defaultAnimations.TryAdd(animType, defaultAnimation);
905  tempAnimations.Add(animType, new AnimSwap(tempParams, animLoadInfo.Priority));
906  return true;
907  }
908 
909  private void UpdateTemporaryAnimations(float deltaTime)
910  {
911  if (tempAnimations.None()) { return; }
912  foreach ((AnimationType animationType, AnimSwap animSwap) in tempAnimations)
913  {
914  if (!animSwap.IsActive)
915  {
916  if (defaultAnimations.TryGetValue(animSwap.AnimationType, out AnimationParams defaultAnimation))
917  {
918  TrySwapAnimParams(defaultAnimation);
919  expiredAnimations.Add(animationType);
920  }
921  else
922  {
923  DebugConsole.ThrowError($"[AnimController] Failed to find the default animation parameters of type {animSwap.AnimationType}. Cannot swap back the default animations!");
924  tempAnimations.Clear();
925  }
926  }
927  }
928  foreach (AnimationType anim in expiredAnimations)
929  {
930  tempAnimations.Remove(anim);
931  }
932  expiredAnimations.Clear();
933  foreach (AnimSwap animSwap in tempAnimations.Values)
934  {
935  animSwap.Update(deltaTime);
936  }
937  }
938 
942  public bool TryLoadAnimation(AnimationType animationType, Either<string, ContentPath> file, out AnimationParams animParams, bool throwErrors)
943  {
944  animParams = null;
945  if (character.IsHumanoid && this is HumanoidAnimController humanAnimController)
946  {
947  switch (animationType)
948  {
949  case AnimationType.Walk:
950  humanAnimController.WalkParams = HumanWalkParams.GetAnimParams(character, file, throwErrors);
951  animParams = humanAnimController.WalkParams;
952  break;
953  case AnimationType.Run:
954  humanAnimController.RunParams = HumanRunParams.GetAnimParams(character, file, throwErrors);
955  animParams = humanAnimController.RunParams;
956  break;
957  case AnimationType.Crouch:
958  humanAnimController.HumanCrouchParams = HumanCrouchParams.GetAnimParams(character, file, throwErrors);
959  animParams = humanAnimController.HumanCrouchParams;
960  break;
961  case AnimationType.SwimSlow:
962  humanAnimController.SwimSlowParams = HumanSwimSlowParams.GetAnimParams(character, file, throwErrors);
963  animParams = humanAnimController.SwimSlowParams;
964  break;
965  case AnimationType.SwimFast:
966  humanAnimController.SwimFastParams = HumanSwimFastParams.GetAnimParams(character, file, throwErrors);
967  animParams = humanAnimController.SwimFastParams;
968  break;
969  default:
970  DebugConsole.ThrowError($"[AnimController] Animation of type {animationType} not implemented!");
971  break;
972  }
973  }
974  else
975  {
976  switch (animationType)
977  {
978  case AnimationType.Walk:
979  if (CanWalk)
980  {
982  animParams = character.AnimController.WalkParams;
983  }
984  break;
985  case AnimationType.Run:
986  if (CanWalk)
987  {
989  animParams = character.AnimController.RunParams;
990  }
991  break;
992  case AnimationType.SwimSlow:
995  break;
996  case AnimationType.SwimFast:
999  break;
1000  default:
1001  DebugConsole.ThrowError($"[AnimController] Animation of type {animationType} not implemented!");
1002  break;
1003  }
1004  }
1005 
1006  bool success = animParams != null;
1007  if (!file.TryGet(out string fileName))
1008  {
1009  if (file.TryGet(out ContentPath contentPath))
1010  {
1011  fileName = contentPath.Value;
1012  if (success)
1013  {
1014  success = contentPath == animParams.Path;
1015  }
1016  }
1017  }
1018  else
1019  {
1020  if (success)
1021  {
1022  success = animParams.FileNameWithoutExtension.Equals(fileName, StringComparison.OrdinalIgnoreCase);
1023  }
1024  }
1025  if (success)
1026  {
1027  DebugConsole.NewMessage($"Animation {fileName} successfully loaded for {character.DisplayName}", Color.LightGreen, debugOnly: true);
1028  }
1029  else if (throwErrors)
1030  {
1031  DebugConsole.ThrowError($"Animation {fileName} for {character.DisplayName} could not be loaded!");
1032  }
1033  return success;
1034  }
1035 
1039  protected bool TrySwapAnimParams(AnimationParams newParams)
1040  {
1041  AnimationType animationType = newParams.AnimationType;
1042  if (character.IsHumanoid && this is HumanoidAnimController humanAnimController)
1043  {
1044  switch (animationType)
1045  {
1046  case AnimationType.Walk:
1047  if (newParams is HumanWalkParams newWalkParams)
1048  {
1049  humanAnimController.WalkParams = newWalkParams;
1050  }
1051  return true;
1052  case AnimationType.Run:
1053  if (newParams is HumanRunParams newRunParams)
1054  {
1055  humanAnimController.HumanRunParams = newRunParams;
1056  }
1057  break;
1058  case AnimationType.Crouch:
1059  if (newParams is HumanCrouchParams newCrouchParams)
1060  {
1061  humanAnimController.HumanCrouchParams = newCrouchParams;
1062  }
1063  return true;
1064  case AnimationType.SwimSlow:
1065  if (newParams is HumanSwimSlowParams newSwimSlowParams)
1066  {
1067  humanAnimController.HumanSwimSlowParams = newSwimSlowParams;
1068  }
1069  return true;
1070  case AnimationType.SwimFast:
1071  if (newParams is HumanSwimFastParams newSwimFastParams)
1072  {
1073  humanAnimController.HumanSwimFastParams = newSwimFastParams;
1074  }
1075  return true;
1076  default:
1077  DebugConsole.ThrowError($"[AnimController] Animation of type {animationType} not implemented!");
1078  return false;
1079  }
1080  }
1081  else
1082  {
1083  switch (animationType)
1084  {
1085  case AnimationType.Walk:
1086  if (newParams is FishWalkParams walkParams)
1087  {
1088  character.AnimController.WalkParams = walkParams;
1089  }
1090  return true;
1091  case AnimationType.Run:
1092  if (newParams is FishRunParams runParams)
1093  {
1094  character.AnimController.RunParams = runParams;
1095  }
1096  return true;
1097  case AnimationType.SwimSlow:
1098  if (newParams is FishSwimSlowParams swimSlowParams)
1099  {
1100  character.AnimController.SwimSlowParams = swimSlowParams;
1101  }
1102  return true;
1103  case AnimationType.SwimFast:
1104  if (newParams is FishSwimFastParams swimFastParams)
1105  {
1106  character.AnimController.SwimFastParams = swimFastParams;
1107  }
1108  return true;
1109  default:
1110  DebugConsole.ThrowError($"[AnimController] Animation of type {animationType} not implemented!");
1111  break;
1112  }
1113  }
1114  return false;
1115  }
1116  }
1117 }
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 UpdateAnimations(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)
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
LimbJoint GetJointBetweenLimbs(LimbType limbTypeA, LimbType limbTypeB)
override? float TorsoPosition
void UpdateUseItem(bool allowMovement, Vector2 handWorldPos)
void LockFlipping(float time=0.2f)
abstract GroundedMovementParams WalkParams
bool IsMovingFast
Note: Presupposes that the slow speed is lower than the high speed. Otherwise will give invalid resul...
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
void SetInput(InputType inputType, bool hit, bool held)
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 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)
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 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)
Note that if there are multiple limbs of the same type, only the first (valid) limb is returned.
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)