Client LuaCsForBarotrauma
FishAnimController.cs
2 using FarseerPhysics;
3 using Microsoft.Xna.Framework;
4 using System;
5 using System.Linq;
7 
8 namespace Barotrauma
9 {
11  {
12  public override RagdollParams RagdollParams
13  {
14  get { return FishRagdollParams; }
15  protected set { FishRagdollParams = value as FishRagdollParams; }
16  }
17 
18  private FishRagdollParams _ragdollParams;
20  {
21  get
22  {
23  if (_ragdollParams == null)
24  {
26  }
27  return _ragdollParams;
28  }
29  protected set
30  {
31  _ragdollParams = value;
32  }
33  }
34 
35  private FishWalkParams _fishWalkParams;
37  {
38  get
39  {
40  if (_fishWalkParams == null)
41  {
43  }
44  return _fishWalkParams;
45  }
46  set { _fishWalkParams = value; }
47  }
48 
49  private FishRunParams _fishRunParams;
51  {
52  get
53  {
54  if (_fishRunParams == null)
55  {
57  }
58  return _fishRunParams;
59  }
60  set { _fishRunParams = value; }
61  }
62 
63  private FishSwimSlowParams _fishSwimSlowParams;
65  {
66  get
67  {
68  if (_fishSwimSlowParams == null)
69  {
70  _fishSwimSlowParams = FishSwimSlowParams.GetDefaultAnimParams(character);
71  }
72  return _fishSwimSlowParams;
73  }
74  set { _fishSwimSlowParams = value; }
75  }
76 
77  private FishSwimFastParams _fishSwimFastParams;
79  {
80  get
81  {
82  if (_fishSwimFastParams == null)
83  {
84  _fishSwimFastParams = FishSwimFastParams.GetDefaultAnimParams(character);
85  }
86  return _fishSwimFastParams;
87  }
88  set { _fishSwimFastParams = value; }
89  }
90 
92  public new FishGroundedParams CurrentGroundedParams => base.CurrentGroundedParams as FishGroundedParams;
93  public new FishSwimParams CurrentSwimParams => base.CurrentSwimParams as FishSwimParams;
94 
95  public float? TailAngle => GetValidOrNull(CurrentAnimationParams, CurrentFishAnimation?.TailAngleInRadians);
103 
105  {
106  get { return FishWalkParams; }
107  set { FishWalkParams = value as FishWalkParams; }
108  }
109 
111  {
112  get { return FishRunParams; }
113  set { FishRunParams = value as FishRunParams; }
114  }
115 
116  public override SwimParams SwimSlowParams
117  {
118  get { return FishSwimSlowParams; }
119  set { FishSwimSlowParams = value as FishSwimSlowParams; }
120  }
121 
122  public override SwimParams SwimFastParams
123  {
124  get { return FishSwimFastParams; }
125  set { FishSwimFastParams = value as FishSwimFastParams; }
126  }
127 
128  private float flipTimer, flipCooldown;
129 
130  public FishAnimController(Character character, string seed, FishRagdollParams ragdollParams = null) : base(character, seed, ragdollParams) { }
131 
132  protected override void UpdateAnim(float deltaTime)
133  {
134  //wait a bit for the ragdoll to "settle" (for joints to force the limbs to appropriate positions) before starting to animate
135  if (Timing.TotalTime - character.SpawnTime < 0.1f) { return; }
136  if (Frozen) { return; }
137  if (MainLimb == null)
138  {
139  ResetState();
140  return;
141  }
142  UpdateConstantTorque(deltaTime);
143  UpdateBlink(deltaTime);
144  var mainLimb = MainLimb;
145 
147 
148  if (!character.CanMove)
149  {
150  levitatingCollider = false;
151  Collider.FarseerBody.FixedRotation = false;
152  if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
153  {
154  Collider.Enabled = false;
155  Collider.LinearVelocity = mainLimb.LinearVelocity;
156  Collider.SetTransformIgnoreContacts(mainLimb.SimPosition, mainLimb.Rotation);
157  //reset pull joints to prevent the character from "hanging" mid-air if pull joints had been active when the character was still moving
158  //(except when dragging, then we need the pull joints)
159  if (!Draggable || character.SelectedBy == null)
160  {
161  ResetPullJoints();
162  }
163  }
164  if (character.IsDead && deathAnimTimer < deathAnimDuration)
165  {
166  deathAnimTimer += deltaTime;
167  UpdateDying(deltaTime);
168  }
169  else if (!InWater && !CanWalk && character.AllowInput)
170  {
171  //cannot walk but on dry land -> wiggle around
172  UpdateDying(deltaTime);
173  }
174  ResetState();
175  return;
176  }
177  else
178  {
179  deathAnimTimer = 0.0f;
180  }
181 
182  //re-enable collider
183  if (!Collider.Enabled)
184  {
185  var lowestLimb = FindLowestLimb();
186 
187  if (InWater)
188  {
190  }
191  else
192  {
193  Collider.SetTransform(new Vector2(
195  Math.Max(lowestLimb.SimPosition.Y + (Collider.Radius + Collider.Height / 2), Collider.SimPosition.Y)),
196  0.0f);
197  }
198  Collider.Enabled = true;
199  }
200 
201  ResetPullJoints();
202 
203  if (strongestImpact > 0.0f)
204  {
205  character.Stun = MathHelper.Clamp(strongestImpact * 0.5f, character.Stun, 5.0f);
206  strongestImpact = 0.0f;
207  }
208 
209  if (Aiming)
210  {
211  TargetMovement = TargetMovement.ClampLength(2);
212  }
213 
214  if (IsClimbing)
215  {
216  UpdateClimbing();
217  }
218 
219  if (inWater && !forceStanding)
220  {
221  Collider.FarseerBody.FixedRotation = false;
222  UpdateSineAnim(deltaTime);
223  }
224  else if (RagdollParams.CanWalk && (currentHull != null || forceStanding))
225  {
226  if (CurrentGroundedParams != null)
227  {
228  //rotate collider back upright
230  if (Math.Abs(MathUtils.GetShortestAngle(Collider.Rotation, standAngle)) > 0.001f)
231  {
232  Collider.AngularVelocity = MathUtils.GetShortestAngle(Collider.Rotation, standAngle) * 60.0f;
233  Collider.FarseerBody.FixedRotation = false;
234  }
235  else
236  {
237  Collider.FarseerBody.FixedRotation = true;
238  }
239  }
240  UpdateWalkAnim(deltaTime);
241  }
242  if (character.SelectedCharacter != null)
243  {
245  ResetState();
246  return;
247  }
249  {
250  ApplyTestPose();
251  }
252  //don't flip when simply physics is enabled
254  {
255  ResetState();
256  return;
257  }
258 
260  {
261  if (!inWater || (CurrentSwimParams != null && CurrentSwimParams.Mirror))
262  {
263  if (targetMovement.X > 0.1f && targetMovement.X > Math.Abs(targetMovement.Y) * 0.2f)
264  {
265  TargetDir = Direction.Right;
266  }
267  else if (targetMovement.X < -0.1f && targetMovement.X < -Math.Abs(targetMovement.Y) * 0.2f)
268  {
269  TargetDir = Direction.Left;
270  }
271  }
272  else
273  {
274  float rotation = MathHelper.WrapAngle(Collider.Rotation);
275  rotation = MathHelper.ToDegrees(rotation);
276  if (rotation < 0.0f)
277  {
278  rotation += 360;
279  }
280  if (rotation > 20 && rotation < 160)
281  {
282  TargetDir = Direction.Left;
283  }
284  else if (rotation > 200 && rotation < 340)
285  {
286  TargetDir = Direction.Right;
287  }
288  }
289  }
290 
291  if (!IsStuck && CurrentFishAnimation.Flip && character.AIController is not { CanFlip: false })
292  {
293  flipCooldown -= deltaTime;
294  if (TargetDir != Direction.None && TargetDir != dir)
295  {
296  flipTimer += deltaTime;
297  // Speed reductions are not taken into account here. It's intentional: an ai character cannot flip if it's heavily paralyzed (for example).
298  float requiredSpeed = CurrentAnimationParams.MovementSpeed / 2;
299  if (CurrentHull != null)
300  {
301  // Enemy movement speeds are halved inside submarines
302  requiredSpeed /= 2;
303  }
304  bool isMovingFastEnough = Math.Abs(MainLimb.LinearVelocity.X) > requiredSpeed;
305  bool isTryingToMoveHorizontally = Math.Abs(TargetMovement.X) > Math.Abs(TargetMovement.Y);
306  if ((flipTimer > CurrentFishAnimation.FlipDelay && flipCooldown <= 0.0f && ((isMovingFastEnough && isTryingToMoveHorizontally) || IsMovingBackwards))
308  {
309  Flip();
310  if (!inWater || (CurrentSwimParams != null && CurrentSwimParams.Mirror))
311  {
313  }
314  flipTimer = 0.0f;
315  flipCooldown = CurrentFishAnimation.FlipCooldown;
316  }
317  }
318  else
319  {
320  flipTimer = 0.0f;
321  }
322  }
323  ResetState();
324 
325  void ResetState()
326  {
327  wasAiming = aiming;
328  aiming = false;
330  aimingMelee = false;
331  }
332  }
333 
334  private bool CanDrag(Character target)
335  {
336  return Mass / target.Mass > 0.1f;
337  }
338 
339  private float eatTimer = 0.0f;
340 
341  public override void DragCharacter(Character target, float deltaTime)
342  {
343  if (target == null) { return; }
344  Limb mouthLimb = GetLimb(LimbType.Head);
345  if (mouthLimb == null) { return; }
346  if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
347  {
348  //stop dragging if there's something between the pull limb and the target
349  Vector2 sourceSimPos = SimplePhysicsEnabled ? character.SimPosition : mouthLimb.SimPosition;
350  Vector2 targetSimPos = target.SimPosition;
352  {
353  targetSimPos -= character.Submarine.SimPosition;
354  }
355  else if (character.Submarine == null && character.SelectedCharacter.Submarine != null)
356  {
358  }
359  var body = Submarine.CheckVisibility(sourceSimPos, targetSimPos, ignoreSubs: true);
360  if (body != null)
361  {
363  return;
364  }
365  }
366  if (Character.CanEat)
367  {
368  Vector2 mouthPos = SimplePhysicsEnabled ? character.SimPosition : GetMouthPosition() ?? Vector2.Zero;
369  Vector2 attackSimPosition = character.Submarine == null ? ConvertUnits.ToSimUnits(target.WorldPosition) : target.SimPosition;
370  Vector2 limbDiff = attackSimPosition - mouthPos;
371  float extent = Math.Max(mouthLimb.body.GetMaxExtent(), 1);
372  bool tooFar = character.InWater ? limbDiff.LengthSquared() > extent * extent : limbDiff.X > extent;
373  if (tooFar)
374  {
376  return;
377  }
378 
379  float dmg = character.Params.EatingSpeed;
380  float eatSpeed = dmg / ((float)Math.Sqrt(Math.Max(target.Mass, 1)) * 10);
381  eatTimer += deltaTime * eatSpeed;
382 
383  //pull the target character to the position of the mouth
384  //(+ make the force fluctuate to waggle the character a bit)
385  float dragForce = MathHelper.Clamp(eatSpeed * 10, 0, 40);
386  if (dragForce > 0.1f)
387  {
388  Vector2 targetPos = mouthPos;
389  if (target.Submarine != null && character.Submarine == null)
390  {
391  targetPos -= target.Submarine.SimPosition;
392  }
393  else if (target.Submarine == null && character.Submarine != null)
394  {
395  targetPos += character.Submarine.SimPosition;
396  }
397  target.AnimController.MainLimb.body.SmoothRotate(mouthLimb.Rotation, dragForce * 2);
399  {
400  target.AnimController.MainLimb.MoveToPos(targetPos, (float)(Math.Sin(eatTimer) + dragForce));
401  }
402  target.AnimController.Collider.MoveToPos(targetPos, (float)(Math.Sin(eatTimer) + dragForce));
403  }
404 
405  if (InWater)
406  {
407  //pull the character's mouth to the target character (again with a fluctuating force)
408  float pullStrength = (float)(Math.Sin(eatTimer) * Math.Max(Math.Sin(eatTimer * 0.5f), 0.0f));
409  mouthLimb.body.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f * pullStrength);
410  }
411  else
412  {
413  float force = (float)Math.Sin(eatTimer * 100) * mouthLimb.Mass;
414  mouthLimb.body.ApplyLinearImpulse(Vector2.UnitY * force * mouthLimb.Params.EatImpulse, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
415  mouthLimb.body.ApplyTorque(-force * mouthLimb.Params.EatTorque);
416  }
417 
418  var jaw = GetLimb(LimbType.Jaw);
419  if (jaw != null)
420  {
421  jaw.body.ApplyTorque(-(float)Math.Sin(eatTimer * 150) * jaw.Mass * 25);
422  }
423  character.ApplyStatusEffects(ActionType.OnEating, deltaTime);
424 
425  if (target.IsDead)
426  {
427  float particleFrequency = MathHelper.Clamp(eatSpeed / 2, 0.02f, 0.5f);
428  if (Rand.Value() < particleFrequency / 6)
429  {
430  target.AnimController.MainLimb.AddDamage(target.SimPosition, dmg, 0, 0, false);
431  }
432  if (Rand.Value() < particleFrequency)
433  {
434  target.AnimController.MainLimb.AddDamage(target.SimPosition, 0, dmg, 0, false);
435  }
436  if (eatTimer % 1.0f < 0.5f && (eatTimer - deltaTime * eatSpeed) % 1.0f > 0.5f)
437  {
438  static bool CanBeSevered(LimbJoint j) => !j.IsSevered && j.CanBeSevered && j.LimbA is { IsSevered: false } && j.LimbB is { IsSevered: false };
439  //keep severing joints until there is only one limb left
440  var nonSeveredJoints = target.AnimController.LimbJoints.Where(CanBeSevered);
441  if (nonSeveredJoints.None())
442  {
443  //small monsters don't eat the contents of the character's inventory
444  if (Mass < target.AnimController.Mass)
445  {
446  target.Inventory?.AllItemsMod.ForEach(it => it?.Drop(dropper: null));
447  }
448  //only one limb left, the character is now full eaten
451  {
452  enemyAi.PetBehavior?.OnEat(target);
453  }
455  }
456  else //sever a random joint
457  {
458  target.AnimController.SeverLimbJoint(nonSeveredJoints.GetRandomUnsynced());
459  }
460  }
461  }
462  }
463  }
464 
465  public bool Reverse;
466 
467  void UpdateSineAnim(float deltaTime)
468  {
469  if (CurrentSwimParams == null) { return; }
471  bool isMoving = movement.LengthSquared() > 0.00001f;
472  var mainLimb = MainLimb;
473  float t = 0.5f;
475  {
476  Vector2 forward = VectorExtensions.Forward(Collider.Rotation + MathHelper.PiOver2);
477  float dot = Vector2.Dot(forward, Vector2.Normalize(movement));
478  if (dot < 0)
479  {
480  // Reduce the linear movement speed when not facing the movement direction
481  t = MathHelper.Clamp((1 + dot) / 10, 0.01f, 0.1f);
482  }
483  }
484  if (Collider.BodyType == BodyType.Dynamic)
485  {
487  }
488  //limbs are disabled when simple physics is enabled, no need to move them
489  if (SimplePhysicsEnabled) { return; }
490  mainLimb.PullJointEnabled = true;
491 
493  {
494  WalkPos = MathHelper.SmoothStep(WalkPos, MathHelper.PiOver2, deltaTime * 5);
495  mainLimb.PullJointWorldAnchorB = Collider.SimPosition;
496  if (aiming)
497  {
498  Vector2 mousePos = ConvertUnits.ToSimUnits(character.CursorPosition);
499  Vector2 diff = (mousePos - (GetLimb(LimbType.Torso) ?? MainLimb).SimPosition) * Dir;
500  TargetMovement = new Vector2(0.0f, -0.1f);
501  float newRotation = MathHelper.WrapAngle(MathUtils.VectorToAngle(diff) - MathHelper.PiOver2 * Dir);
503  if (TorsoAngle.HasValue)
504  {
505  Limb torso = GetLimb(LimbType.Torso);
506  if (torso != null)
507  {
508  SmoothRotateWithoutWrapping(torso, newRotation + TorsoAngle.Value * Dir, mainLimb, TorsoTorque * 2);
509  }
510  }
511  }
512  }
513  else
514  {
515  Vector2 transformedMovement = Reverse ? -movement : movement;
516  float movementAngle = MathUtils.VectorToAngle(transformedMovement) - MathHelper.PiOver2;
517  float mainLimbAngle = 0;
518  if (mainLimb.type == LimbType.Torso && TorsoAngle.HasValue)
519  {
520  mainLimbAngle = TorsoAngle.Value;
521  }
522  else if (mainLimb.type == LimbType.Head && HeadAngle.HasValue)
523  {
524  mainLimbAngle = HeadAngle.Value;
525  }
526  mainLimbAngle *= Dir;
527  while (mainLimb.Rotation - (movementAngle + mainLimbAngle) > MathHelper.Pi)
528  {
529  movementAngle += MathHelper.TwoPi;
530  }
531  while (mainLimb.Rotation - (movementAngle + mainLimbAngle) < -MathHelper.Pi)
532  {
533  movementAngle -= MathHelper.TwoPi;
534  }
536  {
538  if (TorsoAngle.HasValue)
539  {
540  Limb torso = GetLimb(LimbType.Torso);
541  if (torso != null)
542  {
543  SmoothRotateWithoutWrapping(torso, movementAngle + TorsoAngle.Value * Dir, mainLimb, TorsoTorque);
544  }
545  }
546  if (HeadAngle.HasValue)
547  {
548  Limb head = GetLimb(LimbType.Head);
549  if (head != null)
550  {
551  SmoothRotateWithoutWrapping(head, movementAngle + HeadAngle.Value * Dir, mainLimb, HeadTorque);
552  }
553  }
554  if (TailAngle.HasValue)
555  {
556  bool isAngleApplied = false;
557  foreach (var limb in Limbs)
558  {
559  if (limb.IsSevered) { continue; }
560  if (!limb.Params.ApplyTailAngle) { continue; }
561  RotateTail(limb);
562  isAngleApplied = true;
563  }
564  if (!isAngleApplied)
565  {
566  RotateTail(GetLimb(LimbType.Tail));
567  }
568 
569  void RotateTail(Limb tail)
570  {
571  if (tail == null) { return; }
572  float? mainLimbTargetAngle = null;
573  if (mainLimb.type == LimbType.Torso)
574  {
575  mainLimbTargetAngle = TorsoAngle;
576  }
577  else if (mainLimb.type == LimbType.Head)
578  {
579  mainLimbTargetAngle = HeadAngle;
580  }
581  float torque = TailTorque;
582  float maxMultiplier = CurrentSwimParams.TailTorqueMultiplier;
583  if (mainLimbTargetAngle.HasValue && maxMultiplier > 1)
584  {
585  float diff = Math.Abs(mainLimb.Rotation - tail.Rotation);
586  float offset = Math.Abs(mainLimbTargetAngle.Value - TailAngle.Value);
587  torque *= MathHelper.Lerp(1, maxMultiplier, MathUtils.InverseLerp(0, MathHelper.PiOver2, diff - offset));
588  }
589  SmoothRotateWithoutWrapping(tail, movementAngle + TailAngle.Value * Dir, mainLimb, torque);
590  }
591  }
592  }
593  else
594  {
595  movementAngle = Dir > 0 ? -MathHelper.PiOver2 : MathHelper.PiOver2;
596  if (Reverse)
597  {
598  movementAngle = MathUtils.WrapAngleTwoPi(movementAngle - MathHelper.Pi);
599  }
600  if (mainLimb.type == LimbType.Head && HeadAngle.HasValue)
601  {
603  }
604  else if (mainLimb.type == LimbType.Torso && TorsoAngle.HasValue)
605  {
607  }
608  if (TorsoAngle.HasValue)
609  {
610  Limb torso = GetLimb(LimbType.Torso);
611  torso?.body.SmoothRotate(TorsoAngle.Value * Dir, TorsoTorque);
612  }
613  if (HeadAngle.HasValue)
614  {
615  Limb head = GetLimb(LimbType.Head);
616  head?.body.SmoothRotate(HeadAngle.Value * Dir, HeadTorque);
617  }
618  if (TailAngle.HasValue)
619  {
620  bool isAngleApplied = false;
621  foreach (var limb in Limbs)
622  {
623  if (limb.IsSevered) { continue; }
624  if (limb.type != LimbType.Tail) { continue; }
625  if (!limb.Params.ApplyTailAngle) { continue; }
626  RotateTail(limb);
627  isAngleApplied = true;
628  }
629  if (!isAngleApplied)
630  {
631  RotateTail(GetLimb(LimbType.Tail));
632  }
633 
634  void RotateTail(Limb tail)
635  {
636  if (tail != null)
637  {
638  tail.body.SmoothRotate(TailAngle.Value * Dir, TailTorque);
639  }
640  }
641  }
642  }
643 
644  var waveLength = Math.Abs(CurrentSwimParams.WaveLength * RagdollParams.JointScale);
645  var waveAmplitude = Math.Abs(CurrentSwimParams.WaveAmplitude * character.SpeedMultiplier);
646  if (waveLength > 0 && waveAmplitude > 0)
647  {
648  WalkPos -= transformedMovement.Length() / Math.Abs(waveLength);
649  WalkPos = MathUtils.WrapAngleTwoPi(WalkPos);
650  }
651 
652  foreach (var limb in Limbs)
653  {
654  if (limb.IsSevered) { continue; }
655  if (limb.type is LimbType.LeftFoot or LimbType.RightFoot)
656  {
657  if (CurrentSwimParams.FootAnglesInRadians.ContainsKey(limb.Params.ID))
658  {
659  SmoothRotateWithoutWrapping(limb, movementAngle + CurrentSwimParams.FootAnglesInRadians[limb.Params.ID] * Dir, mainLimb, FootTorque);
660  }
661  }
662  if (limb.type == LimbType.Tail || limb.Params.ApplySineMovement)
663  {
664  if (waveLength > 0 && waveAmplitude > 0)
665  {
666  float waveRotation = (float)Math.Sin(WalkPos * limb.Params.SineFrequencyMultiplier);
667  limb.body.ApplyTorque(waveRotation * limb.Mass * waveAmplitude * limb.Params.SineAmplitudeMultiplier);
668  }
669  }
670  if (limb.SteerForce <= 0.0f) { continue; }
671  if (!Collider.PhysEnabled) { continue; }
672  Vector2 pullPos = limb.PullJointWorldAnchorA;
673  limb.body.ApplyForce(movement * limb.SteerForce * limb.Mass * Math.Max(character.SpeedMultiplier, 1), pullPos);
674  }
675 
676  Vector2 mainLimbDiff = mainLimb.PullJointWorldAnchorB - mainLimb.SimPosition;
678  {
679  mainLimb.PullJointWorldAnchorB = Vector2.SmoothStep(
680  mainLimb.PullJointWorldAnchorB,
682  mainLimbDiff.LengthSquared() > 10.0f ? 1.0f : (float)Math.Abs(Math.Sin(WalkPos)));
683  }
684  else
685  {
686  //mainLimb.PullJointWorldAnchorB = Collider.SimPosition;
687  mainLimb.PullJointWorldAnchorB = Vector2.Lerp(
688  mainLimb.PullJointWorldAnchorB,
690  mainLimbDiff.LengthSquared() > 10.0f ? 1.0f : 0.5f);
691  }
692  }
693 
694  floorY = Limbs[0].SimPosition.Y;
695  }
696 
697  void UpdateWalkAnim(float deltaTime)
698  {
699  movement = MathUtils.SmoothStep(movement, TargetMovement, 0.2f);
700 
701  if (Collider.BodyType == BodyType.Dynamic)
702  {
703  Collider.LinearVelocity = new Vector2(
704  movement.X,
706  }
707 
708  //limbs are disabled when simple physics is enabled, no need to move them
709  if (SimplePhysicsEnabled) { return; }
710 
711  Vector2 colliderBottom = GetColliderBottom();
712 
713  float movementAngle = 0.0f;
714  var mainLimb = MainLimb;
715  float mainLimbAngle = (mainLimb.type == LimbType.Torso ? TorsoAngle ?? 0 : HeadAngle ?? 0) * Dir;
716  while (mainLimb.Rotation - (movementAngle + mainLimbAngle) > MathHelper.Pi)
717  {
718  movementAngle += MathHelper.TwoPi;
719  }
720  while (mainLimb.Rotation - (movementAngle + mainLimbAngle) < -MathHelper.Pi)
721  {
722  movementAngle -= MathHelper.TwoPi;
723  }
724 
725  float offset = MathHelper.Pi * CurrentGroundedParams.StepLiftOffset;
726  if (character.AnimController.Dir < 0)
727  {
728  offset += MathHelper.Pi * CurrentGroundedParams.StepLiftFrequency;
729  }
730  float stepLift = TargetMovement.X == 0.0f ? 0 :
732 
733  float limpAmount = character.GetLegPenalty();
734  if (limpAmount > 0)
735  {
736  float walkPosX = (float)Math.Cos(WalkPos);
737  //make the footpos oscillate when limping
738  limpAmount = Math.Max(Math.Abs(walkPosX) * limpAmount, 0.0f) * Math.Min(Math.Abs(TargetMovement.X), 0.3f) * Dir;
739  }
740 
741  Limb torso = GetLimb(LimbType.Torso);
742  if (torso != null)
743  {
744  if (TorsoAngle.HasValue)
745  {
746  SmoothRotateWithoutWrapping(torso, movementAngle + TorsoAngle.Value * Dir, mainLimb, TorsoTorque);
747  }
748  if (TorsoPosition.HasValue && TorsoMoveForce > 0.0f)
749  {
750  Vector2 pos = colliderBottom + new Vector2(limpAmount, TorsoPosition.Value + stepLift);
751 
752  if (torso != mainLimb)
753  {
754  pos.X = torso.SimPosition.X;
755  }
756 
757  torso.MoveToPos(pos, TorsoMoveForce);
758  torso.PullJointEnabled = true;
759  torso.PullJointWorldAnchorB = pos;
760  }
761  }
762 
763  Limb head = GetLimb(LimbType.Head);
764  if (head != null)
765  {
766  bool headFacingBackwards = false;
767  if (HeadAngle.HasValue && head != mainLimb)
768  {
769  SmoothRotateWithoutWrapping(head, movementAngle + HeadAngle.Value * Dir, mainLimb, HeadTorque);
770  if (Math.Sign(head.SimPosition.X - mainLimb.SimPosition.X) != Math.Sign(Dir))
771  {
772  headFacingBackwards = true;
773  }
774  }
775  if (HeadPosition.HasValue && HeadMoveForce > 0.0f && !headFacingBackwards)
776  {
777  Vector2 pos = colliderBottom + new Vector2(limpAmount, HeadPosition.Value + stepLift * CurrentGroundedParams.StepLiftHeadMultiplier);
778 
779  if (head != mainLimb)
780  {
781  pos.X = head.SimPosition.X;
782  }
783 
784  head.MoveToPos(pos, HeadMoveForce);
785  head.PullJointEnabled = true;
786  head.PullJointWorldAnchorB = pos;
787  }
788  }
789 
790  if (TailAngle.HasValue)
791  {
792  bool isAngleApplied = false;
793  foreach (var limb in Limbs)
794  {
795  if (limb.IsSevered) { continue; }
796  if (limb.type != LimbType.Tail) { continue; }
797  if (!limb.Params.ApplyTailAngle) { continue; }
798  RotateTail(limb);
799  isAngleApplied = true;
800  }
801  if (!isAngleApplied)
802  {
803  RotateTail(GetLimb(LimbType.Tail));
804  }
805 
806  void RotateTail(Limb tail)
807  {
808  if (tail != null)
809  {
810  SmoothRotateWithoutWrapping(tail, movementAngle + TailAngle.Value * Dir, mainLimb, TailTorque);
811  }
812  }
813  }
814 
815  float prevWalkPos = WalkPos;
816  WalkPos -= mainLimb.LinearVelocity.X * (CurrentAnimationParams.CycleSpeed / RagdollParams.JointScale / 100.0f);
817 
818  Vector2 transformedStepSize = Vector2.Zero;
819  if (Math.Abs(TargetMovement.X) > 0.01f)
820  {
821  transformedStepSize = new Vector2(
822  (float)Math.Cos(WalkPos) * StepSize.Value.X * 3.0f,
823  (float)Math.Sin(WalkPos) * StepSize.Value.Y * 2.0f);
824  }
825 
826  foreach (Limb limb in Limbs)
827  {
828  if (limb.IsSevered) { continue; }
829  switch (limb.type)
830  {
831  case LimbType.LeftFoot:
832  case LimbType.RightFoot:
833  Vector2 footPos = new Vector2(limb.SimPosition.X, colliderBottom.Y);
834 
835  if (limb.RefJointIndex > -1)
836  {
837  if (LimbJoints.Length <= limb.RefJointIndex)
838  {
839  DebugConsole.ThrowError($"Reference joint index {limb.RefJointIndex} is out of array. This is probably due to a missing joint. If you just deleted a joint, don't do that without first removing the reference joint indices from the limbs. If this is not the case, please ensure that you have defined the index to the right joint.");
840  }
841  else
842  {
843  footPos.X = LimbJoints[limb.RefJointIndex].WorldAnchorA.X;
844  }
845  }
846  footPos.X += limb.StepOffset.X * Dir;
847  footPos.Y += limb.StepOffset.Y;
848 
849  bool playFootstepSound = false;
850  if (limb.type == LimbType.LeftFoot)
851  {
852  if (Math.Sign(Math.Sin(prevWalkPos)) > 0 && Math.Sign(transformedStepSize.Y) < 0)
853  {
854  playFootstepSound = true;
855  }
856 
857  limb.DebugRefPos = footPos + Vector2.UnitX * movement.X * 0.1f;
858  limb.DebugTargetPos = footPos + new Vector2(
859  transformedStepSize.X + movement.X * 0.1f,
860  (transformedStepSize.Y > 0.0f) ? transformedStepSize.Y : 0.0f);
861  limb.MoveToPos(limb.DebugTargetPos, FootMoveForce);
862  }
863  else if (limb.type == LimbType.RightFoot)
864  {
865  if (Math.Sign(Math.Sin(prevWalkPos)) < 0 && Math.Sign(transformedStepSize.Y) > 0)
866  {
867  playFootstepSound = true;
868  }
869 
870  limb.DebugRefPos = footPos + Vector2.UnitX * movement.X * 0.1f;
871  limb.DebugTargetPos = footPos + new Vector2(
872  -transformedStepSize.X + movement.X * 0.1f,
873  (-transformedStepSize.Y > 0.0f) ? -transformedStepSize.Y : 0.0f);
874  limb.MoveToPos(limb.DebugTargetPos, FootMoveForce);
875  }
876 
877  if (playFootstepSound)
878  {
879 #if CLIENT
880  PlayImpactSound(limb);
881 #endif
882  }
883 
884  if (CurrentGroundedParams.FootAnglesInRadians.ContainsKey(limb.Params.ID))
885  {
886  SmoothRotateWithoutWrapping(limb,
887  movementAngle + CurrentGroundedParams.FootAnglesInRadians[limb.Params.ID] * Dir,
888  mainLimb, FootTorque);
889  }
890  break;
891  case LimbType.LeftLeg:
892  case LimbType.RightLeg:
893  if (Math.Abs(CurrentGroundedParams.LegTorque) > 0)
894  {
895  limb.body.ApplyTorque(limb.Mass * CurrentGroundedParams.LegTorque * Dir);
896  }
897  break;
898  }
899  }
900  }
901 
902  void UpdateDying(float deltaTime)
903  {
904  if (deathAnimDuration <= 0.0f) { return; }
905 
906  float noise = (PerlinNoise.GetPerlin(WalkPos * 0.002f, WalkPos * 0.003f) - 0.5f) * 5.0f;
907  float animStrength = (1.0f - deathAnimTimer / deathAnimDuration);
908 
909  Limb baseLimb = GetLimb(LimbType.Head);
910  //if head is the main limb, it technically can't be severed - the rest of the limbs are considered severed if the head gets cut off
911  if (baseLimb == MainLimb)
912  {
913  int connectedToHeadCount = GetConnectedLimbs(baseLimb).Count;
914  //if there's nothing connected to the head, don't make it wiggle by itself
915  if (connectedToHeadCount == 1) { baseLimb = null; }
916  Limb torso = GetLimb(LimbType.Torso, excludeSevered: false);
917  if (torso != null)
918  {
919  //if there are more limbs connected to the torso than to the head, make the torso wiggle instead
920  int connectedToTorsoCount = GetConnectedLimbs(torso).Count;
921  if (connectedToTorsoCount > connectedToHeadCount)
922  {
923  baseLimb = torso;
924  }
925  }
926  }
927  else if (baseLimb == null)
928  {
929  baseLimb = GetLimb(LimbType.Torso, excludeSevered: true);
930  if (baseLimb == null) { return; }
931  }
932 
933  var connectedToBaseLimb = GetConnectedLimbs(baseLimb);
934 
935  Limb tail = GetLimb(LimbType.Tail);
936  if (baseLimb != null) { baseLimb.body.ApplyTorque((float)(Math.Sqrt(baseLimb.Mass) * Dir * (Math.Sin(WalkPos) + noise)) * 30.0f * animStrength); }
937  if (tail != null && connectedToBaseLimb.Contains(tail)) { tail.body.ApplyTorque((float)(Math.Sqrt(tail.Mass) * -Dir * (Math.Sin(WalkPos) + noise)) * 30.0f * animStrength); }
938 
939  WalkPos += deltaTime * 10.0f * animStrength;
940 
941  Vector2 centerOfMass = GetCenterOfMass();
942 
943  foreach (Limb limb in Limbs)
944  {
945  if (!connectedToBaseLimb.Contains(limb)) { continue; }
946 #if CLIENT
947  if (limb.LightSource != null)
948  {
949  limb.LightSource.Color = Color.Lerp(limb.InitialLightSourceColor, Color.TransparentBlack, deathAnimTimer / deathAnimDuration);
950  if (limb.InitialLightSpriteAlpha.HasValue)
951  {
952  limb.LightSource.OverrideLightSpriteAlpha = MathHelper.Lerp(limb.InitialLightSpriteAlpha.Value, 0.0f, deathAnimTimer / deathAnimDuration);
953  }
954  }
955 #endif
956  if (limb.type == LimbType.Head || limb.type == LimbType.Tail || limb.IsSevered || !limb.body.Enabled) continue;
957  if (limb.Mass <= 0.0f)
958  {
959  string errorMsg = "Creature death animation error: invalid limb mass on character \"" + character.SpeciesName + "\" (type: " + limb.type + ", mass: " + limb.Mass + ")";
960  DebugConsole.ThrowError(errorMsg);
961  GameAnalyticsManager.AddErrorEventOnce("FishAnimController.UpdateDying:InvalidMass" + character.ID, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
962  deathAnimTimer = deathAnimDuration;
963  return;
964  }
965 
966  Vector2 diff = (centerOfMass - limb.SimPosition);
967  if (!MathUtils.IsValid(diff))
968  {
969  string errorMsg = "Creature death animation error: invalid diff (center of mass: " + centerOfMass + ", limb position: " + limb.SimPosition + ")";
970  DebugConsole.ThrowError(errorMsg);
971  GameAnalyticsManager.AddErrorEventOnce("FishAnimController.UpdateDying:InvalidDiff" + character.ID, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
972  deathAnimTimer = deathAnimDuration;
973  return;
974  }
975 
976  limb.body.ApplyForce(diff * (float)(Math.Sin(WalkPos) * Math.Sqrt(limb.Mass)) * 30.0f * animStrength, maxVelocity: 10.0f);
977  }
978  }
979 
980  private void SmoothRotateWithoutWrapping(Limb limb, float angle, Limb referenceLimb, float torque)
981  {
982  //make sure the angle "has the same number of revolutions" as the reference limb
983  //(e.g. we don't want to rotate the legs to 0 if the torso is at 360, because that'd blow up the hip joints)
984  angle = referenceLimb.body.WrapAngleToSameNumberOfRevolutions(angle);
985  limb?.body.SmoothRotate(angle, torque, wrapAngle: false);
986  }
987 
988  public override void Flip()
989  {
990  base.Flip();
991  foreach (Limb l in Limbs)
992  {
993  if (l.IsSevered) { continue; }
994  if (!l.DoesFlip) { continue; }
996  {
997  //horizontally aligned limbs need to be flipped 180 degrees
998  l.body.SetTransform(l.SimPosition, l.body.Rotation + MathHelper.Pi * Dir);
999  }
1000  //no need to do anything when flipping vertically oriented limbs
1001  //the sprite gets flipped horizontally, which does the job
1002  }
1003  }
1004 
1005  public void Mirror(bool lerp = true)
1006  {
1007  Vector2 centerOfMass = GetCenterOfMass();
1008 
1009  foreach (Limb l in Limbs)
1010  {
1011  if (l.IsSevered) { continue; }
1012 
1013  float rotation = l.body.Rotation;
1014  if (l.DoesMirror)
1015  {
1017  {
1018  //horizontally oriented sprites can be mirrored by rotating 180 deg and inverting the angle
1019  rotation = -(l.body.Rotation + MathHelper.Pi);
1020  }
1021  else
1022  {
1023  //vertically oriented limbs can be mirrored by inverting the angle (neutral angle is straight upwards)
1024  rotation = -l.body.Rotation;
1025  }
1026  }
1027 
1029  centerOfMass,
1030  new Vector2(centerOfMass.X - (l.SimPosition.X - centerOfMass.X), l.SimPosition.Y),
1031  rotation,
1032  lerp);
1033 
1034  l.body.PositionSmoothingFactor = 0.8f;
1035 
1036  }
1037  if (character.SelectedCharacter != null && CanDrag(character.SelectedCharacter))
1038  {
1039  float diff = character.SelectedCharacter.SimPosition.X - centerOfMass.X;
1040  if (diff < 100.0f)
1041  {
1043  new Vector2(centerOfMass.X - diff, character.SelectedCharacter.SimPosition.Y), lerp);
1044  }
1045  }
1046  }
1047  }
1048 }
override? float HeadPosition
float? GetValidOrNull(AnimationParams p, float? v)
virtual ? Vector2 StepSize
AnimationParams? CurrentAnimationParams
override? float TorsoAngle
void UpdateBlink(float deltaTime)
void UpdateConstantTorque(float deltaTime)
override? float TorsoPosition
bool IsRemotelyControlled
Is the character controlled remotely (either by another player, or a server-side AIController)
void ApplyStatusEffects(ActionType actionType, float deltaTime)
float SpeedMultiplier
Can be used to modify the character's speed via StatusEffects
bool IsRemotePlayer
Is the character controlled by another human player (should always be false in single player)
static EntitySpawner Spawner
Definition: Entity.cs:31
virtual Vector2 WorldPosition
Definition: Entity.cs:49
Submarine Submarine
Definition: Entity.cs:53
readonly ushort ID
Unique, but non-persistent identifier. Stays the same if the entities are created in the exactly same...
Definition: Entity.cs:43
double SpawnTime
Definition: Entity.cs:79
override void UpdateAnim(float deltaTime)
FishSwimFastParams FishSwimFastParams
override SwimParams SwimFastParams
override GroundedMovementParams RunParams
new FishSwimParams CurrentSwimParams
FishSwimSlowParams FishSwimSlowParams
override SwimParams SwimSlowParams
override void DragCharacter(Character target, float deltaTime)
override GroundedMovementParams WalkParams
new FishGroundedParams CurrentGroundedParams
override RagdollParams RagdollParams
FishAnimController(Character character, string seed, FishRagdollParams ragdollParams=null)
Dictionary< int, float > FootAnglesInRadians
Key = limb id, value = angle in radians
static FishRagdollParams GetDefaultRagdollParams(Character character)
static FishRunParams GetDefaultAnimParams(Character character)
static FishSwimFastParams GetDefaultAnimParams(Character character)
Dictionary< int, float > FootAnglesInRadians
Key = limb id, value = angle in radians
static FishSwimSlowParams GetDefaultAnimParams(Character character)
static FishWalkParams GetDefaultAnimParams(Character character)
static NetworkMember NetworkMember
Definition: GameMain.cs:190
IEnumerable< Item > AllItemsMod
All items contained in the inventory. Allows modifying the contents of the inventory while being enum...
AttackResult AddDamage(Vector2 simPosition, float damage, float bleedingDamage, float burnDamage, bool playSound)
void MoveToPos(Vector2 pos, float force, bool pullFromCenter=false)
void ApplyForce(Vector2 force, float maxVelocity=NetConfig.MaxPhysicsBodyVelocity)
bool SetTransform(Vector2 simPosition, float rotation, bool setPrevTransform=true)
void MoveToPos(Vector2 simPosition, float force, Vector2? pullPos=null)
bool SetTransformIgnoreContacts(Vector2 simPosition, float rotation, bool setPrevTransform=true)
void SmoothRotate(float targetRotation, float force=10.0f, bool wrapAngle=true)
Rotate the body towards the target rotation in the "shortest direction", taking into account the curr...
void ResetPullJoints(Func< Limb, bool > condition=null)
bool IsHangingWithRope
Is hanging to something with a rope, so that can reel towards it. Currently only possible in water.
Limb GetLimb(LimbType limbType, bool excludeSevered=true, bool excludeLimbsWithSecondaryType=false, bool useSecondaryType=false)
Note that if there are multiple limbs of the same type, only the first (valid) limb is returned.
void SetPosition(Vector2 simPosition, bool lerp=false, bool ignorePlatforms=true, bool forceMainLimbToCollider=false, bool moveLatchers=true)
void TrySetLimbPosition(Limb limb, Vector2 original, Vector2 simPosition, float rotation, bool lerp=false, bool ignorePlatforms=true)
static Body CheckVisibility(Vector2 rayStart, Vector2 rayEnd, bool ignoreLevel=false, bool ignoreSubs=false, bool ignoreSensors=true, bool ignoreDisabledWalls=true, bool ignoreBranches=true, Predicate< Fixture > blocksVisibilityPredicate=null)
Check visibility between two points (in sim units).
ActionType
ActionTypes define when a StatusEffect is executed.
Definition: Enums.cs:26