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  var mainLimb = MainLimb;
143 
145 
146  if (!character.CanMove)
147  {
148  levitatingCollider = false;
149  Collider.FarseerBody.FixedRotation = false;
150  if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
151  {
152  Collider.Enabled = false;
153  Collider.LinearVelocity = mainLimb.LinearVelocity;
154  Collider.SetTransformIgnoreContacts(mainLimb.SimPosition, mainLimb.Rotation);
155  //reset pull joints to prevent the character from "hanging" mid-air if pull joints had been active when the character was still moving
156  //(except when dragging, then we need the pull joints)
157  if (!Draggable || character.SelectedBy == null)
158  {
159  ResetPullJoints();
160  }
161  }
162  if (character.IsDead && deathAnimTimer < deathAnimDuration)
163  {
164  deathAnimTimer += deltaTime;
165  UpdateDying(deltaTime);
166  }
167  else if (!InWater && !CanWalk && character.AllowInput)
168  {
169  //cannot walk but on dry land -> wiggle around
170  UpdateDying(deltaTime);
171  }
172  ResetState();
173  return;
174  }
175  else
176  {
177  deathAnimTimer = 0.0f;
178  }
179 
180  //re-enable collider
181  if (!Collider.Enabled)
182  {
183  var lowestLimb = FindLowestLimb();
184 
185  if (InWater)
186  {
188  }
189  else
190  {
191  Collider.SetTransform(new Vector2(
193  Math.Max(lowestLimb.SimPosition.Y + (Collider.Radius + Collider.Height / 2), Collider.SimPosition.Y)),
194  0.0f);
195  }
196  Collider.Enabled = true;
197  }
198 
199  ResetPullJoints();
200 
201  if (strongestImpact > 0.0f)
202  {
203  character.Stun = MathHelper.Clamp(strongestImpact * 0.5f, character.Stun, 5.0f);
204  strongestImpact = 0.0f;
205  }
206 
207  if (Aiming)
208  {
209  TargetMovement = TargetMovement.ClampLength(2);
210  }
211 
212  if (inWater && !forceStanding)
213  {
214  Collider.FarseerBody.FixedRotation = false;
215  UpdateSineAnim(deltaTime);
216  }
217  else if (RagdollParams.CanWalk && (currentHull != null || forceStanding))
218  {
219  if (CurrentGroundedParams != null)
220  {
221  //rotate collider back upright
223  if (Math.Abs(MathUtils.GetShortestAngle(Collider.Rotation, standAngle)) > 0.001f)
224  {
225  Collider.AngularVelocity = MathUtils.GetShortestAngle(Collider.Rotation, standAngle) * 60.0f;
226  Collider.FarseerBody.FixedRotation = false;
227  }
228  else
229  {
230  Collider.FarseerBody.FixedRotation = true;
231  }
232  }
233  UpdateWalkAnim(deltaTime);
234  }
235  if (character.SelectedCharacter != null)
236  {
238  ResetState();
239  return;
240  }
242  {
243  ApplyTestPose();
244  }
245  //don't flip when simply physics is enabled
247  {
248  ResetState();
249  return;
250  }
251 
253  {
254  if (!inWater || (CurrentSwimParams != null && CurrentSwimParams.Mirror))
255  {
256  if (targetMovement.X > 0.1f && targetMovement.X > Math.Abs(targetMovement.Y) * 0.2f)
257  {
258  TargetDir = Direction.Right;
259  }
260  else if (targetMovement.X < -0.1f && targetMovement.X < -Math.Abs(targetMovement.Y) * 0.2f)
261  {
262  TargetDir = Direction.Left;
263  }
264  }
265  else
266  {
267  float rotation = MathHelper.WrapAngle(Collider.Rotation);
268  rotation = MathHelper.ToDegrees(rotation);
269  if (rotation < 0.0f)
270  {
271  rotation += 360;
272  }
273  if (rotation > 20 && rotation < 160)
274  {
275  TargetDir = Direction.Left;
276  }
277  else if (rotation > 200 && rotation < 340)
278  {
279  TargetDir = Direction.Right;
280  }
281  }
282  }
283 
284  if (!IsStuck && CurrentFishAnimation.Flip && character.AIController is not { CanFlip: false })
285  {
286  flipCooldown -= deltaTime;
287  if (TargetDir != Direction.None && TargetDir != dir)
288  {
289  flipTimer += deltaTime;
290  // Speed reductions are not taken into account here. It's intentional: an ai character cannot flip if it's heavily paralyzed (for example).
291  float requiredSpeed = CurrentAnimationParams.MovementSpeed / 2;
292  if (CurrentHull != null)
293  {
294  // Enemy movement speeds are halved inside submarines
295  requiredSpeed /= 2;
296  }
297  bool isMovingFastEnough = Math.Abs(MainLimb.LinearVelocity.X) > requiredSpeed;
298  bool isTryingToMoveHorizontally = Math.Abs(TargetMovement.X) > Math.Abs(TargetMovement.Y);
299  if ((flipTimer > CurrentFishAnimation.FlipDelay && flipCooldown <= 0.0f && ((isMovingFastEnough && isTryingToMoveHorizontally) || IsMovingBackwards))
301  {
302  Flip();
303  if (!inWater || (CurrentSwimParams != null && CurrentSwimParams.Mirror))
304  {
306  }
307  flipTimer = 0.0f;
308  flipCooldown = CurrentFishAnimation.FlipCooldown;
309  }
310  }
311  else
312  {
313  flipTimer = 0.0f;
314  }
315  }
316  ResetState();
317 
318  void ResetState()
319  {
320  wasAiming = aiming;
321  aiming = false;
323  aimingMelee = false;
324  }
325  }
326 
327  private bool CanDrag(Character target)
328  {
329  return Mass / target.Mass > 0.1f;
330  }
331 
332  private float eatTimer = 0.0f;
333 
334  public override void DragCharacter(Character target, float deltaTime)
335  {
336  if (target == null) { return; }
337  Limb mouthLimb = GetLimb(LimbType.Head);
338  if (mouthLimb == null) { return; }
339 
340  if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
341  {
342  //stop dragging if there's something between the pull limb and the target
343  Vector2 sourceSimPos = SimplePhysicsEnabled ? character.SimPosition : mouthLimb.SimPosition;
344  Vector2 targetSimPos = target.SimPosition;
346  {
347  targetSimPos -= character.Submarine.SimPosition;
348  }
349  else if (character.Submarine == null && character.SelectedCharacter.Submarine != null)
350  {
352  }
353  var body = Submarine.CheckVisibility(sourceSimPos, targetSimPos, ignoreSubs: true);
354  if (body != null)
355  {
357  return;
358  }
359  }
360 
361  float dmg = character.Params.EatingSpeed;
362  float eatSpeed = dmg / ((float)Math.Sqrt(Math.Max(target.Mass, 1)) * 10);
363  eatTimer += deltaTime * eatSpeed;
364 
365  Vector2 mouthPos = SimplePhysicsEnabled ? character.SimPosition : GetMouthPosition().Value;
366  Vector2 attackSimPosition = character.Submarine == null ? ConvertUnits.ToSimUnits(target.WorldPosition) : target.SimPosition;
367 
368  Vector2 limbDiff = attackSimPosition - mouthPos;
369  float extent = Math.Max(mouthLimb.body.GetMaxExtent(), 1);
370  bool tooFar = character.InWater ? limbDiff.LengthSquared() > extent * extent : limbDiff.X > extent;
371  if (tooFar)
372  {
374  }
375  else
376  {
377  //pull the target character to the position of the mouth
378  //(+ make the force fluctuate to waggle the character a bit)
379  float dragForce = MathHelper.Clamp(eatSpeed * 10, 0, 40);
380  if (dragForce > 0.1f)
381  {
382  Vector2 targetPos = mouthPos;
383  if (target.Submarine != null && character.Submarine == null)
384  {
385  targetPos -= target.Submarine.SimPosition;
386  }
387  else if (target.Submarine == null && character.Submarine != null)
388  {
389  targetPos += character.Submarine.SimPosition;
390  }
391  target.AnimController.MainLimb.body.SmoothRotate(mouthLimb.Rotation, dragForce * 2);
393  {
394  target.AnimController.MainLimb.MoveToPos(targetPos, (float)(Math.Sin(eatTimer) + dragForce));
395  }
396  target.AnimController.Collider.MoveToPos(targetPos, (float)(Math.Sin(eatTimer) + dragForce));
397  }
398 
399  if (InWater)
400  {
401  //pull the character's mouth to the target character (again with a fluctuating force)
402  float pullStrength = (float)(Math.Sin(eatTimer) * Math.Max(Math.Sin(eatTimer * 0.5f), 0.0f));
403  mouthLimb.body.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f * pullStrength);
404  }
405  else
406  {
407  float force = (float)Math.Sin(eatTimer * 100) * mouthLimb.Mass;
408  mouthLimb.body.ApplyLinearImpulse(Vector2.UnitY * force * 2, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
409  mouthLimb.body.ApplyTorque(-force * 50);
410  }
411 
412  if (Character.CanEat && target.IsDead)
413  {
414  var jaw = GetLimb(LimbType.Jaw);
415  if (jaw != null)
416  {
417  jaw.body.ApplyTorque(-(float)Math.Sin(eatTimer * 150) * jaw.Mass * 25);
418  }
419 
420  character.ApplyStatusEffects(ActionType.OnEating, deltaTime);
421 
422  float particleFrequency = MathHelper.Clamp(eatSpeed / 2, 0.02f, 0.5f);
423  if (Rand.Value() < particleFrequency / 6)
424  {
425  target.AnimController.MainLimb.AddDamage(target.SimPosition, dmg, 0, 0, false);
426  }
427  if (Rand.Value() < particleFrequency)
428  {
429  target.AnimController.MainLimb.AddDamage(target.SimPosition, 0, dmg, 0, false);
430  }
431  if (eatTimer % 1.0f < 0.5f && (eatTimer - deltaTime * eatSpeed) % 1.0f > 0.5f)
432  {
433  static bool CanBeSevered(LimbJoint j) => !j.IsSevered && j.CanBeSevered && j.LimbA != null && !j.LimbA.IsSevered && j.LimbB != null && !j.LimbB.IsSevered;
434  //keep severing joints until there is only one limb left
435  var nonSeveredJoints = target.AnimController.LimbJoints.Where(CanBeSevered);
436  if (nonSeveredJoints.None())
437  {
438  //small monsters don't eat the contents of the character's inventory
439  if (Mass < target.AnimController.Mass)
440  {
441  target.Inventory?.AllItemsMod.ForEach(it => it?.Drop(dropper: null));
442  }
443 
444  //only one limb left, the character is now full eaten
446 
448  {
449  enemyAi.PetBehavior?.OnEat(target);
450  }
451 
453  }
454  else //sever a random joint
455  {
456  target.AnimController.SeverLimbJoint(nonSeveredJoints.GetRandomUnsynced());
457  }
458  }
459  }
460  }
461  }
462 
463  public bool reverse;
464 
465  void UpdateSineAnim(float deltaTime)
466  {
467  if (CurrentSwimParams == null) { return; }
469  bool isMoving = movement.LengthSquared() > 0.00001f;
470  var mainLimb = MainLimb;
471  float t = 0.5f;
473  {
474  Vector2 forward = VectorExtensions.Forward(Collider.Rotation + MathHelper.PiOver2);
475  float dot = Vector2.Dot(forward, Vector2.Normalize(movement));
476  if (dot < 0)
477  {
478  // Reduce the linear movement speed when not facing the movement direction
479  t = MathHelper.Clamp((1 + dot) / 10, 0.01f, 0.1f);
480  }
481  }
482  if (Collider.BodyType == BodyType.Dynamic)
483  {
485  }
486  //limbs are disabled when simple physics is enabled, no need to move them
487  if (SimplePhysicsEnabled) { return; }
488  mainLimb.PullJointEnabled = true;
489 
491  {
492  WalkPos = MathHelper.SmoothStep(WalkPos, MathHelper.PiOver2, deltaTime * 5);
493  mainLimb.PullJointWorldAnchorB = Collider.SimPosition;
494  if (aiming)
495  {
496  Vector2 mousePos = ConvertUnits.ToSimUnits(character.CursorPosition);
497  Vector2 diff = (mousePos - (GetLimb(LimbType.Torso) ?? MainLimb).SimPosition) * Dir;
498  TargetMovement = new Vector2(0.0f, -0.1f);
499  float newRotation = MathHelper.WrapAngle(MathUtils.VectorToAngle(diff) - MathHelper.PiOver2 * Dir);
501  if (TorsoAngle.HasValue)
502  {
503  Limb torso = GetLimb(LimbType.Torso);
504  if (torso != null)
505  {
506  SmoothRotateWithoutWrapping(torso, newRotation + TorsoAngle.Value * Dir, mainLimb, TorsoTorque * 2);
507  }
508  }
509  }
510  }
511  else
512  {
513  Vector2 transformedMovement = reverse ? -movement : movement;
514  float movementAngle = MathUtils.VectorToAngle(transformedMovement) - MathHelper.PiOver2;
515  float mainLimbAngle = 0;
516  if (mainLimb.type == LimbType.Torso && TorsoAngle.HasValue)
517  {
518  mainLimbAngle = TorsoAngle.Value;
519  }
520  else if (mainLimb.type == LimbType.Head && HeadAngle.HasValue)
521  {
522  mainLimbAngle = HeadAngle.Value;
523  }
524  mainLimbAngle *= Dir;
525  while (mainLimb.Rotation - (movementAngle + mainLimbAngle) > MathHelper.Pi)
526  {
527  movementAngle += MathHelper.TwoPi;
528  }
529  while (mainLimb.Rotation - (movementAngle + mainLimbAngle) < -MathHelper.Pi)
530  {
531  movementAngle -= MathHelper.TwoPi;
532  }
534  {
536  if (TorsoAngle.HasValue)
537  {
538  Limb torso = GetLimb(LimbType.Torso);
539  if (torso != null)
540  {
541  SmoothRotateWithoutWrapping(torso, movementAngle + TorsoAngle.Value * Dir, mainLimb, TorsoTorque);
542  }
543  }
544  if (HeadAngle.HasValue)
545  {
546  Limb head = GetLimb(LimbType.Head);
547  if (head != null)
548  {
549  SmoothRotateWithoutWrapping(head, movementAngle + HeadAngle.Value * Dir, mainLimb, HeadTorque);
550  }
551  }
552  if (TailAngle.HasValue)
553  {
554  bool isAngleApplied = false;
555  foreach (var limb in Limbs)
556  {
557  if (limb.IsSevered) { continue; }
558  if (limb.type != LimbType.Tail) { continue; }
559  if (!limb.Params.ApplyTailAngle) { continue; }
560  RotateTail(limb);
561  isAngleApplied = true;
562  }
563  if (!isAngleApplied)
564  {
565  RotateTail(GetLimb(LimbType.Tail));
566  }
567 
568  void RotateTail(Limb tail)
569  {
570  if (tail == null) { return; }
571  float? mainLimbTargetAngle = null;
572  if (mainLimb.type == LimbType.Torso)
573  {
574  mainLimbTargetAngle = TorsoAngle;
575  }
576  else if (mainLimb.type == LimbType.Head)
577  {
578  mainLimbTargetAngle = HeadAngle;
579  }
580  float torque = TailTorque;
581  float maxMultiplier = CurrentSwimParams.TailTorqueMultiplier;
582  if (mainLimbTargetAngle.HasValue && maxMultiplier > 1)
583  {
584  float diff = Math.Abs(mainLimb.Rotation - tail.Rotation);
585  float offset = Math.Abs(mainLimbTargetAngle.Value - TailAngle.Value);
586  torque *= MathHelper.Lerp(1, maxMultiplier, MathUtils.InverseLerp(0, MathHelper.PiOver2, diff - offset));
587  }
588  SmoothRotateWithoutWrapping(tail, movementAngle + TailAngle.Value * Dir, mainLimb, torque);
589  }
590  }
591  }
592  else
593  {
594  movementAngle = Dir > 0 ? -MathHelper.PiOver2 : MathHelper.PiOver2;
595  if (reverse)
596  {
597  movementAngle = MathUtils.WrapAngleTwoPi(movementAngle - MathHelper.Pi);
598  }
599  if (mainLimb.type == LimbType.Head && HeadAngle.HasValue)
600  {
602  }
603  else if (mainLimb.type == LimbType.Torso && TorsoAngle.HasValue)
604  {
606  }
607  if (TorsoAngle.HasValue)
608  {
609  Limb torso = GetLimb(LimbType.Torso);
610  torso?.body.SmoothRotate(TorsoAngle.Value * Dir, TorsoTorque);
611  }
612  if (HeadAngle.HasValue)
613  {
614  Limb head = GetLimb(LimbType.Head);
615  head?.body.SmoothRotate(HeadAngle.Value * Dir, HeadTorque);
616  }
617  if (TailAngle.HasValue)
618  {
619  bool isAngleApplied = false;
620  foreach (var limb in Limbs)
621  {
622  if (limb.IsSevered) { continue; }
623  if (limb.type != LimbType.Tail) { continue; }
624  if (!limb.Params.ApplyTailAngle) { continue; }
625  RotateTail(limb);
626  isAngleApplied = true;
627  }
628  if (!isAngleApplied)
629  {
630  RotateTail(GetLimb(LimbType.Tail));
631  }
632 
633  void RotateTail(Limb tail)
634  {
635  if (tail != null)
636  {
637  tail.body.SmoothRotate(TailAngle.Value * Dir, TailTorque);
638  }
639  }
640  }
641  }
642 
643  var waveLength = Math.Abs(CurrentSwimParams.WaveLength * RagdollParams.JointScale);
644  var waveAmplitude = Math.Abs(CurrentSwimParams.WaveAmplitude * character.SpeedMultiplier);
645  if (waveLength > 0 && waveAmplitude > 0)
646  {
647  WalkPos -= transformedMovement.Length() / Math.Abs(waveLength);
648  WalkPos = MathUtils.WrapAngleTwoPi(WalkPos);
649  }
650 
651  foreach (var limb in Limbs)
652  {
653  if (limb.IsSevered) { continue; }
654  switch (limb.type)
655  {
656  case LimbType.LeftFoot:
657  case LimbType.RightFoot:
658  if (CurrentSwimParams.FootAnglesInRadians.ContainsKey(limb.Params.ID))
659  {
660  SmoothRotateWithoutWrapping(limb, movementAngle + CurrentSwimParams.FootAnglesInRadians[limb.Params.ID] * Dir, mainLimb, FootTorque);
661  }
662  break;
663  case LimbType.Tail:
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  break;
670  }
671  }
672 
673  for (int i = 0; i < Limbs.Length; i++)
674  {
675  var limb = Limbs[i];
676  if (limb.IsSevered) { continue; }
677  if (limb.SteerForce <= 0.0f) { continue; }
678  if (!Collider.PhysEnabled) { continue; }
679  Vector2 pullPos = limb.PullJointWorldAnchorA;
680  limb.body.ApplyForce(movement * limb.SteerForce * limb.Mass * Math.Max(character.SpeedMultiplier, 1), pullPos);
681  }
682 
683  Vector2 mainLimbDiff = mainLimb.PullJointWorldAnchorB - mainLimb.SimPosition;
685  {
686  mainLimb.PullJointWorldAnchorB = Vector2.SmoothStep(
687  mainLimb.PullJointWorldAnchorB,
689  mainLimbDiff.LengthSquared() > 10.0f ? 1.0f : (float)Math.Abs(Math.Sin(WalkPos)));
690  }
691  else
692  {
693  //mainLimb.PullJointWorldAnchorB = Collider.SimPosition;
694  mainLimb.PullJointWorldAnchorB = Vector2.Lerp(
695  mainLimb.PullJointWorldAnchorB,
697  mainLimbDiff.LengthSquared() > 10.0f ? 1.0f : 0.5f);
698  }
699  }
700 
701  foreach (var limb in Limbs)
702  {
703  if (limb.IsSevered) { continue; }
704  if (Math.Abs(limb.Params.ConstantTorque) > 0)
705  {
706  float movementFactor = Math.Max(character.AnimController.Collider.LinearVelocity.Length() * 0.5f, 1);
707  limb.body.SmoothRotate(MainLimb.Rotation + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Mass * limb.Params.ConstantTorque * movementFactor, wrapAngle: true);
708  }
709  if (limb.Params.BlinkFrequency > 0)
710  {
711  limb.UpdateBlink(deltaTime, MainLimb.Rotation);
712  }
713  }
714 
715  floorY = Limbs[0].SimPosition.Y;
716  }
717 
718  void UpdateWalkAnim(float deltaTime)
719  {
720  movement = MathUtils.SmoothStep(movement, TargetMovement, 0.2f);
721 
722  if (Collider.BodyType == BodyType.Dynamic)
723  {
724  Collider.LinearVelocity = new Vector2(
725  movement.X,
727  }
728 
729  //limbs are disabled when simple physics is enabled, no need to move them
730  if (SimplePhysicsEnabled) { return; }
731 
732  Vector2 colliderBottom = GetColliderBottom();
733 
734  float movementAngle = 0.0f;
735  var mainLimb = MainLimb;
736  float mainLimbAngle = (mainLimb.type == LimbType.Torso ? TorsoAngle ?? 0 : HeadAngle ?? 0) * Dir;
737  while (mainLimb.Rotation - (movementAngle + mainLimbAngle) > MathHelper.Pi)
738  {
739  movementAngle += MathHelper.TwoPi;
740  }
741  while (mainLimb.Rotation - (movementAngle + mainLimbAngle) < -MathHelper.Pi)
742  {
743  movementAngle -= MathHelper.TwoPi;
744  }
745 
746  float offset = MathHelper.Pi * CurrentGroundedParams.StepLiftOffset;
748  {
749  offset *= Dir;
750  }
751  float stepLift = TargetMovement.X == 0.0f ? 0 :
753 
754  float limpAmount = character.GetLegPenalty();
755  if (limpAmount > 0)
756  {
757  float walkPosX = (float)Math.Cos(WalkPos);
758  //make the footpos oscillate when limping
759  limpAmount = Math.Max(Math.Abs(walkPosX) * limpAmount, 0.0f) * Math.Min(Math.Abs(TargetMovement.X), 0.3f) * Dir;
760  }
761 
762  Limb torso = GetLimb(LimbType.Torso);
763  if (torso != null)
764  {
765  if (TorsoAngle.HasValue)
766  {
767  SmoothRotateWithoutWrapping(torso, movementAngle + TorsoAngle.Value * Dir, mainLimb, TorsoTorque);
768  }
769  if (TorsoPosition.HasValue && TorsoMoveForce > 0.0f)
770  {
771  Vector2 pos = colliderBottom + new Vector2(limpAmount, TorsoPosition.Value + stepLift);
772 
773  if (torso != mainLimb)
774  {
775  pos.X = torso.SimPosition.X;
776  }
777 
778  torso.MoveToPos(pos, TorsoMoveForce);
779  torso.PullJointEnabled = true;
780  torso.PullJointWorldAnchorB = pos;
781  }
782  }
783 
784  Limb head = GetLimb(LimbType.Head);
785  if (head != null)
786  {
787  bool headFacingBackwards = false;
788  if (HeadAngle.HasValue && head != mainLimb)
789  {
790  SmoothRotateWithoutWrapping(head, movementAngle + HeadAngle.Value * Dir, mainLimb, HeadTorque);
791  if (Math.Sign(head.SimPosition.X - mainLimb.SimPosition.X) != Math.Sign(Dir))
792  {
793  headFacingBackwards = true;
794  }
795  }
796  if (HeadPosition.HasValue && HeadMoveForce > 0.0f && !headFacingBackwards)
797  {
798  Vector2 pos = colliderBottom + new Vector2(limpAmount, HeadPosition.Value + stepLift * CurrentGroundedParams.StepLiftHeadMultiplier);
799 
800  if (head != mainLimb)
801  {
802  pos.X = head.SimPosition.X;
803  }
804 
805  head.MoveToPos(pos, HeadMoveForce);
806  head.PullJointEnabled = true;
807  head.PullJointWorldAnchorB = pos;
808  }
809  }
810 
811  if (TailAngle.HasValue)
812  {
813  bool isAngleApplied = false;
814  foreach (var limb in Limbs)
815  {
816  if (limb.IsSevered) { continue; }
817  if (limb.type != LimbType.Tail) { continue; }
818  if (!limb.Params.ApplyTailAngle) { continue; }
819  RotateTail(limb);
820  isAngleApplied = true;
821  }
822  if (!isAngleApplied)
823  {
824  RotateTail(GetLimb(LimbType.Tail));
825  }
826 
827  void RotateTail(Limb tail)
828  {
829  if (tail != null)
830  {
831  SmoothRotateWithoutWrapping(tail, movementAngle + TailAngle.Value * Dir, mainLimb, TailTorque);
832  }
833  }
834  }
835 
836  float prevWalkPos = WalkPos;
837  WalkPos -= mainLimb.LinearVelocity.X * (CurrentAnimationParams.CycleSpeed / RagdollParams.JointScale / 100.0f);
838 
839  Vector2 transformedStepSize = Vector2.Zero;
840  if (Math.Abs(TargetMovement.X) > 0.01f)
841  {
842  transformedStepSize = new Vector2(
843  (float)Math.Cos(WalkPos) * StepSize.Value.X * 3.0f,
844  (float)Math.Sin(WalkPos) * StepSize.Value.Y * 2.0f);
845  }
846 
847  foreach (Limb limb in Limbs)
848  {
849  if (limb.IsSevered) { continue; }
850  if (Math.Abs(limb.Params.ConstantTorque) > 0)
851  {
852  limb.body.SmoothRotate(MainLimb.Rotation + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Mass * limb.Params.ConstantTorque, wrapAngle: true);
853  }
854  if (limb.Params.BlinkFrequency > 0 && !limb.Params.OnlyBlinkInWater)
855  {
856  limb.UpdateBlink(deltaTime, MainLimb.Rotation);
857  }
858  switch (limb.type)
859  {
860  case LimbType.LeftFoot:
861  case LimbType.RightFoot:
862  Vector2 footPos = new Vector2(limb.SimPosition.X, colliderBottom.Y);
863 
864  if (limb.RefJointIndex > -1)
865  {
866  if (LimbJoints.Length <= limb.RefJointIndex)
867  {
868  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.");
869  }
870  else
871  {
872  footPos.X = LimbJoints[limb.RefJointIndex].WorldAnchorA.X;
873  }
874  }
875  footPos.X += limb.StepOffset.X * Dir;
876  footPos.Y += limb.StepOffset.Y;
877 
878  bool playFootstepSound = false;
879  if (limb.type == LimbType.LeftFoot)
880  {
881  if (Math.Sign(Math.Sin(prevWalkPos)) > 0 && Math.Sign(transformedStepSize.Y) < 0)
882  {
883  playFootstepSound = true;
884  }
885 
886  limb.DebugRefPos = footPos + Vector2.UnitX * movement.X * 0.1f;
887  limb.DebugTargetPos = footPos + new Vector2(
888  transformedStepSize.X + movement.X * 0.1f,
889  (transformedStepSize.Y > 0.0f) ? transformedStepSize.Y : 0.0f);
890  limb.MoveToPos(limb.DebugTargetPos, FootMoveForce);
891  }
892  else if (limb.type == LimbType.RightFoot)
893  {
894  if (Math.Sign(Math.Sin(prevWalkPos)) < 0 && Math.Sign(transformedStepSize.Y) > 0)
895  {
896  playFootstepSound = true;
897  }
898 
899  limb.DebugRefPos = footPos + Vector2.UnitX * movement.X * 0.1f;
900  limb.DebugTargetPos = footPos + new Vector2(
901  -transformedStepSize.X + movement.X * 0.1f,
902  (-transformedStepSize.Y > 0.0f) ? -transformedStepSize.Y : 0.0f);
903  limb.MoveToPos(limb.DebugTargetPos, FootMoveForce);
904  }
905 
906  if (playFootstepSound)
907  {
908 #if CLIENT
909  PlayImpactSound(limb);
910 #endif
911  }
912 
913  if (CurrentGroundedParams.FootAnglesInRadians.ContainsKey(limb.Params.ID))
914  {
915  SmoothRotateWithoutWrapping(limb,
916  movementAngle + CurrentGroundedParams.FootAnglesInRadians[limb.Params.ID] * Dir,
917  mainLimb, FootTorque);
918  }
919  break;
920  case LimbType.LeftLeg:
921  case LimbType.RightLeg:
922  if (Math.Abs(CurrentGroundedParams.LegTorque) > 0)
923  {
924  limb.body.ApplyTorque(limb.Mass * CurrentGroundedParams.LegTorque * Dir);
925  }
926  break;
927  }
928  }
929  }
930 
931  void UpdateDying(float deltaTime)
932  {
933  if (deathAnimDuration <= 0.0f) { return; }
934 
935  float noise = (PerlinNoise.GetPerlin(WalkPos * 0.002f, WalkPos * 0.003f) - 0.5f) * 5.0f;
936  float animStrength = (1.0f - deathAnimTimer / deathAnimDuration);
937 
938  Limb baseLimb = GetLimb(LimbType.Head);
939  //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
940  if (baseLimb == MainLimb)
941  {
942  int connectedToHeadCount = GetConnectedLimbs(baseLimb).Count;
943  //if there's nothing connected to the head, don't make it wiggle by itself
944  if (connectedToHeadCount == 1) { baseLimb = null; }
945  Limb torso = GetLimb(LimbType.Torso, excludeSevered: false);
946  if (torso != null)
947  {
948  //if there are more limbs connected to the torso than to the head, make the torso wiggle instead
949  int connectedToTorsoCount = GetConnectedLimbs(torso).Count;
950  if (connectedToTorsoCount > connectedToHeadCount)
951  {
952  baseLimb = torso;
953  }
954  }
955  }
956  else if (baseLimb == null)
957  {
958  baseLimb = GetLimb(LimbType.Torso, excludeSevered: true);
959  if (baseLimb == null) { return; }
960  }
961 
962  var connectedToBaseLimb = GetConnectedLimbs(baseLimb);
963 
964  Limb tail = GetLimb(LimbType.Tail);
965  if (baseLimb != null) { baseLimb.body.ApplyTorque((float)(Math.Sqrt(baseLimb.Mass) * Dir * (Math.Sin(WalkPos) + noise)) * 30.0f * animStrength); }
966  if (tail != null && connectedToBaseLimb.Contains(tail)) { tail.body.ApplyTorque((float)(Math.Sqrt(tail.Mass) * -Dir * (Math.Sin(WalkPos) + noise)) * 30.0f * animStrength); }
967 
968  WalkPos += deltaTime * 10.0f * animStrength;
969 
970  Vector2 centerOfMass = GetCenterOfMass();
971 
972  foreach (Limb limb in Limbs)
973  {
974  if (!connectedToBaseLimb.Contains(limb)) { continue; }
975 #if CLIENT
976  if (limb.LightSource != null)
977  {
978  limb.LightSource.Color = Color.Lerp(limb.InitialLightSourceColor, Color.TransparentBlack, deathAnimTimer / deathAnimDuration);
979  if (limb.InitialLightSpriteAlpha.HasValue)
980  {
981  limb.LightSource.OverrideLightSpriteAlpha = MathHelper.Lerp(limb.InitialLightSpriteAlpha.Value, 0.0f, deathAnimTimer / deathAnimDuration);
982  }
983  }
984 #endif
985  if (limb.type == LimbType.Head || limb.type == LimbType.Tail || limb.IsSevered || !limb.body.Enabled) continue;
986  if (limb.Mass <= 0.0f)
987  {
988  string errorMsg = "Creature death animation error: invalid limb mass on character \"" + character.SpeciesName + "\" (type: " + limb.type + ", mass: " + limb.Mass + ")";
989  DebugConsole.ThrowError(errorMsg);
990  GameAnalyticsManager.AddErrorEventOnce("FishAnimController.UpdateDying:InvalidMass" + character.ID, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
991  deathAnimTimer = deathAnimDuration;
992  return;
993  }
994 
995  Vector2 diff = (centerOfMass - limb.SimPosition);
996  if (!MathUtils.IsValid(diff))
997  {
998  string errorMsg = "Creature death animation error: invalid diff (center of mass: " + centerOfMass + ", limb position: " + limb.SimPosition + ")";
999  DebugConsole.ThrowError(errorMsg);
1000  GameAnalyticsManager.AddErrorEventOnce("FishAnimController.UpdateDying:InvalidDiff" + character.ID, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
1001  deathAnimTimer = deathAnimDuration;
1002  return;
1003  }
1004 
1005  limb.body.ApplyForce(diff * (float)(Math.Sin(WalkPos) * Math.Sqrt(limb.Mass)) * 30.0f * animStrength, maxVelocity: 10.0f);
1006  }
1007  }
1008 
1009  private void SmoothRotateWithoutWrapping(Limb limb, float angle, Limb referenceLimb, float torque)
1010  {
1011  //make sure the angle "has the same number of revolutions" as the reference limb
1012  //(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)
1013  angle = referenceLimb.body.WrapAngleToSameNumberOfRevolutions(angle);
1014  limb?.body.SmoothRotate(angle, torque, wrapAngle: false);
1015  }
1016 
1017  public override void Flip()
1018  {
1019  base.Flip();
1020  foreach (Limb l in Limbs)
1021  {
1022  if (l.IsSevered) { continue; }
1023  if (!l.DoesFlip) { continue; }
1025  {
1026  //horizontally aligned limbs need to be flipped 180 degrees
1027  l.body.SetTransform(l.SimPosition, l.body.Rotation + MathHelper.Pi * Dir);
1028  }
1029  //no need to do anything when flipping vertically oriented limbs
1030  //the sprite gets flipped horizontally, which does the job
1031  }
1032  }
1033 
1034  public void Mirror(bool lerp = true)
1035  {
1036  Vector2 centerOfMass = GetCenterOfMass();
1037 
1038  foreach (Limb l in Limbs)
1039  {
1040  if (l.IsSevered) { continue; }
1041 
1042  float rotation = l.body.Rotation;
1043  if (l.DoesMirror)
1044  {
1046  {
1047  //horizontally oriented sprites can be mirrored by rotating 180 deg and inverting the angle
1048  rotation = -(l.body.Rotation + MathHelper.Pi);
1049  }
1050  else
1051  {
1052  //vertically oriented limbs can be mirrored by inverting the angle (neutral angle is straight upwards)
1053  rotation = -l.body.Rotation;
1054  }
1055  }
1056 
1058  centerOfMass,
1059  new Vector2(centerOfMass.X - (l.SimPosition.X - centerOfMass.X), l.SimPosition.Y),
1060  rotation,
1061  lerp);
1062 
1063  l.body.PositionSmoothingFactor = 0.8f;
1064 
1065  }
1066  if (character.SelectedCharacter != null && CanDrag(character.SelectedCharacter))
1067  {
1068  float diff = character.SelectedCharacter.SimPosition.X - centerOfMass.X;
1069  if (diff < 100.0f)
1070  {
1072  new Vector2(centerOfMass.X - diff, character.SelectedCharacter.SimPosition.Y), lerp);
1073  }
1074  }
1075  }
1076  }
1077 }
override? float HeadPosition
float? GetValidOrNull(AnimationParams p, float? v)
virtual ? Vector2 StepSize
AnimationParams? CurrentAnimationParams
override? float TorsoAngle
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.
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)
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.
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:19