3 using FarseerPhysics.Dynamics;
4 using FarseerPhysics.Dynamics.Contacts;
5 using FarseerPhysics.Dynamics.Joints;
6 using Microsoft.Xna.Framework;
8 using System.Collections.Generic;
10 using System.Xml.Linq;
14 using MoonSharp.Interpreter;
22 const float ImpactDamageMultiplayer = 10.0f;
26 const float MaxImpactDamage = 0.1f;
28 private static readonly List<Ragdoll> list =
new List<Ragdoll>();
32 public Fixture F1, F2;
33 public Vector2 LocalNormal;
34 public Vector2 Velocity;
35 public Vector2 ImpactPos;
37 public Impact(Fixture f1, Fixture f2, Contact contact, Vector2 velocity)
42 LocalNormal = contact.Manifold.LocalNormal;
43 contact.GetWorldManifold(out _, out FarseerPhysics.Common.FixedArray2<Vector2> points);
44 ImpactPos = points[0];
48 private readonly Queue<Impact> impactQueue =
new Queue<Impact>();
52 private bool accessRemovedCharacterErrorShown;
62 return Array.Empty<
Limb>();
73 get {
return frozen; }
76 if (frozen == value)
return;
89 private Dictionary<LimbType, Limb> limbDictionary;
92 private bool simplePhysicsEnabled;
99 private float splashSoundTimer;
103 private float flowForceTolerance, flowStunTolerance;
122 private Vector2 lastFloorCheckPos;
123 private bool lastFloorCheckIgnoreStairs, lastFloorCheckIgnorePlatforms;
140 private Category prevCollisionCategory = Category.None;
175 if (value >=
collider.Count || value < 0) {
return; }
182 pos2.Y +=
collider[value].Height * 1.1f;
183 if (
GameMain.
World.RayCast(pos1, pos2).Any(f => f.CollisionCategories.HasFlag(Physics.CollisionWall) && !(f.Body.UserData is
Submarine))) {
return; }
188 pos.Y +=
collider[value].Height * 0.5f;
194 collider[value].PhysEnabled = !frozen;
195 collider[value].Enabled = !simplePhysicsEnabled;
215 if (limbs.Contains(limb))
226 if (!IsValid(mainLimb))
230 mainLimb = torso ?? head;
231 if (!IsValid(mainLimb))
233 mainLimb =
Limbs.FirstOrDefault(l => IsValid(l));
235 if (mainLimb ==
null)
237 DebugConsole.ThrowError(
"Couldn't find a valid main limb. The limb can't be hidden nor be set to ignore collisions!");
238 mainLimb =
Limbs.FirstOrDefault();
258 get {
return simplePhysicsEnabled; }
261 if (value == simplePhysicsEnabled) {
return; }
263 simplePhysicsEnabled = value;
268 if (limb.
body ==
null)
270 DebugConsole.ThrowError(
"Limb has no body! (" + (
character !=
null ?
character.
Name :
"Unknown character") +
", " + limb.
type.ToString());
281 if (!simplePhysicsEnabled)
305 if (!MathUtils.IsValid(value))
return;
316 float? impactTolerance;
321 if (impactTolerance ==
null)
326 float? tolerance =
character.
Params.
VariantFile.GetRootExcludingOverride().GetChildElement(
"ragdoll")?.GetAttributeFloat(
"impacttolerance", impactTolerance.Value);
327 if (tolerance.HasValue)
329 impactTolerance = tolerance;
333 return impactTolerance.Value;
352 get {
return headInWater; }
385 Dictionary<LimbParams, List<WearableSprite>> items =
null;
386 if (ragdollParams !=
null)
393 items = limbs?.ToDictionary(l => l.Params, l => l.WearingItems);
403 DebugConsole.ThrowError($
"Invalid collider dimensions (r: {limbParams.Radius}, h: {limbParams.Height}, w: {limbParams.Width}) on limb: {limbParams.Name}. Fixing.");
404 limbParams.Radius = 10;
411 DebugConsole.ThrowError($
"Invalid collider dimensions (r: {colliderParams.Radius}, h: {colliderParams.Height}, w: {colliderParams.Width}) on collider: {colliderParams.Name}. Fixing.");
412 colliderParams.Radius = 10;
418 UpdateCollisionCategories();
422 foreach (var kvp
in items)
426 if (
id > limbs.Length - 1) {
continue; }
427 var limb = limbs[id];
428 var itemList = kvp.Value;
436 if (characterPrefab?.ConfigElement !=
null)
439 foreach (var huskAppendage
in mainElement.GetChildElements(
"huskappendage"))
441 if (huskAppendage.GetAttributeBool(
"onlyfromafflictions",
false)) {
continue; }
447 DebugConsole.ThrowError($
"Could not find an affliction of type 'huskinfection' that matches the affliction '{afflictionIdentifier}'!",
448 contentPackage: huskAppendage.ContentPackage);
469 DebugConsole.Log($
"Creating colliders from {RagdollParams.Name}.");
475 DebugConsole.ThrowError(
"Invalid collider dimensions: " + cParams.Name);
478 var body =
new PhysicsBody(cParams, findNewContacts:
false);
484 body.PhysEnabled =
false;
498 DebugConsole.Log($
"Creating joints from {RagdollParams.Name}.");
506 DebugConsole.ThrowError($
"Joint {i} null.");
510 UpdateCollisionCategories();
511 SetInitialLimbPositions();
514 private void SetInitialLimbPositions()
518 if (joint ==
null) {
continue; }
519 float angle = (joint.LowerLimit + joint.UpperLimit) / 2.0f;
520 joint.LimbB?.body?.SetTransformIgnoreContacts(
521 (joint.WorldAnchorA - MathUtils.RotatePointAroundTarget(joint.LocalAnchorB, Vector2.Zero, joint.BodyA.Rotation + angle,
true)),
522 joint.BodyA.Rotation + angle);
528 limbs?.ForEach(l => l.Remove());
530 DebugConsole.Log($
"Creating limbs from {RagdollParams.Name}.");
531 limbDictionary =
new Dictionary<LimbType, Limb>();
534 if (limbs.Contains(
null)) {
return; }
538 partial
void SetupDrawOrder();
572 Limbs.ForEach(l => l.LoadParams());
578 if (!checkLimbIndex(jointParams.Limb1,
"Limb1") || !checkLimbIndex(jointParams.Limb2,
"Limb2"))
593 bool checkLimbIndex(
int index,
string debugName)
595 if (index < 0 || index >= limbs.Length)
597 string errorMsg = $
"Failed to add a joint to character {character.Name}. {debugName} out of bounds (index: {index}, limbs: {limbs.Length}.";
598 DebugConsole.ThrowError(errorMsg, contentPackage: jointParams.Element?.ContentPackage);
601 GameAnalyticsManager.AddErrorEventOnce(
"Ragdoll.AddJoint:IndexOutOfRange", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
611 if (limbParams.ID < 0 || limbParams.ID > 255)
613 throw new Exception($
"Invalid limb params in limb \"{limbParams.Type}\". \"{limbParams.ID}\" is not a valid limb ID.");
615 byte ID = Convert.ToByte(limbParams.ID);
618 if (ID >=
Limbs.Length)
620 throw new Exception($
"Failed to add a limb to the character \"{Character?.ConfigPath ?? "null"}\" (limb index {ID} out of bounds). The ragdoll file may be configured incorrectly.");
624 if (!limbDictionary.ContainsKey(limb.
type)) { limbDictionary.Add(limb.
type, limb); }
629 if (
Limbs.Contains(limb)) {
return; }
631 Array.Resize(ref limbs,
Limbs.Length + 1);
634 if (!limbDictionary.ContainsKey(limb.
type)) { limbDictionary.Add(limb.
type, limb); }
640 if (!
Limbs.Contains(limb))
return;
647 if (existingLimb == limb)
continue;
648 newLimbs[i] = existingLimb;
653 if (limbDictionary.ContainsKey(limb.type))
655 limbDictionary.
Remove(limb.type);
659 var otherLimb =
Limbs.FirstOrDefault(l => l != limb && l.type == limb.type);
660 if (otherLimb !=
null)
662 limbDictionary.Add(otherLimb.type, otherLimb);
672 if (attachedJoints.Length > 0)
678 if (attachedJoints.Contains(limbJoint))
continue;
679 newJoints[i] = limbJoint;
686 foreach (
LimbJoint limbJoint
in attachedJoints)
692 private enum LimbStairCollisionResponse
695 ClimbWithoutLimbCollision,
696 ClimbWithLimbCollision
702 if (f2.UserData is
Hull)
713 (f1.Body.UserData is
Limb limb && !limb.
Params.CanEnterSubmarine);
724 velocity -= sub.Velocity;
728 if (f2.Body.UserData is not
Structure structure)
734 impactQueue.Enqueue(
new Impact(f1, f2, contact, velocity));
745 if (structure.IsPlatform)
749 if (colliderBottom.Y < ConvertUnits.ToSimUnits(structure.Rect.Y - 5)) {
return false; }
750 if (f1.Body.Position.Y < ConvertUnits.ToSimUnits(structure.Rect.Y - 5)) {
return false; }
752 else if (structure.StairDirection !=
Direction.None)
759 var collisionResponse = getStairCollisionResponse();
760 if (collisionResponse == LimbStairCollisionResponse.ClimbWithLimbCollision)
766 if (collisionResponse == LimbStairCollisionResponse.DontClimbStairs) {
Stairs =
null; }
771 LimbStairCollisionResponse getStairCollisionResponse()
776 float stairBottomPos = ConvertUnits.ToSimUnits(structure.Rect.Y - structure.Rect.Height + 10);
777 if (colliderBottom.Y < stairBottomPos &&
targetMovement.Y < 0.5f) {
return LimbStairCollisionResponse.DontClimbStairs; }
782 return LimbStairCollisionResponse.DontClimbStairs;
786 if (
targetMovement.Y >= 0.0f && colliderBottom.Y >= ConvertUnits.ToSimUnits(structure.Rect.Y -
Submarine.
GridSize.Y * 5)) {
return LimbStairCollisionResponse.DontClimbStairs; }
789 if (contact.Manifold.LocalNormal.Y < 0.0f)
791 return Stairs != structure
792 ? LimbStairCollisionResponse.DontClimbStairs
793 : LimbStairCollisionResponse.ClimbWithoutLimbCollision;
797 contact.GetWorldManifold(out _, out FarseerPhysics.Common.FixedArray2<Vector2> points);
798 if (points[0].Y >
Collider.
SimPosition.Y) {
return LimbStairCollisionResponse.DontClimbStairs; }
803 return LimbStairCollisionResponse.ClimbWithLimbCollision;
809 impactQueue.Enqueue(
new Impact(f1, f2, contact, velocity));
815 private void ApplyImpact(Fixture f1, Fixture f2, Vector2 localNormal, Vector2 impactPos, Vector2 velocity)
819 if (f2.Body?.UserData is
Item)
826 Vector2 normal = localNormal;
827 float impact = Vector2.Dot(velocity, -normal);
830 bool isNotRemote =
true;
837 if (impact > impactTolerance)
839 impactPos = ConvertUnits.ToDisplayUnits(impactPos);
844 var should = GameMain.LuaCs.Hook.Call<
float?>(
"changeFallDamage", impactDamage,
character, impactPos, velocity);
848 impactDamage = should.Value;
852 character.
AddDamage(impactPos, AfflictionPrefab.ImpactDamage.Instantiate(impactDamage).ToEnumerable(), 0.0f,
true);
864 ImpactProjSpecific(impact, f1.Body);
870 return Math.Min((impact - tolerance) * ImpactDamageMultiplayer,
character.
MaxVitality * MaxImpactDamage);
873 private readonly List<Limb> connectedLimbs =
new List<Limb>();
874 private readonly List<LimbJoint> checkedJoints =
new List<LimbJoint>();
886 if (limbDiff.LengthSquared() < 0.0001f) { limbDiff = Rand.Vector(1.0f); }
887 limbDiff = Vector2.Normalize(limbDiff);
888 float mass = limbJoint.
BodyA.Mass + limbJoint.
BodyB.Mass;
892 connectedLimbs.Clear();
893 checkedJoints.Clear();
897 if (connectedLimbs.Contains(limb)) {
continue; }
915 SeverLimbJointProjSpecific(limbJoint, playSound:
true);
923 partial
void SeverLimbJointProjSpecific(
LimbJoint limbJoint,
bool playSound);
927 connectedLimbs.Clear();
928 checkedJoints.Clear();
930 return connectedLimbs;
935 connectedLimbs.Add(limb);
939 if (joint.
IsSevered || checkedJoints.Contains(joint)) {
continue; }
940 if (joint.
LimbA == limb)
942 if (!connectedLimbs.Contains(joint.LimbB))
944 checkedJoints.Add(joint);
948 else if (joint.LimbB == limb)
950 if (!connectedLimbs.Contains(joint.
LimbA))
952 checkedJoints.Add(joint);
959 partial
void ImpactProjSpecific(
float impact, Body body);
991 partial
void FlipProjSpecific();
996 if (!
Limbs.Any(l => !l.IsSevered && l.body.Enabled))
1001 Vector2 centerOfMass = Vector2.Zero;
1002 float totalMass = 0.0f;
1007 totalMass += limb.
Mass;
1011 centerOfMass /= totalMass;
1013 if (!MathUtils.IsValid(centerOfMass))
1015 string errorMsg =
"Ragdoll.GetCenterOfMass returned an invalid value (" + centerOfMass +
"). Limb positions: {"
1016 +
string.Join(
", ", limbs.Select(l => l.SimPosition)) +
"}, total mass: " + totalMass +
".";
1017 DebugConsole.ThrowError(errorMsg);
1018 GameAnalyticsManager.AddErrorEventOnce(
"Ragdoll.GetCenterOfMass", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
1022 return centerOfMass;
1027 public void MoveLimb(
Limb limb, Vector2 pos,
float amount,
bool pullFromCenter =
false)
1029 limb.
MoveToPos(pos, amount, pullFromCenter);
1034 for (
int i = 0; i <
Limbs.Length; i++)
1036 if (
Limbs[i] ==
null) {
continue; }
1037 if (condition !=
null && !condition(
Limbs[i])) {
continue; }
1050 public void FindHull(Vector2? worldPosition =
null,
bool setSubmarine =
true)
1052 Vector2 findPos = worldPosition ==
null ? this.
WorldPosition : (Vector2)worldPosition;
1053 if (!MathUtils.IsValid(findPos))
1055 GameAnalyticsManager.AddErrorEventOnce(
1056 "Ragdoll.FindHull:InvalidPosition",
1057 GameAnalyticsManager.ErrorSeverity.Error,
1058 "Attempted to find a hull at an invalid position (" + findPos +
")\n" + Environment.StackTrace.CleanupStackTrace());
1077 Vector2 moveDir = hullDiff.LengthSquared() < 0.001f ? Vector2.UnitY : Vector2.Normalize(hullDiff);
1080 if (MathUtils.GetLineRectangleIntersection(
1084 out Vector2 intersection))
1120 Teleport(ConvertUnits.ToSimUnits(prevSubPos - newSubPos), Vector2.Zero);
1128 attachedProjectile.Item.CurrentHull =
currentHull;
1134 private void PreventOutsideCollision()
1139 foreach (Gap gap
in connectedGaps)
1141 if (gap.IsHorizontal)
1160 gap.RefreshOutsideCollider();
1164 public void Teleport(Vector2 moveAmount, Vector2 velocityChange,
bool detachProjectiles =
true)
1172 while (ce !=
null && ce.Contact !=
null)
1174 ce.Contact.Enabled =
false;
1197 private void UpdateCollisionCategories()
1200 Physics.CollisionLevel | Physics.CollisionWall
1201 : Physics.CollisionWall;
1204 wall | Physics.CollisionProjectile | Physics.CollisionStairs
1205 : wall | Physics.CollisionProjectile | Physics.CollisionPlatform | Physics.CollisionStairs;
1207 if (collisionCategory == prevCollisionCategory) {
return; }
1208 prevCollisionCategory = collisionCategory;
1212 foreach (Limb limb
in Limbs)
1214 if (limb.IgnoreCollisions || limb.IsSevered) {
continue; }
1218 limb.body.CollidesWith = collisionCategory;
1222 DebugConsole.ThrowError(
"Failed to update ragdoll limb collisioncategories", e);
1232 private float bodyInRestTimer;
1234 private float BodyInRestDelay = 1.0f;
1238 get {
return bodyInRestTimer > BodyInRestDelay; }
1245 bodyInRestTimer = value ? BodyInRestDelay : 0.0f;
1256 while (impactQueue.Count > 0)
1258 var impact = impactQueue.Dequeue();
1259 ApplyImpact(impact.F1, impact.F2, impact.LocalNormal, impact.ImpactPos, impact.Velocity);
1264 UpdateNetPlayerPosition(deltaTime);
1266 UpdateCollisionCategories();
1269 PreventOutsideCollision();
1271 CheckBodyInRest(deltaTime);
1273 splashSoundTimer -= deltaTime;
1304 headInWater =
false;
1305 RefreshFloorY(deltaTime, ignoreStairs:
Stairs ==
null);
1315 headInWater =
false;
1317 RefreshFloorY(deltaTime, ignoreStairs:
Stairs ==
null);
1320 (
float waterSurfaceDisplayUnits,
float ceilingDisplayUnits) = GetWaterSurfaceAndCeilingY();
1321 float waterSurfaceY = ConvertUnits.ToSimUnits(waterSurfaceDisplayUnits);
1322 float ceilingY = ConvertUnits.ToSimUnits(ceilingDisplayUnits);
1331 var lowerHull =
Hull.
FindHull(ConvertUnits.ToDisplayUnits(colliderBottom), useWorldCoordinates:
false);
1332 if (lowerHull !=
null)
1334 floorY = ConvertUnits.ToSimUnits(lowerHull.Rect.Y - lowerHull.Rect.Height);
1342 if (waterSurfaceY -
floorY > standHeight * 0.8f ||
1343 ceilingY -
floorY < standHeight * 0.8f)
1351 UpdateHullFlowForces(deltaTime);
1366 bool prevInWater = limb.
InWater;
1373 else if (newHull ==
null)
1377 if (limb.
type ==
LimbType.Head) { headInWater =
true; }
1393 Splash(limb, newHull);
1399 newHull.
WaveVel[n] += MathHelper.Clamp(impulse.Y, -5.0f, 5.0f);
1403 limb.
Hull = newHull;
1407 bool isAttachedToController =
1410 controller.IsAttachedUser(controller.User);
1418 const float LevitationSpeedMultiplier = 5f;
1421 float slopePull = 0f;
1426 slopePull = Math.Abs(
movement.X * steepness) / LevitationSpeedMultiplier;
1434 yVelocity = Math.Sign(yVelocity);
1437 yVelocity -= slopePull * LevitationSpeedMultiplier;
1456 UpdateProjSpecific(deltaTime, cam);
1460 private void CheckBodyInRest(
float deltaTime)
1466 bodyInRestTimer = 0.0f;
1467 foreach (Limb limb
in Limbs)
1469 limb.body.PhysEnabled =
true;
1472 else if (
Limbs.All(l => l !=
null && !l.body.Enabled || l.LinearVelocity.LengthSquared() < 0.001f))
1474 bodyInRestTimer += deltaTime;
1475 if (bodyInRestTimer > BodyInRestDelay)
1477 foreach (Limb limb
in Limbs)
1479 limb.body.PhysEnabled =
false;
1486 private int validityResets;
1487 private bool CheckValidity()
1491 DebugConsole.ThrowError(
"Attempted to check the validity of a potentially removed ragdoll. Character: " +
character.
Name +
", id: " +
character.
ID +
", removed: " +
character.
Removed +
", ragdoll removed: " + !list.Contains(
this));
1495 bool isColliderValid = CheckValidity(
Collider);
1497 bool limbsValid =
true;
1498 foreach (Limb limb
in limbs)
1500 if (limb?.body ==
null || !limb.body.Enabled) {
continue; }
1501 if (!CheckValidity(limb.body))
1504 limb.body.ResetDynamics();
1508 bool isValid = isColliderValid && limbsValid;
1512 if (validityResets > 3)
1515 DebugConsole.ThrowError(
"Invalid ragdoll physics. Ragdoll frozen to prevent crashes.");
1518 foreach (Limb limb
in Limbs)
1521 limb.body?.ResetDynamics();
1529 private bool CheckValidity(PhysicsBody body)
1531 string errorMsg =
null;
1532 if (!MathUtils.IsValid(body.SimPosition) || Math.Abs(body.SimPosition.X) > 1e10f || Math.Abs(body.SimPosition.Y) > 1e10f)
1534 errorMsg = GetBodyName() +
" position invalid (" + body.SimPosition +
", character: [name]).";
1536 else if (!MathUtils.IsValid(body.LinearVelocity) || Math.Abs(body.LinearVelocity.X) > 1000f || Math.Abs(body.LinearVelocity.Y) > 1000f)
1538 errorMsg = GetBodyName() +
" velocity invalid (" + body.LinearVelocity +
", character: [name]).";
1540 else if (!MathUtils.IsValid(body.Rotation))
1542 errorMsg = GetBodyName() +
" rotation invalid (" + body.Rotation +
", character: [name]).";
1544 else if (!MathUtils.IsValid(body.AngularVelocity) || Math.Abs(body.AngularVelocity) > 1000f)
1546 errorMsg = GetBodyName() +
" angular velocity invalid (" + body.AngularVelocity +
", character: [name]).";
1548 if (errorMsg !=
null)
1552 errorMsg +=
" Ragdoll controlled remotely.";
1556 errorMsg +=
" Simple physics enabled.";
1558 if (GameMain.NetworkMember !=
null)
1560 errorMsg += GameMain.NetworkMember.IsClient ?
" Playing as a client." :
" Hosting a server.";
1564 DebugConsole.ThrowError(errorMsg.Replace(
"[name]",
Character.
Name));
1566 DebugConsole.NewMessage(errorMsg.Replace(
"[name]",
Character.
Name), Color.Red);
1568 GameAnalyticsManager.AddErrorEventOnce(
"Ragdoll.CheckValidity:" +
character.
ID, GameAnalyticsManager.ErrorSeverity.Error, errorMsg.Replace(
"[name]",
Character.
SpeciesName.Value));
1574 foreach (Limb otherLimb
in Limbs)
1577 otherLimb.body.ResetDynamics();
1579 SetInitialLimbPositions();
1583 string GetBodyName()
1585 return body.UserData is Limb limb ?
"Limb (" + limb.type +
")" :
"Collider";
1593 if (!accessRemovedCharacterErrorShown)
1595 string errorMsg =
"Attempted to access a potentially removed ragdoll. Character: " +
character.
Name +
", id: " +
character.
ID +
", removed: " +
character.
Removed +
", ragdoll removed: " + !list.Contains(
this);
1596 errorMsg +=
'\n' + Environment.StackTrace.CleanupStackTrace();
1597 DebugConsole.ThrowError(errorMsg);
1598 GameAnalyticsManager.AddErrorEventOnce(
1599 "Ragdoll:AccessRemoved",
1600 GameAnalyticsManager.ErrorSeverity.Error,
1601 "Attempted to access a potentially removed ragdoll. Character: " +
character.
SpeciesName +
", id: " +
character.
ID +
", removed: " +
character.
Removed +
", ragdoll removed: " + !list.Contains(
this) +
"\n" + Environment.StackTrace.CleanupStackTrace());
1602 accessRemovedCharacterErrorShown =
true;
1606 partial
void UpdateProjSpecific(
float deltaTime,
Camera cam);
1608 partial
void Splash(
Limb limb,
Hull limbHull);
1610 private void UpdateHullFlowForces(
float deltaTime)
1614 const float StunForceThreshold = 5.0f;
1615 const float StunDuration = 0.5f;
1616 const float ToleranceIncreaseSpeed = 5.0f;
1617 const float ToleranceDecreaseSpeed = 1.0f;
1620 const float DistanceFactor = 0.5f;
1621 const float ForceMultiplier = 0.035f;
1623 Vector2 flowForce = Vector2.Zero;
1624 foreach (Gap gap
in Gap.GapList)
1626 if (gap.Open <= 0.0f || !gap.linkedTo.Contains(
currentHull) || gap.LerpedFlowForce.LengthSquared() < 0.01f) {
continue; }
1628 flowForce += Vector2.Normalize(gap.LerpedFlowForce) * (Math.Max(gap.LerpedFlowForce.Length() - dist, 0.0f) * ForceMultiplier);
1635 float flowForceMagnitude = flowForce.Length();
1636 float limbMultipier = limbs.Count(l => l.InWater) / (float)limbs.Length;
1638 if ((flowForceMagnitude * limbMultipier) - flowStunTolerance > StunForceThreshold)
1641 flowStunTolerance = Math.Max(flowStunTolerance, flowForceMagnitude);
1646 float shakeStrength = Math.Min(flowForceMagnitude / 10.0f, 5.0f) * limbMultipier;
1647 Screen.Selected.Cam.Shake = Math.Max(Screen.Selected.Cam.Shake, shakeStrength);
1650 if (flowForceMagnitude > 0.0001f)
1652 flowForce = Vector2.Normalize(flowForce) * Math.Max(flowForceMagnitude - flowForceTolerance, 0.0f);
1655 if (flowForceTolerance <= flowForceMagnitude * 1.5f &&
inWater)
1659 flowForceTolerance += deltaTime * ToleranceIncreaseSpeed;
1660 flowStunTolerance = Math.Max(flowStunTolerance, flowForceTolerance);
1664 flowForceTolerance = Math.Max(flowForceTolerance - deltaTime * ToleranceDecreaseSpeed, 0.0f);
1665 flowStunTolerance = Math.Max(flowStunTolerance - deltaTime * ToleranceDecreaseSpeed, 0.0f);
1668 if (flowForce.LengthSquared() > 0.001f)
1671 foreach (Limb limb
in limbs)
1673 if (!limb.InWater) {
continue; }
1674 limb.body.ApplyForce(flowForce * (limb.Mass /
Mass * limbs.Length));
1681 lastFloorCheckPos = Vector2.Zero;
1685 private const float FloorYStaleTime = 1;
1686 private float floorYCheckTimer;
1687 private void RefreshFloorY(
float deltaTime,
bool ignoreStairs =
false)
1689 floorYCheckTimer -= deltaTime;
1691 if (floorYCheckTimer < 0 ||
1692 lastFloorCheckIgnoreStairs != ignoreStairs ||
1694 Vector2.DistanceSquared(lastFloorCheckPos, refBody.
SimPosition) > 0.1f * 0.1f)
1698 lastFloorCheckIgnoreStairs = ignoreStairs;
1701 floorYCheckTimer = FloorYStaleTime * Rand.Range(0.9f, 1.1f);
1705 private float GetFloorY(Vector2 simPosition,
bool ignoreStairs =
false)
1710 Vector2 rayStart = simPosition;
1715 Vector2 rayEnd = rayStart -
new Vector2(0.0f, height * 2f);
1716 Vector2 colliderBottomDisplay = ConvertUnits.ToDisplayUnits(
GetColliderBottom());
1718 Fixture standOnFloorFixture =
null;
1719 float standOnFloorFraction = 1;
1720 float closestFraction = 1;
1721 GameMain.World.RayCast((fixture, point, normal, fraction) =>
1723 switch (fixture.CollisionCategories)
1725 case Physics.CollisionStairs:
1726 if (inWater && TargetMovement.Y < 0.5f) { return -1; }
1730 Structure structure = fixture.Body.UserData as Structure;
1731 if (colliderBottomDisplay.Y >= structure.Rect.Y - structure.Rect.Height + 30 || TargetMovement.Y > 0.5f || Stairs != null)
1733 standOnFloorFraction = fraction;
1734 standOnFloorFixture = fixture;
1738 if (ignoreStairs) {
return -1; }
1740 case Physics.CollisionPlatform:
1745 if (colliderBottomDisplay.Y >= platform.Rect.Y - 16 || (
targetMovement.Y > 0.0f &&
Stairs ==
null))
1747 standOnFloorFraction = fraction;
1748 standOnFloorFixture = fixture;
1752 if (colliderBottomDisplay.Y < platform.Rect.Y - 16 && (
targetMovement.Y <= 0.0f ||
Stairs !=
null))
return -1;
1755 case Physics.CollisionWall:
1756 case Physics.CollisionLevel:
1757 if (!fixture.CollidesWith.HasFlag(Physics.CollisionCharacter)) { return -1; }
1758 if (fixture.Body.UserData is Submarine &&
character.
Submarine !=
null) { return -1; }
1759 if (fixture.IsSensor) { return -1; }
1760 if (fraction < standOnFloorFraction)
1762 standOnFloorFraction = fraction;
1763 standOnFloorFixture = fixture;
1767 System.Diagnostics.Debug.Assert(
false,
"Floor raycast should not have hit a fixture with the collision category " + fixture.CollisionCategories);
1771 if (fraction < closestFraction)
1774 closestFraction = fraction;
1777 return closestFraction;
1778 }, rayStart, rayEnd, Physics.CollisionStairs | Physics.CollisionPlatform | Physics.CollisionWall | Physics.CollisionLevel);
1780 if (standOnFloorFixture !=
null && !IsHangingWithRope)
1782 floorFixture = standOnFloorFixture;
1783 standOnFloorY = rayStart.Y + (rayEnd.Y - rayStart.Y) * standOnFloorFraction;
1784 if (rayStart.Y - standOnFloorY < Collider.Height * 0.5f + Collider.Radius + ColliderHeightFromFloor * 1.2f)
1787 if (standOnFloorFixture.CollisionCategories == Physics.CollisionStairs)
1789 Stairs = standOnFloorFixture.Body.UserData as
Structure;
1794 if (closestFraction >= 1)
1796 floorNormal = Vector2.UnitY;
1797 if (CurrentHull ==
null)
1803 float hullBottom = currentHull.
Rect.Y - currentHull.
Rect.Height;
1807 if (!gap.IsRoomToRoom || gap.Open < 1.0f || gap.ConnectedDoor !=
null || gap.IsHorizontal) {
continue; }
1808 if (WorldPosition.X > gap.WorldRect.X && WorldPosition.X < gap.WorldRect.Right && gap.WorldPosition.Y < WorldPosition.Y)
1810 var lowerHull = gap.linkedTo[0] == currentHull ? gap.
linkedTo[1] : gap.linkedTo[0];
1811 hullBottom = Math.Min(hullBottom, lowerHull.Rect.Y - lowerHull.Rect.Height);
1814 return ConvertUnits.ToSimUnits(hullBottom);
1819 return rayStart.Y + (rayEnd.Y - rayStart.Y) * closestFraction;
1828 return GetWaterSurfaceAndCeilingY().WaterSurfaceY;
1834 private (
float WaterSurfaceY,
float CeilingY) GetWaterSurfaceAndCeilingY()
1837 if (currentHull ==
null || character.CurrentHull ==
null)
1839 return (
float.PositiveInfinity,
float.PositiveInfinity);
1842 float surfaceY = currentHull.
Surface;
1843 float ceilingY = currentHull.
Rect.Y;
1844 float surfaceThreshold = ConvertUnits.ToDisplayUnits(Collider.SimPosition.Y + 1.0f);
1847 if (currentHull.
Rect.Y - currentHull.
Surface < 5.0f)
1849 GetSurfacePos(currentHull, ref surfaceY, ref ceilingY);
1850 void GetSurfacePos(Hull hull, ref
float prevSurfacePos, ref
float ceilingPos)
1852 if (prevSurfacePos > surfaceThreshold) {
return; }
1853 foreach (Gap gap
in hull.ConnectedGaps)
1855 if (gap.IsHorizontal || gap.Open <= 0.0f || gap.WorldPosition.Y < hull.WorldPosition.Y) {
continue; }
1856 if (Collider.SimPosition.X < ConvertUnits.ToSimUnits(gap.Rect.X) || Collider.SimPosition.X > ConvertUnits.ToSimUnits(gap.Rect.Right)) {
continue; }
1859 if (!gap.IsRoomToRoom && gap.Position.Y > hull.Position.Y)
1861 ceilingPos += 100000.0f;
1862 prevSurfacePos += 100000.0f;
1866 foreach (var linkedTo
in gap.linkedTo)
1868 if (linkedTo is Hull otherHull && otherHull != hull && otherHull != currentHull)
1870 prevSurfacePos = Math.Max(surfaceY, otherHull.Surface);
1871 ceilingPos = Math.Max(ceilingPos, otherHull.Rect.Y);
1872 GetSurfacePos(otherHull, ref prevSurfacePos, ref ceilingPos);
1879 return (surfaceY, ceilingY);
1882 public void SetPosition(Vector2 simPosition,
bool lerp =
false,
bool ignorePlatforms =
true,
bool forceMainLimbToCollider =
false,
bool moveLatchers =
true)
1884 if (!MathUtils.IsValid(simPosition))
1886 DebugConsole.ThrowError(
"Attempted to move a ragdoll (" + character.Name +
") to an invalid position (" + simPosition +
"). " + Environment.StackTrace.CleanupStackTrace());
1887 GameAnalyticsManager.AddErrorEventOnce(
1888 "Ragdoll.SetPosition:InvalidPosition",
1889 GameAnalyticsManager.ErrorSeverity.Error,
1890 "Attempted to move a ragdoll (" + character.SpeciesName +
") to an invalid position (" + simPosition +
"). " + Environment.StackTrace.CleanupStackTrace());
1893 if (MainLimb ==
null) {
return; }
1895 Vector2 limbMoveAmount = forceMainLimbToCollider ? simPosition - MainLimb.SimPosition : simPosition - Collider.SimPosition;
1900 const float ForceDeattachThreshold = 10.0f;
1901 if (limbMoveAmount.LengthSquared() > ForceDeattachThreshold * ForceDeattachThreshold &&
1904 var target = enemyAI.LatchOntoAI.TargetCharacter;
1907 target.Latchers.ForEachMod(l => l?.DeattachFromBody(reset:
true));
1908 target.Latchers.Clear();
1910 enemyAI.LatchOntoAI.DeattachFromBody(reset:
true);
1917 Collider.TargetPosition = simPosition;
1918 Collider.MoveToTargetPosition(
true);
1922 Collider.SetTransformIgnoreContacts(simPosition, Collider.Rotation);
1925 if (!MathUtils.NearlyEqual(limbMoveAmount, Vector2.Zero))
1927 foreach (
Limb limb
in Limbs)
1931 Vector2 movePos = limb.
SimPosition + limbMoveAmount;
1932 TrySetLimbPosition(limb, simPosition, movePos, limb.
Rotation, lerp, ignorePlatforms);
1940 public bool IsHoldingToRope {
get;
private set; }
1946 public bool IsHangingWithRope {
get;
private set; }
1952 public bool IsDraggedWithRope {
get;
private set; }
1957 shouldHangWithRope =
true;
1958 IsHangingWithRope =
true;
1961 levitatingCollider =
false;
1966 shouldHoldToRope =
true;
1967 IsHoldingToRope =
true;
1972 shouldBeDraggedWithRope =
true;
1973 IsDraggedWithRope =
true;
1978 shouldHangWithRope =
false;
1979 IsHangingWithRope =
false;
1984 shouldHoldToRope =
false;
1985 IsHoldingToRope =
false;
1990 shouldBeDraggedWithRope =
false;
1991 IsDraggedWithRope =
false;
1994 protected void TrySetLimbPosition(
Limb limb, Vector2 original, Vector2 simPosition,
float rotation,
bool lerp =
false,
bool ignorePlatforms =
true)
1996 Vector2 movePos = simPosition;
1998 if (Vector2.DistanceSquared(original, simPosition) > 0.0001f)
2000 Category collisionCategory = Physics.CollisionWall | Physics.CollisionLevel;
2001 if (!ignorePlatforms) { collisionCategory |= Physics.CollisionPlatform; }
2025 foreach (var attachedProjectile
in character.AttachedProjectiles)
2027 if (attachedProjectile.IsAttachedTo(limb.body))
2029 attachedProjectile.Item.SetTransform(
2030 attachedProjectile.Item.SimPosition + (movePos - prevPosition),
2031 attachedProjectile.Item.body.Rotation,
2032 findNewHull:
false);
2038 private bool collisionsDisabled;
2039 private double lastObstacleRayCastTime;
2043 float allowedDist = Math.Max(Math.Max(Collider.Radius, Collider.Width), Collider.Height) * 2.0f;
2044 allowedDist = Math.Max(allowedDist, 1.0f);
2045 float resetDist = allowedDist * 5.0f;
2047 float obstacleCheckDist = 0.3f;
2049 Vector2 diff = Collider.SimPosition - MainLimb.SimPosition;
2050 float distSqrd = diff.LengthSquared();
2052 bool shouldReset = distSqrd > resetDist * resetDist;
2053 if (!shouldReset && distSqrd > obstacleCheckDist * obstacleCheckDist)
2055 if (Timing.TotalTime > lastObstacleRayCastTime + 1 &&
2056 Submarine.
PickBody(Collider.SimPosition, MainLimb.SimPosition, collisionCategory: Physics.CollisionWall) !=
null)
2059 lastObstacleRayCastTime = Timing.TotalTime;
2066 SetPosition(Collider.SimPosition, lerp:
true, forceMainLimbToCollider:
true);
2068 else if (distSqrd > allowedDist * allowedDist)
2073 Vector2 forceDir = diff / (float)Math.Sqrt(distSqrd);
2074 foreach (
Limb limb
in Limbs)
2081 collisionsDisabled =
true;
2083 else if (collisionsDisabled)
2086 SetPosition(Collider.SimPosition, lerp:
true);
2087 collisionsDisabled =
false;
2089 prevCollisionCategory = Category.None;
2093 partial
void UpdateNetPlayerPositionProjSpecific(
float deltaTime,
float lowestSubPos);
2094 private void UpdateNetPlayerPosition(
float deltaTime)
2098 float lowestSubPos =
float.MaxValue;
2101 lowestSubPos = ConvertUnits.ToSimUnits(
Submarine.
Loaded.Min(s => s.HiddenSubPosition.Y - s.Borders.Height - 128.0f));
2102 for (
int i = 0; i < character.MemState.Count; i++)
2104 if (character.Submarine ==
null)
2107 if (character.MemState[i].Position.Y > lowestSubPos)
2108 character.MemState[i].TransformInToOutside();
2110 else if (currentHull?.Submarine !=
null)
2113 if (character.MemState[i].Position.Y < lowestSubPos)
2114 character.MemState[i].TransformOutToInside(currentHull.
Submarine);
2119 UpdateNetPlayerPositionProjSpecific(deltaTime, lowestSubPos);
2130 public Limb GetLimb(
LimbType limbType,
bool excludeSevered =
true,
bool excludeLimbsWithSecondaryType =
false,
bool useSecondaryType =
false)
2133 if (!HasMultipleLimbsOfSameType && !useSecondaryType && !excludeLimbsWithSecondaryType)
2136 if (limbDictionary.TryGetValue(limbType, out limb))
2142 if (excludeSevered && limb is { IsSevered:
true } )
2151 foreach (var l
in limbs)
2153 if (l.Removed) {
continue; }
2154 if (useSecondaryType)
2156 if (l.Params.SecondaryType != limbType) {
continue; }
2158 else if (l.type != limbType)
2162 if (excludeSevered && l.IsSevered) {
continue; }
2163 if (excludeLimbsWithSecondaryType && l.Params.SecondaryType !=
LimbType.None) {
continue; }
2175 if (mouthLimb ==
null) {
return null; }
2176 float cos = (float)Math.Cos(mouthLimb.
Rotation);
2177 float sin = (float)Math.Sin(mouthLimb.
Rotation);
2179 Vector2 offset =
new Vector2(mouthLimb.
MouthPos.X * bodySize.X / 2, mouthLimb.
MouthPos.Y * bodySize.Y / 2);
2180 return mouthLimb.
SimPosition +
new Vector2(offset.X * cos - offset.Y * sin, offset.X * sin + offset.Y * cos);
2185 float offset = 0.0f;
2187 if (!character.IsDead && character.Stun <= 0.0f && !character.IsIncapacitated)
2189 offset = -ColliderHeightFromFloor;
2192 float lowestBound = Collider.SimPosition.Y;
2193 if (Collider.FarseerBody.FixtureList !=
null)
2195 for (
int i = 0; i < Collider.FarseerBody.FixtureList.Count; i++)
2197 Collider.FarseerBody.GetTransform(out FarseerPhysics.Common.Transform transform);
2198 Collider.FarseerBody.FixtureList[i].Shape.ComputeAABB(out FarseerPhysics.Collision.AABB aabb, ref transform, i);
2200 lowestBound = Math.Min(aabb.LowerBound.Y, lowestBound);
2203 return new Vector2(Collider.SimPosition.X, lowestBound + offset);
2208 Limb lowestLimb =
null;
2209 foreach (
Limb limb
in Limbs)
2212 if (lowestLimb ==
null)
2216 else if (limb.
SimPosition.Y < lowestLimb.SimPosition.Y)
2233 foreach (var limb
in Limbs)
2235 if (limb.
type == limbType)
2248 foreach (
Limb l
in Limbs)
2255 if (collider !=
null)
2264 if (LimbJoints !=
null)
2266 foreach (var joint
in LimbJoints)
2268 var j = joint?.Joint;
2282 for (
int i = list.Count - 1; i >= 0; i--)
2286 System.Diagnostics.Debug.Assert(list.Count == 0,
"Some ragdolls were not removed in Ragdoll.RemoveAll");
virtual CanEnterSubmarine CanEnterSubmarine
A special affliction type that gradually makes the character turn into another type of character....
static List< Limb > AttachHuskAppendage(Character character, AfflictionPrefabHusk matchingAffliction, Identifier huskedSpeciesName, ContentXElement appendageDefinition=null, Ragdoll ragdoll=null)
AfflictionPrefab is a prefab that defines a type of affliction that can be applied to a character....
static readonly PrefabCollection< AfflictionPrefab > Prefabs
AfflictionPrefabHusk is a special type of affliction that has added functionality for husk infection.
readonly CharacterParams Params
void SetInput(InputType inputType, bool hit, bool held)
readonly HashSet< Projectile > AttachedProjectiles
bool IsRemotelyControlled
Is the character controlled remotely (either by another player, or a server-side AIController)
virtual AIController AIController
float GetStatValue(StatTypes statType, bool includeSaved=true)
void ApplyStatusEffects(ActionType actionType, float deltaTime)
float DisableImpactDamageTimer
CharacterInventory Inventory
readonly HashSet< LatchOntoAI > Latchers
static Character Controlled
void LoadHeadAttachments()
override Vector2 Position
readonly AnimController AnimController
AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, Vector2 impulseDirection, float deltaTime, bool playSound=true)
List< CharacterStateInfo > MemLocalState
Item? SelectedItem
The primary selected item. It can be any device that character interacts with. This excludes items li...
Item GetItemInLimbSlot(InvSlotType limbSlot)
static CharacterPrefab FindByFilePath(string filePath)
ContentXElement ConfigElement
Identifier GetAttributeIdentifier(string key, string def)
virtual Vector2 WorldPosition
readonly ushort ID
Unique, but non-persistent identifier. Stays the same if the entities are created in the exactly same...
static ContentPackage VanillaContent
static NetworkMember NetworkMember
static Gap FindAdjacent(IEnumerable< Gap > gaps, Vector2 worldPos, float allowedOrthogonalDist, bool allowRoomToRoom=false)
static List< Gap > GapList
Decal AddDecal(UInt32 decalId, Vector2 worldPosition, float scale, bool isNetworkEvent, int? spriteIndex=null)
readonly List< Gap > ConnectedGaps
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)
void Drop(Character dropper, bool createNetworkEvent=true, bool setTransform=true)
List< ItemComponent > Components
LatchOntoAI(XElement element, EnemyAIController enemyAI)
readonly LimbParams Params
readonly List< WearableSprite > WearingItems
void MoveToPos(Vector2 pos, float force, bool pullFromCenter=false)
void Update(float deltaTime)
Vector2 PullJointWorldAnchorB
Vector2 PullJointWorldAnchorA
void HideAndDisable(float duration=0, bool ignoreCollisions=true)
readonly List< MapEntity > linkedTo
void ApplyLinearImpulse(Vector2 impulse)
static bool IsValidShape(float radius, float height, float width)
void MoveToTargetPosition(bool lerp=true)
void ApplyForce(Vector2 force, float maxVelocity=NetConfig.MaxPhysicsBodyVelocity)
bool SetTransform(Vector2 simPosition, float rotation, bool setPrevTransform=true)
void ApplyWaterForces()
Applies buoyancy, drag and angular drag caused by water
bool SetTransformIgnoreContacts(Vector2 simPosition, float rotation, bool setPrevTransform=true)
void UpdateDrawPosition(bool interpolate=true)
const float DefaultAngularDamping
void FindHull(Vector2? worldPosition=null, bool setSubmarine=true)
bool TryGetCollider(int index, out PhysicsBody collider)
bool shouldBeDraggedWithRope
void ResetJoints()
Resets the current joint values to the serialized joint params.
void ResetLimbs()
Resets the current limb values to the serialized limb params.
void ResetPullJoints(Func< Limb, bool > condition=null)
void ResetRagdoll()
Resets the serializable data to the currently selected ragdoll params. Always loads the xml stored on...
Ragdoll(Character character, string seed, RagdollParams ragdollParams=null)
List< Limb > GetConnectedLimbs(Limb limb)
void StopHangingWithRope()
abstract ? float HeadPosition
Vector2 GetColliderBottom()
bool OnLimbCollision(Fixture f1, Fixture f2, Contact contact)
bool SeverLimbJoint(LimbJoint limbJoint)
static void UpdateAll(float deltaTime, Camera cam)
List< PhysicsBody > collider
void LogAccessedRemovedCharacterError()
void CheckDistFromCollider()
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 SaveRagdoll(string fileNameWithoutExtension=null)
Saves all serializable data in the currently selected ragdoll params. This method should properly han...
void RemoveLimb(Limb limb)
void SetPosition(Vector2 simPosition, bool lerp=false, bool ignorePlatforms=true, bool forceMainLimbToCollider=false, bool moveLatchers=true)
abstract ? float HeadAngle
void RestoreTemporarilyDisabled()
bool? SimplePhysicsEnabled
Vector2 overrideTargetMovement
void TrySetLimbPosition(Limb limb, Vector2 original, Vector2 simPosition, float rotation, bool lerp=false, bool ignorePlatforms=true)
Vector2? GetMouthPosition()
float GetImpactDamage(float impact, float? impactTolerance=null)
abstract ? float TorsoPosition
readonly Character character
float GetSurfaceY()
Get the position of the surface of water at the position of the character, in display units (taking i...
void SubtractMass(Limb limb)
void ForceRefreshFloorY()
void UpdateRagdoll(float deltaTime, Camera cam)
float ColliderHeightFromFloor
In sim units. Joint scale applied.
bool HasMultipleLimbsOfSameType
Vector2 GetCenterOfMass()
void StopGettingDraggedWithRope()
void AddLimb(LimbParams limbParams)
virtual void Recreate(RagdollParams ragdollParams=null)
Call this to create the ragdoll from the RagdollParams.
void Teleport(Vector2 moveAmount, Vector2 velocityChange, bool detachProjectiles=true)
void MoveLimb(Limb limb, Vector2 pos, float amount, bool pullFromCenter=false)
if false, force is applied to the position of pullJoint
void HideAndDisable(LimbType limbType, float duration=0, bool ignoreCollisions=true)
void AddJoint(JointParams jointParams)
abstract ? float TorsoAngle
override bool Reset(bool forceReload=false)
Resets the current properties to the xml (stored in memory). Force reload reloads the file from disk.
void TryApplyVariantScale(XDocument variantFile)
bool Save(string fileNameWithoutExtension=null)
List< ColliderParams > Colliders
float ColliderHeightFromFloor
CanEnterSubmarine CanEnterSubmarine
List< JointParams > Joints
Submarine(SubmarineInfo info, bool showErrorMessages=true, Func< Submarine, List< MapEntity >> loadEntities=null, IdRemap linkedRemap=null)
static bool RectContains(Rectangle rect, Vector2 pos, bool inclusive=false)
static List< Submarine > Loaded
void UpdateTransform(bool interpolate=true)
static readonly Vector2 GridSize
static float LastPickedFraction
override Vector2? Position
static Body PickBody(Vector2 rayStart, Vector2 rayEnd, IEnumerable< Body > ignoredBodies=null, Category? collisionCategory=null, bool ignoreSensors=true, Predicate< Fixture > customPredicate=null, bool allowInsideFixture=false)
ActionType
ActionTypes define when a StatusEffect is executed.
StatTypes
StatTypes are used to alter several traits of a character. They are mostly used by talents.
@ Structure
Structures and hulls, but also items (for backwards support)!