Client LuaCsForBarotrauma
BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs
5 using FarseerPhysics;
6 using FarseerPhysics.Dynamics;
7 using Microsoft.Xna.Framework;
8 using Microsoft.Xna.Framework.Graphics;
9 using System;
10 using System.Collections.Generic;
11 using System.Linq;
12 
13 namespace Barotrauma
14 {
15  abstract partial class Ragdoll
16  {
17  public HashSet<SpriteDeformation> SpriteDeformations { get; protected set; } = new HashSet<SpriteDeformation>();
18 
23 
24  partial void UpdateNetPlayerPositionProjSpecific(float deltaTime, float lowestSubPos)
25  {
27  {
28  //remove states without a timestamp (there may still be ID-based states
29  //in the list when the controlled character switches to timestamp-based interpolation)
30  character.MemState.RemoveAll(m => m.Timestamp == 0.0f);
31 
32  //use simple interpolation for other players' characters and characters that can't move
33  if (character.MemState.Count > 0)
34  {
35  CharacterStateInfo serverPos = character.MemState.Last();
36  if (!character.isSynced)
37  {
38  SetPosition(serverPos.Position, lerp: false);
39  Collider.LinearVelocity = Vector2.Zero;
40  character.MemLocalState.Clear();
41  character.LastNetworkUpdateID = serverPos.ID;
42  character.isSynced = true;
43  return;
44  }
45 
46  if (character.MemState[0].SelectedCharacter == null || character.MemState[0].SelectedCharacter.Removed)
47  {
49  }
50  else if (character.MemState[0].SelectedCharacter != null)
51  {
52  character.SelectCharacter(character.MemState[0].SelectedCharacter);
53  }
54 
55  if (character.MemState[0].SelectedItem == null || character.MemState[0].SelectedItem.Removed)
56  {
57  character.SelectedItem = null;
58  }
59  else if (character.SelectedItem != character.MemState[0].SelectedItem)
60  {
61  foreach (var ic in character.MemState[0].SelectedItem.Components)
62  {
63  if (ic.CanBeSelected)
64  {
65  ic.Select(character);
66  }
67  }
68  character.SelectedItem = character.MemState[0].SelectedItem;
69  }
70 
71  if (character.MemState[0].SelectedSecondaryItem == null || character.MemState[0].SelectedSecondaryItem.Removed)
72  {
74  }
75  else if (character.SelectedSecondaryItem != character.MemState[0].SelectedSecondaryItem)
76  {
77  foreach (var ic in character.MemState[0].SelectedSecondaryItem.Components)
78  {
79  if (ic.CanBeSelected)
80  {
81  ic.Select(character);
82  }
83  }
84  character.SelectedSecondaryItem = character.MemState[0].SelectedSecondaryItem;
85  }
86 
87  if (character.MemState[0].Animation == AnimController.Animation.CPR)
88  {
90  }
92  {
94  }
95 
96  Vector2 newVelocity = Collider.LinearVelocity;
97  Vector2 newPosition = Collider.SimPosition;
98  float newRotation = Collider.Rotation;
99  float newAngularVelocity = Collider.AngularVelocity;
100  Collider.CorrectPosition(character.MemState, out newPosition, out newVelocity, out newRotation, out newAngularVelocity);
101 
102  if (Collider.BodyType == BodyType.Dynamic)
103  {
104  newVelocity = newVelocity.ClampLength(100.0f);
105  if (!MathUtils.IsValid(newVelocity)) { newVelocity = Vector2.Zero; }
106  overrideTargetMovement = newVelocity.LengthSquared() > 0.01f ? newVelocity : Vector2.Zero;
107  Collider.LinearVelocity = newVelocity;
108  Collider.AngularVelocity = newAngularVelocity;
109  }
110 
111  float distSqrd = Vector2.DistanceSquared(newPosition, Collider.SimPosition);
112  float errorTolerance = character.CanMove && (!character.IsRagdolled || character.AnimController.IsHangingWithRope) ? 0.01f : 0.2f;
113  if (distSqrd > errorTolerance)
114  {
115  if (distSqrd > 10.0f || !character.CanMove)
116  {
117  Collider.TargetRotation = newRotation;
118  if (distSqrd > 10.0f)
119  {
120  //teleported very far - see if we need to move to another sub
121  Hull serverHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(newPosition), CurrentHull, newPosition.Y < lowestSubPos);
122  if (currentHull != null && serverHull != null && serverHull.Submarine != currentHull.Submarine)
123  {
124  character.Submarine = serverHull.Submarine;
125  character.CurrentHull = CurrentHull = serverHull;
126  }
127  }
128  SetPosition(newPosition, lerp: distSqrd < 5.0f, ignorePlatforms: false);
129  }
130  else
131  {
132  Collider.TargetRotation = newRotation;
133  Collider.TargetPosition = newPosition;
135  }
136  }
137 
138  //immobilized characters can't correct their position using AnimController movement
139  // -> we need to correct it manually
140  if (!character.CanMove)
141  {
142  float mainLimbDistSqrd = Vector2.DistanceSquared(MainLimb.PullJointWorldAnchorA, Collider.SimPosition);
143  float mainLimbErrorTolerance = 0.1f;
144  //if the main limb is roughly at the correct position and the collider isn't moving (much at least),
145  //don't attempt to correct the position.
146  if (mainLimbDistSqrd > mainLimbErrorTolerance || Collider.LinearVelocity.LengthSquared() > 0.05f)
147  {
149  MainLimb.PullJointEnabled = true;
150  MainLimb.body.LinearVelocity = newVelocity;
151  }
152  }
153  }
154  character.MemLocalState.Clear();
155  }
156  else
157  {
158  //remove states with a timestamp (there may still timestamp-based states
159  //in the list if the controlled character switches from timestamp-based interpolation to ID-based)
160  character.MemState.RemoveAll(m => m.Timestamp > 0.0f);
161 
162  for (int i = 0; i < character.MemLocalState.Count; i++)
163  {
164  if (character.Submarine == null)
165  {
166  //transform in-sub coordinates to outside coordinates
167  if (character.MemLocalState[i].Position.Y > lowestSubPos)
168  {
169  character.MemLocalState[i].TransformInToOutside();
170  }
171  }
172  else if (currentHull?.Submarine != null)
173  {
174  //transform outside coordinates to in-sub coordinates
175  if (character.MemLocalState[i].Position.Y < lowestSubPos)
176  {
177  character.MemLocalState[i].TransformOutToInside(currentHull.Submarine);
178  }
179  }
180  }
181 
182  if (character.MemState.Count < 1) return;
183 
184  overrideTargetMovement = Vector2.Zero;
185 
186  CharacterStateInfo serverPos = character.MemState.Last();
187 
188  if (!character.isSynced)
189  {
190  SetPosition(serverPos.Position, lerp: false);
191  Collider.LinearVelocity = Vector2.Zero;
192  character.MemLocalState.Clear();
193  character.LastNetworkUpdateID = serverPos.ID;
194  character.isSynced = true;
195  return;
196  }
197 
198  int localPosIndex = character.MemLocalState.FindIndex(m => m.ID == serverPos.ID);
199  if (localPosIndex > -1)
200  {
201  CharacterStateInfo localPos = character.MemLocalState[localPosIndex];
202 
203  //the entity we're interacting with doesn't match the server's
204  if (localPos.SelectedCharacter != serverPos.SelectedCharacter)
205  {
206  if (serverPos.SelectedCharacter == null || serverPos.SelectedCharacter.Removed)
207  {
209  }
210  else if (serverPos.SelectedCharacter != null)
211  {
213  }
214  }
215  if (localPos.SelectedItem != serverPos.SelectedItem)
216  {
217  if (serverPos.SelectedItem == null || serverPos.SelectedItem.Removed)
218  {
219  character.SelectedItem = null;
220  }
221  else if (character.SelectedItem != serverPos.SelectedItem)
222  {
223  serverPos.SelectedItem.TryInteract(character, ignoreRequiredItems: true, forceSelectKey: true);
224  character.SelectedItem = serverPos.SelectedItem;
225  }
226  }
227  if (localPos.SelectedSecondaryItem != serverPos.SelectedSecondaryItem)
228  {
229  if (serverPos.SelectedSecondaryItem == null || serverPos.SelectedSecondaryItem.Removed)
230  {
232  }
233  else if (character.SelectedSecondaryItem != serverPos.SelectedSecondaryItem)
234  {
235  serverPos.SelectedSecondaryItem.TryInteract(character, ignoreRequiredItems: true, forceSelectKey: true);
236  character.SelectedSecondaryItem = serverPos.SelectedSecondaryItem;
237  }
238  }
239 
240  if (localPos.Animation != serverPos.Animation)
241  {
242  if (serverPos.Animation == AnimController.Animation.CPR)
243  {
245  }
247  {
249  }
250  }
251 
252  Hull serverHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(serverPos.Position), character.CurrentHull, serverPos.Position.Y < lowestSubPos);
253  Hull clientHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(localPos.Position), serverHull, localPos.Position.Y < lowestSubPos);
254 
255  if (serverHull != null && clientHull != null && serverHull.Submarine != clientHull.Submarine)
256  {
257  //hull subs don't match => teleport the camera to the other sub
258  character.Submarine = serverHull.Submarine;
259  character.CurrentHull = CurrentHull = serverHull;
260  SetPosition(serverPos.Position);
261  character.MemLocalState.Clear();
262  }
263  else
264  {
265  Vector2 positionError = serverPos.Position - localPos.Position;
266  float rotationError = serverPos.Rotation.HasValue && localPos.Rotation.HasValue ?
267  serverPos.Rotation.Value - localPos.Rotation.Value :
268  0.0f;
269 
270  for (int i = localPosIndex; i < character.MemLocalState.Count; i++)
271  {
272  Hull pointHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(character.MemLocalState[i].Position), clientHull, character.MemLocalState[i].Position.Y < lowestSubPos);
273  if (pointHull != clientHull && ((pointHull == null) || (clientHull == null) || (pointHull.Submarine == clientHull.Submarine))) break;
274  character.MemLocalState[i].Translate(positionError, rotationError);
275  }
276 
277  float errorMagnitude = positionError.Length();
278  if (errorMagnitude > 0.5f)
279  {
280  character.MemLocalState.Clear();
281  SetPosition(serverPos.Position, lerp: true, ignorePlatforms: false);
282  }
283  else if (errorMagnitude > 0.01f)
284  {
285  Collider.TargetPosition = Collider.SimPosition + positionError;
286  Collider.TargetRotation = Collider.Rotation + rotationError;
287  Collider.MoveToTargetPosition(lerp: true);
288  }
289  }
290 
291  }
292 
293  if (character.MemLocalState.Count > 120) character.MemLocalState.RemoveRange(0, character.MemLocalState.Count - 120);
294  character.MemState.Clear();
295  }
296  }
297 
298  partial void ImpactProjSpecific(float impact, Body body)
299  {
300  float volume = MathHelper.Clamp(impact - 3.0f, 0.5f, 1.0f);
301 
302  if (body.UserData is Limb limb && character.Stun <= 0f)
303  {
304  if (impact > 3.0f) { PlayImpactSound(limb); }
305  }
306  else if (body.UserData is Limb || body == Collider.FarseerBody)
307  {
309  {
310  SoundPlayer.PlayDamageSound("LimbBlunt", strongestImpact, Collider);
311  }
312  }
314  {
315  GameMain.GameScreen.Cam.Shake = Math.Min(Math.Max(strongestImpact, GameMain.GameScreen.Cam.Shake), 3.0f);
316  }
317  }
318 
319  public void PlayImpactSound(Limb limb)
320  {
321  limb.LastImpactSoundTime = (float)Timing.TotalTime;
322  if (!string.IsNullOrWhiteSpace(limb.HitSoundTag))
323  {
324  bool inWater = limb.InWater;
325  if (character.CurrentHull != null &&
327  limb.SimPosition.Y < ConvertUnits.ToSimUnits(character.CurrentHull.Rect.Y - character.CurrentHull.Rect.Height) + limb.body.GetMaxExtent())
328  {
329  inWater = true;
330  }
331  SoundPlayer.PlaySound(inWater ? "footstep_water" : limb.HitSoundTag, limb.WorldPosition, hullGuess: character.CurrentHull);
332  }
333  foreach (WearableSprite wearable in limb.WearingItems)
334  {
335  if (limb.type == wearable.Limb && !string.IsNullOrWhiteSpace(wearable.Sound))
336  {
337  SoundPlayer.PlaySound(wearable.Sound, limb.WorldPosition, hullGuess: character.CurrentHull);
338  }
339  }
340  }
341 
342  partial void Splash(Limb limb, Hull limbHull)
343  {
344  //create a splash particle
345  for (int i = 0; i < MathHelper.Clamp(Math.Abs(limb.LinearVelocity.Y), 1.0f, 5.0f); i++)
346  {
347  var splash = GameMain.ParticleManager.CreateParticle("watersplash",
348  new Vector2(limb.WorldPosition.X, limbHull.WorldSurface),
349  new Vector2(0.0f, Math.Abs(-limb.LinearVelocity.Y * 20.0f)) + Rand.Vector(Math.Abs(limb.LinearVelocity.Y * 10)),
350  Rand.Range(0.0f, MathHelper.TwoPi), limbHull);
351 
352  if (splash != null)
353  {
354  splash.Size *= MathHelper.Clamp(Math.Abs(limb.LinearVelocity.Y) * 0.1f, 1.0f, 2.0f);
355  }
356  }
357 
358  GameMain.ParticleManager.CreateParticle("bubbles",
359  new Vector2(limb.WorldPosition.X, limbHull.WorldSurface),
360  limb.LinearVelocity * 0.001f,
361  0.0f, limbHull);
362 
363  //if the Character dropped into water, create a wave
364  if (limb.LinearVelocity.Y < 0.0f)
365  {
366  if (splashSoundTimer <= 0.0f)
367  {
368  SoundPlayer.PlaySplashSound(limb.WorldPosition, Math.Abs(limb.LinearVelocity.Y) + Rand.Range(-5.0f, 0.0f));
369  splashSoundTimer = 0.5f;
370  }
371 
372  //+ some extra bubbles to follow the character underwater
373  GameMain.ParticleManager.CreateParticle("bubbles",
374  new Vector2(limb.WorldPosition.X, limbHull.WorldSurface),
375  limb.LinearVelocity * 10.0f,
376  0.0f, limbHull);
377  }
378  }
379 
380  partial void SetupDrawOrder()
381  {
382  //make sure every character gets drawn at a distinct "layer"
383  //(instead of having some of the limbs appear behind and some in front of other characters)
384  float startDepth = 0.1f;
385  float increment = 0.001f;
386  foreach (Character otherCharacter in Character.CharacterList)
387  {
388  if (otherCharacter == character) { continue; }
389  startDepth += increment;
390  }
391  //make sure each limb has a distinct depth value
392  List<Limb> depthSortedLimbs = Limbs.OrderBy(l => l.DefaultSpriteDepth).ToList();
393  foreach (Limb limb in Limbs)
394  {
395  var sprite = limb.GetActiveSprite();
396  if (sprite == null) { continue; }
397  sprite.Depth = startDepth + depthSortedLimbs.IndexOf(limb) * 0.00001f;
398  foreach (var conditionalSprite in limb.ConditionalSprites)
399  {
400  if (conditionalSprite.Exclusive)
401  {
402  conditionalSprite.ActiveSprite.Depth = sprite.Depth;
403  }
404  }
405  }
406  foreach (Limb limb in Limbs)
407  {
408  if (limb.ActiveSprite == null) { continue; }
409  if (limb.Params.InheritLimbDepth == LimbType.None) { continue; }
410  var matchingLimb = GetLimb(limb.Params.InheritLimbDepth);
411  if (matchingLimb != null)
412  {
413  limb.ActiveSprite.Depth = matchingLimb.ActiveSprite.Depth - 0.0000001f;
414  }
415  }
416 
417  depthSortedLimbs.Reverse();
418  inversedLimbDrawOrder = depthSortedLimbs.ToArray();
419  }
420 
421  partial void UpdateProjSpecific(float deltaTime, Camera cam)
422  {
423  if (!character.IsVisible) { return; }
424 
425  LimbJoints.ForEach(j => j.UpdateDeformations(deltaTime));
426  foreach (var deformation in SpriteDeformations)
427  {
428  if (character.IsDead && deformation.Params.StopWhenHostIsDead) { continue; }
429  if (!character.AnimController.InWater && deformation.Params.OnlyInWater) { continue; }
430  if (deformation.Params.UseMovementSine)
431  {
432  if (this is AnimController animator)
433  {
434  deformation.Phase = MathUtils.WrapAngleTwoPi(animator.WalkPos * deformation.Params.Frequency + MathHelper.Pi * deformation.Params.SineOffset);
435  }
436  }
437  else
438  {
439  deformation.Update(deltaTime);
440  }
441  }
442  }
443 
444  partial void FlipProjSpecific()
445  {
446  foreach (Limb limb in Limbs)
447  {
448  if (limb == null || limb.IsSevered || !limb.DoesMirror) { continue; }
449 
450  FlipSprite(limb.DeformSprite?.Sprite ?? limb.Sprite);
451  foreach (var conditionalSprite in limb.ConditionalSprites)
452  {
453  FlipSprite(conditionalSprite.DeformableSprite?.Sprite ?? conditionalSprite.Sprite);
454  }
455  }
456  static void FlipSprite(Sprite sprite)
457  {
458  if (sprite == null) { return; }
459  Vector2 spriteOrigin = sprite.Origin;
460  spriteOrigin.X = sprite.SourceRect.Width - spriteOrigin.X;
461  sprite.Origin = spriteOrigin;
462  }
463  }
464 
465  partial void SeverLimbJointProjSpecific(LimbJoint limbJoint, bool playSound)
466  {
467  foreach (Limb limb in new Limb[] { limbJoint.LimbA, limbJoint.LimbB })
468  {
469  float gibParticleAmount = MathHelper.Clamp(limb.Mass / character.AnimController.Mass, 0.1f, 1.0f);
470  foreach (ParticleEmitter emitter in character.GibEmitters)
471  {
472  if (emitter?.Prefab == null) { continue; }
473  if (inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Air) { continue; }
474  if (!inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) { continue; }
475 
476  emitter.Emit(1.0f, limb.WorldPosition, character.CurrentHull, amountMultiplier: gibParticleAmount);
477  }
478  }
479 
480  if (playSound)
481  {
482  var damageSound = character.GetSound(s => s.Type == CharacterSound.SoundType.Damage);
483  float range = damageSound != null ? damageSound.Range * 2 : ConvertUnits.ToDisplayUnits(character.AnimController.Collider.GetSize().Length() * 10);
484  if (!limbJoint.Params.BreakSound.IsNullOrEmpty() && !limbJoint.Params.BreakSound.Equals("none", StringComparison.OrdinalIgnoreCase))
485  {
486  SoundPlayer.PlayDamageSound(limbJoint.Params.BreakSound, 1.0f, limbJoint.LimbA.body.DrawPosition, range: range);
487  }
488  }
489  }
490 
491  public void Draw(SpriteBatch spriteBatch, Camera cam)
492  {
493  if (simplePhysicsEnabled) { return; }
494 
496 
497  if (Limbs == null)
498  {
499  DebugConsole.ThrowError("Failed to draw a ragdoll, limbs have been removed. Character: \"" + character.Name + "\", removed: " + character.Removed + "\n" + Environment.StackTrace.CleanupStackTrace());
500  GameAnalyticsManager.AddErrorEventOnce("Ragdoll.Draw:LimbsRemoved",
501  GameAnalyticsManager.ErrorSeverity.Error,
502  "Failed to draw a ragdoll, limbs have been removed. Character: \"" + character.SpeciesName + "\", removed: " + character.Removed + "\n" + Environment.StackTrace.CleanupStackTrace());
503  return;
504  }
505 
506  Color? color = null;
508  {
509  color = Color.Lerp(Color.White, GUIStyle.Orange, (float)Math.Sin(Timing.TotalTime * 3.5f));
510  }
511 
512  float depthOffset = GetDepthOffset();
513  if (!MathUtils.NearlyEqual(depthOffset, 0.0f))
514  {
515  foreach (Limb limb in limbs) { limb.ActiveSprite.Depth += depthOffset; }
516  }
517  for (int i = 0; i < limbs.Length; i++)
518  {
519  inversedLimbDrawOrder[i].Draw(spriteBatch, cam, color);
520  }
521  if (!MathUtils.NearlyEqual(depthOffset, 0.0f))
522  {
523  foreach (Limb limb in limbs) { limb.ActiveSprite.Depth -= depthOffset; }
524  }
525  LimbJoints.ForEach(j => j.Draw(spriteBatch));
526  }
527 
531  public float GetDepthOffset()
532  {
533  float maxDepth = 0.0f;
534  float minDepth = 1.0f;
535  float depthOffset = 0.0f;
536 
537  if (character.SelectedSecondaryItem?.GetComponent<Ladder>() is Ladder ladder)
538  {
539  CalculateLimbDepths();
541  {
542  //at the left side of the ladder, needs to be drawn in front of the rungs
543  if (maxDepth > ladder.BackgroundSpriteDepth)
544  {
545  depthOffset = Math.Max(ladder.BackgroundSpriteDepth - 0.01f - maxDepth, 0.0f);
546  }
547  else
548  {
549  depthOffset = Math.Max(ladder.Item.GetDrawDepth() + 0.0001f - minDepth, -minDepth);
550  }
551  }
552  else
553  {
554  //at the right side of the ladder, needs to be drawn behind the rungs
555  depthOffset = Math.Max(ladder.BackgroundSpriteDepth + 0.01f - minDepth, 0.0f);
556  }
557  }
558  else
559  {
560  CalculateLimbDepths();
561  AdjustDepthOffset(character.SelectedItem);
562  AdjustDepthOffset(character.SelectedSecondaryItem);
563 
564  void AdjustDepthOffset(Item item)
565  {
566  if (item?.GetComponent<Controller>() is { ControlCharacterPose: true, UserInCorrectPosition: true } controller && controller.User == character)
567  {
568  if (controller.Item.SpriteDepth <= maxDepth || controller.DrawUserBehind)
569  {
570  depthOffset = Math.Max(controller.Item.GetDrawDepth() + 0.0001f - minDepth, -minDepth);
571  }
572  else
573  {
574  depthOffset = Math.Max(controller.Item.GetDrawDepth() - 0.0001f - maxDepth, 0.0f);
575  }
576  }
577  }
578  }
579 
580  void CalculateLimbDepths()
581  {
582  foreach (Limb limb in Limbs)
583  {
584  var activeSprite = limb.ActiveSprite;
585  if (activeSprite != null)
586  {
587  maxDepth = Math.Max(activeSprite.Depth, maxDepth);
588  minDepth = Math.Min(activeSprite.Depth, minDepth);
589  }
590  }
591  }
592 
593  return depthOffset;
594  }
595 
596  public void DebugDraw(SpriteBatch spriteBatch)
597  {
598  if (!GameMain.DebugDraw || !character.Enabled) { return; }
599  if (simplePhysicsEnabled) { return; }
600 
601  foreach (Limb limb in Limbs)
602  {
603  if (limb.PullJointEnabled)
604  {
605  Vector2 pos = ConvertUnits.ToDisplayUnits(limb.PullJointWorldAnchorB);
607  pos.Y = -pos.Y;
608  GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)pos.Y, 5, 5), GUIStyle.Red, true, 0.01f);
609 
610  pos = ConvertUnits.ToDisplayUnits(limb.PullJointWorldAnchorA);
612  pos.Y = -pos.Y;
613  GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)pos.Y, 5, 5), Color.Cyan, true, 0.01f);
614  }
615 
616  limb.body.DebugDraw(spriteBatch, inWater ? (currentHull == null ? Color.Blue : Color.Cyan) : Color.White);
617  }
618 
619  Collider.DebugDraw(spriteBatch, frozen ? GUIStyle.Red : (inWater ? Color.SkyBlue : Color.Gray));
620  GUIStyle.Font.DrawString(spriteBatch, Collider.LinearVelocity.X.FormatSingleDecimal(), new Vector2(Collider.DrawPosition.X, -Collider.DrawPosition.Y), Color.Orange);
621 
622  foreach (var joint in LimbJoints)
623  {
624  Vector2 pos = ConvertUnits.ToDisplayUnits(joint.WorldAnchorA);
625  GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 5, 5), Color.White, true);
626 
627  pos = ConvertUnits.ToDisplayUnits(joint.WorldAnchorB);
628  GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 5, 5), Color.White, true);
629  }
630 
631  foreach (Limb limb in Limbs)
632  {
633  if (limb.body.TargetPosition != null)
634  {
635  Vector2 pos = ConvertUnits.ToDisplayUnits((Vector2)limb.body.TargetPosition);
636  if (currentHull?.Submarine != null)
637  {
639  }
640  pos.Y = -pos.Y;
641 
642  GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X - 10, (int)pos.Y - 10, 20, 20), Color.Cyan, false, 0.01f);
643  GUI.DrawLine(spriteBatch, pos, new Vector2(limb.WorldPosition.X, -limb.WorldPosition.Y), Color.Cyan);
644  }
645  }
646 
647  if (this is HumanoidAnimController humanoid)
648  {
649  Vector2 pos = ConvertUnits.ToDisplayUnits(humanoid.RightHandIKPos);
650  if (humanoid.character.Submarine != null) { pos += humanoid.character.Submarine.DrawPosition; }
651  GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 4, 4), GUIStyle.Green, true);
652  pos = ConvertUnits.ToDisplayUnits(humanoid.LeftHandIKPos);
653  if (humanoid.character.Submarine != null) { pos += humanoid.character.Submarine.DrawPosition; }
654  GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 4, 4), GUIStyle.Green, true);
655 
656  Vector2 aimPos = humanoid.AimSourceWorldPos;
657  aimPos.Y = -aimPos.Y;
658  GUI.DrawLine(spriteBatch, aimPos - Vector2.UnitY * 3, aimPos + Vector2.UnitY * 3, Color.Red);
659  GUI.DrawLine(spriteBatch, aimPos - Vector2.UnitX * 3, aimPos + Vector2.UnitX * 3, Color.Red);
660  }
661 
662  if (character.MemState.Count > 1)
663  {
664  Vector2 prevPos = ConvertUnits.ToDisplayUnits(character.MemState[0].Position);
665  if (currentHull?.Submarine != null)
666  {
668  }
669  prevPos.Y = -prevPos.Y;
670 
671  for (int i = 1; i < character.MemState.Count; i++)
672  {
673  Vector2 currPos = ConvertUnits.ToDisplayUnits(character.MemState[i].Position);
674  if (currentHull?.Submarine != null)
675  {
677  }
678  currPos.Y = -currPos.Y;
679 
680  GUI.DrawRectangle(spriteBatch, new Rectangle((int)currPos.X - 3, (int)currPos.Y - 3, 6, 6), Color.Cyan * 0.6f, true, 0.01f);
681  GUI.DrawLine(spriteBatch, prevPos, currPos, Color.Cyan * 0.6f, 0, 3);
682 
683  prevPos = currPos;
684  }
685  }
686 
687  if (currentHull != null)
688  {
689  Vector2 displayFloorPos = ConvertUnits.ToDisplayUnits(new Vector2(Collider.SimPosition.X, floorY));
690  if (currentHull?.Submarine != null) { displayFloorPos += currentHull.Submarine.DrawPosition; }
691  displayFloorPos.Y = -displayFloorPos.Y;
692  GUI.DrawLine(spriteBatch, displayFloorPos, displayFloorPos + new Vector2(floorNormal.X, -floorNormal.Y) * 50.0f, Color.Cyan * 0.5f, 0, 2);
693  }
694 
695  if (IgnorePlatforms)
696  {
697  GUI.DrawLine(spriteBatch,
698  new Vector2(Collider.DrawPosition.X, -Collider.DrawPosition.Y),
699  new Vector2(Collider.DrawPosition.X, -Collider.DrawPosition.Y + 50),
700  Color.Orange, 0, 5);
701  }
702  }
703  }
704 }
bool IsRemotelyControlled
Is the character controlled remotely (either by another player, or a server-side AIController)
Item????????? SelectedItem
The primary selected item. It can be any device that character interacts with. This excludes items li...
Item SelectedSecondaryItem
The secondary selected item. It's an item other than a device (see SelectedItem), e....
bool IsVisible
Is the character currently visible on the camera. Refresh the value by calling DoVisibilityCheck.
CharacterSound GetSound(Func< CharacterSound, bool > predicate=null, bool random=false)
Note that when a predicate is provided, the random option uses Linq.Where() extension method,...
virtual Vector2 WorldPosition
Definition: Entity.cs:49
Submarine Submarine
Definition: Entity.cs:53
static bool DebugDraw
Definition: GameMain.cs:29
static ParticleManager ParticleManager
Definition: GameMain.cs:101
static GameClient Client
Definition: GameMain.cs:188
static Hull FindHull(Vector2 position, Hull guess=null, bool useWorldCoordinates=true, bool inclusive=true)
Returns the hull which contains the point (or null if it isn't inside any)
bool TryInteract(Character user, bool ignoreRequiredItems=false, bool forceSelectKey=false, bool forceUseKey=false)
Sprite GetActiveSprite(bool excludeConditionalSprites=true)
void Draw(SpriteBatch spriteBatch, Camera cam, Color? overrideColor=null, bool disableDeformations=false)
readonly ParticleEmitterPrefab Prefab
void Emit(float deltaTime, Vector2 position, Hull? hullGuess=null, float angle=0.0f, float particleRotation=0.0f, float velocityMultiplier=1.0f, float sizeMultiplier=1.0f, float amountMultiplier=1.0f, Color? colorMultiplier=null, ParticlePrefab? overrideParticle=null, bool mirrorAngle=false, Tuple< Vector2, Vector2 >? tracerPoints=null)
Particle CreateParticle(string prefabName, Vector2 position, float angle, float speed, Hull hullGuess=null, float collisionIgnoreTimer=0f, Tuple< Vector2, Vector2 > tracerPoints=null)
void DebugDraw(SpriteBatch spriteBatch, Color color, bool forceColor=false)
float GetDepthOffset()
Offset added to the default draw depth of the character's limbs. For example, climbing on ladders aff...
Limb[] inversedLimbDrawOrder
Inversed draw order, which is used for drawing the limbs in 3d (deformable sprites).
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)
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.