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)
396 items = limbs?.ToDictionary(l => l.Params, l => l.WearingItems);
402 DebugConsole.ThrowError($
"Invalid collider dimensions (r: {limbParams.Radius}, h: {limbParams.Height}, w: {limbParams.Width}) on limb: {limbParams.Name}. Fixing.");
403 limbParams.Radius = 10;
410 DebugConsole.ThrowError($
"Invalid collider dimensions (r: {colliderParams.Radius}, h: {colliderParams.Height}, w: {colliderParams.Width}) on collider: {colliderParams.Name}. Fixing.");
411 colliderParams.Radius = 10;
417 UpdateCollisionCategories();
421 foreach (var kvp
in items)
425 if (
id > limbs.Length - 1) {
continue; }
426 var limb = limbs[id];
427 var itemList = kvp.Value;
434 bool inEditor =
false;
440 if (characterPrefab?.ConfigElement !=
null)
443 foreach (var huskAppendage
in mainElement.GetChildElements(
"huskappendage"))
445 if (!inEditor && huskAppendage.GetAttributeBool(
"onlyfromafflictions",
false)) {
continue; }
451 DebugConsole.ThrowError($
"Could not find an affliction of type 'huskinfection' that matches the affliction '{afflictionIdentifier}'!",
452 contentPackage: huskAppendage.ContentPackage);
473 DebugConsole.Log($
"Creating colliders from {RagdollParams.Name}.");
479 DebugConsole.ThrowError(
"Invalid collider dimensions: " + cParams.Name);
488 body.PhysEnabled =
false;
502 DebugConsole.Log($
"Creating joints from {RagdollParams.Name}.");
510 DebugConsole.ThrowError($
"Joint {i} null.");
514 UpdateCollisionCategories();
515 SetInitialLimbPositions();
518 private void SetInitialLimbPositions()
522 if (joint ==
null) {
continue; }
523 float angle = (joint.LowerLimit + joint.UpperLimit) / 2.0f;
524 joint.LimbB?.body?.SetTransform(
525 (joint.WorldAnchorA - MathUtils.RotatePointAroundTarget(joint.LocalAnchorB, Vector2.Zero, joint.BodyA.Rotation + angle,
true)),
526 joint.BodyA.Rotation + angle);
532 limbs?.ForEach(l => l.Remove());
533 DebugConsole.Log($
"Creating limbs from {RagdollParams.Name}.");
534 limbDictionary =
new Dictionary<LimbType, Limb>();
537 if (limbs.Contains(
null)) {
return; }
541 partial
void SetupDrawOrder();
575 Limbs.ForEach(l => l.LoadParams());
581 if (!checkLimbIndex(jointParams.Limb2,
"Limb1") || !checkLimbIndex(jointParams.Limb2,
"Limb2"))
596 bool checkLimbIndex(
int index,
string debugName)
598 if (index < 0 || index >= limbs.Length)
600 string errorMsg = $
"Failed to add a joint to character {character.Name}. {debugName} out of bounds (index: {index}, limbs: {limbs.Length}.";
601 DebugConsole.ThrowError(errorMsg, contentPackage: jointParams.Element?.ContentPackage);
604 GameAnalyticsManager.AddErrorEventOnce(
"Ragdoll.AddJoint:IndexOutOfRange", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
614 if (limbParams.ID < 0 || limbParams.ID > 255)
616 throw new Exception($
"Invalid limb params in limb \"{limbParams.Type}\". \"{limbParams.ID}\" is not a valid limb ID.");
618 byte ID = Convert.ToByte(limbParams.ID);
621 if (ID >=
Limbs.Length)
623 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.");
627 if (!limbDictionary.ContainsKey(limb.
type)) { limbDictionary.Add(limb.
type, limb); }
632 if (
Limbs.Contains(limb)) {
return; }
634 Array.Resize(ref limbs,
Limbs.Length + 1);
637 if (!limbDictionary.ContainsKey(limb.
type)) { limbDictionary.Add(limb.
type, limb); }
643 if (!
Limbs.Contains(limb))
return;
650 if (existingLimb == limb)
continue;
651 newLimbs[i] = existingLimb;
656 if (limbDictionary.ContainsKey(limb.type))
658 limbDictionary.
Remove(limb.type);
662 var otherLimb =
Limbs.FirstOrDefault(l => l != limb && l.type == limb.type);
663 if (otherLimb !=
null)
665 limbDictionary.Add(otherLimb.type, otherLimb);
675 if (attachedJoints.Length > 0)
681 if (attachedJoints.Contains(limbJoint))
continue;
682 newJoints[i] = limbJoint;
690 foreach (
LimbJoint limbJoint
in attachedJoints)
696 private enum LimbStairCollisionResponse
699 ClimbWithoutLimbCollision,
700 ClimbWithLimbCollision
706 if (f2.UserData is
Hull)
717 (f1.Body.UserData is
Limb limb && !limb.
Params.CanEnterSubmarine);
728 velocity -= sub.Velocity;
732 if (f2.Body.UserData is not
Structure structure)
738 impactQueue.Enqueue(
new Impact(f1, f2, contact, velocity));
749 if (structure.IsPlatform)
753 if (colliderBottom.Y < ConvertUnits.ToSimUnits(structure.Rect.Y - 5)) {
return false; }
754 if (f1.Body.Position.Y < ConvertUnits.ToSimUnits(structure.Rect.Y - 5)) {
return false; }
756 else if (structure.StairDirection !=
Direction.None)
763 var collisionResponse = getStairCollisionResponse();
764 if (collisionResponse == LimbStairCollisionResponse.ClimbWithLimbCollision)
770 if (collisionResponse == LimbStairCollisionResponse.DontClimbStairs) {
Stairs =
null; }
775 LimbStairCollisionResponse getStairCollisionResponse()
780 float stairBottomPos = ConvertUnits.ToSimUnits(structure.Rect.Y - structure.Rect.Height + 10);
781 if (colliderBottom.Y < stairBottomPos &&
targetMovement.Y < 0.5f) {
return LimbStairCollisionResponse.DontClimbStairs; }
786 return LimbStairCollisionResponse.DontClimbStairs;
790 if (
targetMovement.Y >= 0.0f && colliderBottom.Y >= ConvertUnits.ToSimUnits(structure.Rect.Y -
Submarine.
GridSize.Y * 5)) {
return LimbStairCollisionResponse.DontClimbStairs; }
793 if (contact.Manifold.LocalNormal.Y < 0.0f)
795 return Stairs != structure
796 ? LimbStairCollisionResponse.DontClimbStairs
797 : LimbStairCollisionResponse.ClimbWithoutLimbCollision;
801 contact.GetWorldManifold(out _, out FarseerPhysics.Common.FixedArray2<Vector2> points);
802 if (points[0].Y >
Collider.
SimPosition.Y) {
return LimbStairCollisionResponse.DontClimbStairs; }
807 return LimbStairCollisionResponse.ClimbWithLimbCollision;
813 impactQueue.Enqueue(
new Impact(f1, f2, contact, velocity));
819 private void ApplyImpact(Fixture f1, Fixture f2, Vector2 localNormal, Vector2 impactPos, Vector2 velocity)
823 if (f2.Body?.UserData is Item)
830 Vector2 normal = localNormal;
831 float impact = Vector2.Dot(velocity, -normal);
834 bool isNotRemote =
true;
841 if (impact > impactTolerance)
843 impactPos = ConvertUnits.ToDisplayUnits(impactPos);
848 var should = GameMain.LuaCs.Hook.Call<
float?>(
"changeFallDamage", impactDamage,
character, impactPos, velocity);
852 impactDamage = should.Value;
856 character.
AddDamage(impactPos, AfflictionPrefab.ImpactDamage.Instantiate(impactDamage).ToEnumerable(), 0.0f,
true);
868 ImpactProjSpecific(impact, f1.Body);
874 return Math.Min((impact - tolerance) * ImpactDamageMultiplayer,
character.
MaxVitality * MaxImpactDamage);
877 private readonly List<Limb> connectedLimbs =
new List<Limb>();
878 private readonly List<LimbJoint> checkedJoints =
new List<LimbJoint>();
890 if (limbDiff.LengthSquared() < 0.0001f) { limbDiff = Rand.Vector(1.0f); }
891 limbDiff = Vector2.Normalize(limbDiff);
892 float mass = limbJoint.
BodyA.Mass + limbJoint.
BodyB.Mass;
896 connectedLimbs.Clear();
897 checkedJoints.Clear();
901 if (connectedLimbs.Contains(limb)) {
continue; }
919 SeverLimbJointProjSpecific(limbJoint, playSound:
true);
927 partial
void SeverLimbJointProjSpecific(
LimbJoint limbJoint,
bool playSound);
931 connectedLimbs.Clear();
932 checkedJoints.Clear();
934 return connectedLimbs;
939 connectedLimbs.Add(limb);
943 if (joint.
IsSevered || checkedJoints.Contains(joint)) {
continue; }
944 if (joint.
LimbA == limb)
946 if (!connectedLimbs.Contains(joint.LimbB))
948 checkedJoints.Add(joint);
952 else if (joint.LimbB == limb)
954 if (!connectedLimbs.Contains(joint.
LimbA))
956 checkedJoints.Add(joint);
963 partial
void ImpactProjSpecific(
float impact, Body body);
995 partial
void FlipProjSpecific();
1000 if (!
Limbs.Any(l => !l.IsSevered && l.body.Enabled))
1005 Vector2 centerOfMass = Vector2.Zero;
1006 float totalMass = 0.0f;
1011 totalMass += limb.
Mass;
1015 centerOfMass /= totalMass;
1017 if (!MathUtils.IsValid(centerOfMass))
1019 string errorMsg =
"Ragdoll.GetCenterOfMass returned an invalid value (" + centerOfMass +
"). Limb positions: {"
1020 +
string.Join(
", ", limbs.Select(l => l.SimPosition)) +
"}, total mass: " + totalMass +
".";
1021 DebugConsole.ThrowError(errorMsg);
1022 GameAnalyticsManager.AddErrorEventOnce(
"Ragdoll.GetCenterOfMass", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
1026 return centerOfMass;
1031 public void MoveLimb(
Limb limb, Vector2 pos,
float amount,
bool pullFromCenter =
false)
1033 limb.
MoveToPos(pos, amount, pullFromCenter);
1038 for (
int i = 0; i <
Limbs.Length; i++)
1040 if (
Limbs[i] ==
null) {
continue; }
1041 if (condition !=
null && !condition(
Limbs[i])) {
continue; }
1054 public void FindHull(Vector2? worldPosition =
null,
bool setSubmarine =
true)
1056 Vector2 findPos = worldPosition ==
null ? this.
WorldPosition : (Vector2)worldPosition;
1057 if (!MathUtils.IsValid(findPos))
1059 GameAnalyticsManager.AddErrorEventOnce(
1060 "Ragdoll.FindHull:InvalidPosition",
1061 GameAnalyticsManager.ErrorSeverity.Error,
1062 "Attempted to find a hull at an invalid position (" + findPos +
")\n" + Environment.StackTrace.CleanupStackTrace());
1081 Vector2 moveDir = hullDiff.LengthSquared() < 0.001f ? Vector2.UnitY : Vector2.Normalize(hullDiff);
1084 if (MathUtils.GetLineRectangleIntersection(
1088 out Vector2 intersection))
1124 Teleport(ConvertUnits.ToSimUnits(prevSubPos - newSubPos), Vector2.Zero);
1132 attachedProjectile.Item.CurrentHull =
currentHull;
1138 private void PreventOutsideCollision()
1143 foreach (Gap gap
in connectedGaps)
1145 if (gap.IsHorizontal)
1164 gap.RefreshOutsideCollider();
1168 public void Teleport(Vector2 moveAmount, Vector2 velocityChange,
bool detachProjectiles =
true)
1176 while (ce !=
null && ce.Contact !=
null)
1178 ce.Contact.Enabled =
false;
1201 private void UpdateCollisionCategories()
1204 Physics.CollisionLevel | Physics.CollisionWall
1205 : Physics.CollisionWall;
1208 wall | Physics.CollisionProjectile | Physics.CollisionStairs
1209 : wall | Physics.CollisionProjectile | Physics.CollisionPlatform | Physics.CollisionStairs;
1211 if (collisionCategory == prevCollisionCategory) {
return; }
1212 prevCollisionCategory = collisionCategory;
1216 foreach (Limb limb
in Limbs)
1218 if (limb.IgnoreCollisions || limb.IsSevered) {
continue; }
1222 limb.body.CollidesWith = collisionCategory;
1226 DebugConsole.ThrowError(
"Failed to update ragdoll limb collisioncategories", e);
1236 private float bodyInRestTimer;
1238 private float BodyInRestDelay = 1.0f;
1242 get {
return bodyInRestTimer > BodyInRestDelay; }
1249 bodyInRestTimer = value ? BodyInRestDelay : 0.0f;
1260 while (impactQueue.Count > 0)
1262 var impact = impactQueue.Dequeue();
1263 ApplyImpact(impact.F1, impact.F2, impact.LocalNormal, impact.ImpactPos, impact.Velocity);
1268 UpdateNetPlayerPosition(deltaTime);
1270 UpdateCollisionCategories();
1273 PreventOutsideCollision();
1275 CheckBodyInRest(deltaTime);
1277 splashSoundTimer -= deltaTime;
1308 headInWater =
false;
1309 RefreshFloorY(deltaTime, ignoreStairs:
Stairs ==
null);
1319 headInWater =
false;
1321 RefreshFloorY(deltaTime, ignoreStairs:
Stairs ==
null);
1324 (
float waterSurfaceDisplayUnits,
float ceilingDisplayUnits) = GetWaterSurfaceAndCeilingY();
1325 float waterSurfaceY = ConvertUnits.ToSimUnits(waterSurfaceDisplayUnits);
1326 float ceilingY = ConvertUnits.ToSimUnits(ceilingDisplayUnits);
1335 var lowerHull =
Hull.
FindHull(ConvertUnits.ToDisplayUnits(colliderBottom), useWorldCoordinates:
false);
1336 if (lowerHull !=
null)
1338 floorY = ConvertUnits.ToSimUnits(lowerHull.Rect.Y - lowerHull.Rect.Height);
1346 if (waterSurfaceY -
floorY > standHeight * 0.8f ||
1347 ceilingY -
floorY < standHeight * 0.8f)
1355 UpdateHullFlowForces(deltaTime);
1370 bool prevInWater = limb.
InWater;
1377 else if (newHull ==
null)
1381 if (limb.
type ==
LimbType.Head) { headInWater =
true; }
1397 Splash(limb, newHull);
1403 newHull.
WaveVel[n] += MathHelper.Clamp(impulse.Y, -5.0f, 5.0f);
1407 limb.
Hull = newHull;
1417 const float LevitationSpeedMultiplier = 5f;
1420 float slopePull = 0f;
1425 slopePull = Math.Abs(
movement.X * steepness) / LevitationSpeedMultiplier;
1433 yVelocity = Math.Sign(yVelocity);
1436 yVelocity -= slopePull * LevitationSpeedMultiplier;
1455 UpdateProjSpecific(deltaTime, cam);
1459 private void CheckBodyInRest(
float deltaTime)
1465 bodyInRestTimer = 0.0f;
1466 foreach (Limb limb
in Limbs)
1468 limb.body.PhysEnabled =
true;
1471 else if (
Limbs.All(l => l !=
null && !l.body.Enabled || l.LinearVelocity.LengthSquared() < 0.001f))
1473 bodyInRestTimer += deltaTime;
1474 if (bodyInRestTimer > BodyInRestDelay)
1476 foreach (Limb limb
in Limbs)
1478 limb.body.PhysEnabled =
false;
1485 private int validityResets;
1486 private bool CheckValidity()
1490 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));
1494 bool isColliderValid = CheckValidity(
Collider);
1496 bool limbsValid =
true;
1497 foreach (Limb limb
in limbs)
1499 if (limb?.body ==
null || !limb.body.Enabled) {
continue; }
1500 if (!CheckValidity(limb.body))
1503 limb.body.ResetDynamics();
1507 bool isValid = isColliderValid && limbsValid;
1511 if (validityResets > 3)
1514 DebugConsole.ThrowError(
"Invalid ragdoll physics. Ragdoll frozen to prevent crashes.");
1517 foreach (Limb limb
in Limbs)
1520 limb.body?.ResetDynamics();
1528 private bool CheckValidity(PhysicsBody body)
1530 string errorMsg =
null;
1531 if (!MathUtils.IsValid(body.SimPosition) || Math.Abs(body.SimPosition.X) > 1e10f || Math.Abs(body.SimPosition.Y) > 1e10f)
1533 errorMsg = GetBodyName() +
" position invalid (" + body.SimPosition +
", character: [name]).";
1535 else if (!MathUtils.IsValid(body.LinearVelocity) || Math.Abs(body.LinearVelocity.X) > 1000f || Math.Abs(body.LinearVelocity.Y) > 1000f)
1537 errorMsg = GetBodyName() +
" velocity invalid (" + body.LinearVelocity +
", character: [name]).";
1539 else if (!MathUtils.IsValid(body.Rotation))
1541 errorMsg = GetBodyName() +
" rotation invalid (" + body.Rotation +
", character: [name]).";
1543 else if (!MathUtils.IsValid(body.AngularVelocity) || Math.Abs(body.AngularVelocity) > 1000f)
1545 errorMsg = GetBodyName() +
" angular velocity invalid (" + body.AngularVelocity +
", character: [name]).";
1547 if (errorMsg !=
null)
1551 errorMsg +=
" Ragdoll controlled remotely.";
1555 errorMsg +=
" Simple physics enabled.";
1557 if (GameMain.NetworkMember !=
null)
1559 errorMsg += GameMain.NetworkMember.IsClient ?
" Playing as a client." :
" Hosting a server.";
1563 DebugConsole.ThrowError(errorMsg.Replace(
"[name]",
Character.
Name));
1565 DebugConsole.NewMessage(errorMsg.Replace(
"[name]",
Character.
Name), Color.Red);
1567 GameAnalyticsManager.AddErrorEventOnce(
"Ragdoll.CheckValidity:" +
character.
ID, GameAnalyticsManager.ErrorSeverity.Error, errorMsg.Replace(
"[name]",
Character.
SpeciesName.Value));
1573 foreach (Limb otherLimb
in Limbs)
1576 otherLimb.body.ResetDynamics();
1578 SetInitialLimbPositions();
1582 string GetBodyName()
1584 return body.UserData is Limb limb ?
"Limb (" + limb.type +
")" :
"Collider";
1592 if (!accessRemovedCharacterErrorShown)
1594 string errorMsg =
"Attempted to access a potentially removed ragdoll. Character: " +
character.
Name +
", id: " +
character.
ID +
", removed: " +
character.
Removed +
", ragdoll removed: " + !list.Contains(
this);
1595 errorMsg +=
'\n' + Environment.StackTrace.CleanupStackTrace();
1596 DebugConsole.ThrowError(errorMsg);
1597 GameAnalyticsManager.AddErrorEventOnce(
1598 "Ragdoll:AccessRemoved",
1599 GameAnalyticsManager.ErrorSeverity.Error,
1600 "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());
1601 accessRemovedCharacterErrorShown =
true;
1605 partial
void UpdateProjSpecific(
float deltaTime,
Camera cam);
1607 partial
void Splash(
Limb limb,
Hull limbHull);
1609 private void UpdateHullFlowForces(
float deltaTime)
1613 const float StunForceThreshold = 5.0f;
1614 const float StunDuration = 0.5f;
1615 const float ToleranceIncreaseSpeed = 5.0f;
1616 const float ToleranceDecreaseSpeed = 1.0f;
1619 const float DistanceFactor = 0.5f;
1620 const float ForceMultiplier = 0.035f;
1622 Vector2 flowForce = Vector2.Zero;
1623 foreach (Gap gap
in Gap.GapList)
1625 if (gap.Open <= 0.0f || !gap.linkedTo.Contains(
currentHull) || gap.LerpedFlowForce.LengthSquared() < 0.01f) {
continue; }
1627 flowForce += Vector2.Normalize(gap.LerpedFlowForce) * (Math.Max(gap.LerpedFlowForce.Length() - dist, 0.0f) * ForceMultiplier);
1634 float flowForceMagnitude = flowForce.Length();
1635 float limbMultipier = limbs.Count(l => l.InWater) / (float)limbs.Length;
1637 if ((flowForceMagnitude * limbMultipier) - flowStunTolerance > StunForceThreshold)
1640 flowStunTolerance = Math.Max(flowStunTolerance, flowForceMagnitude);
1645 float shakeStrength = Math.Min(flowForceMagnitude / 10.0f, 5.0f) * limbMultipier;
1646 Screen.Selected.Cam.Shake = Math.Max(Screen.Selected.Cam.Shake, shakeStrength);
1649 if (flowForceMagnitude > 0.0001f)
1651 flowForce = Vector2.Normalize(flowForce) * Math.Max(flowForceMagnitude - flowForceTolerance, 0.0f);
1654 if (flowForceTolerance <= flowForceMagnitude * 1.5f &&
inWater)
1658 flowForceTolerance += deltaTime * ToleranceIncreaseSpeed;
1659 flowStunTolerance = Math.Max(flowStunTolerance, flowForceTolerance);
1663 flowForceTolerance = Math.Max(flowForceTolerance - deltaTime * ToleranceDecreaseSpeed, 0.0f);
1664 flowStunTolerance = Math.Max(flowStunTolerance - deltaTime * ToleranceDecreaseSpeed, 0.0f);
1667 if (flowForce.LengthSquared() > 0.001f)
1670 foreach (Limb limb
in limbs)
1672 if (!limb.InWater) {
continue; }
1673 limb.body.ApplyForce(flowForce * (limb.Mass /
Mass * limbs.Length));
1680 lastFloorCheckPos = Vector2.Zero;
1684 private const float FloorYStaleTime = 1;
1685 private float floorYCheckTimer;
1686 private void RefreshFloorY(
float deltaTime,
bool ignoreStairs =
false)
1688 floorYCheckTimer -= deltaTime;
1690 if (floorYCheckTimer < 0 ||
1691 lastFloorCheckIgnoreStairs != ignoreStairs ||
1693 Vector2.DistanceSquared(lastFloorCheckPos, refBody.
SimPosition) > 0.1f * 0.1f)
1697 lastFloorCheckIgnoreStairs = ignoreStairs;
1700 floorYCheckTimer = FloorYStaleTime * Rand.Range(0.9f, 1.1f);
1704 private float GetFloorY(Vector2 simPosition,
bool ignoreStairs =
false)
1709 Vector2 rayStart = simPosition;
1714 Vector2 rayEnd = rayStart -
new Vector2(0.0f, height * 2f);
1715 Vector2 colliderBottomDisplay = ConvertUnits.ToDisplayUnits(
GetColliderBottom());
1717 Fixture standOnFloorFixture =
null;
1718 float standOnFloorFraction = 1;
1719 float closestFraction = 1;
1720 GameMain.World.RayCast((fixture, point, normal, fraction) =>
1722 switch (fixture.CollisionCategories)
1724 case Physics.CollisionStairs:
1725 if (inWater && TargetMovement.Y < 0.5f) { return -1; }
1729 Structure structure = fixture.Body.UserData as Structure;
1730 if (colliderBottomDisplay.Y >= structure.Rect.Y - structure.Rect.Height + 30 || TargetMovement.Y > 0.5f || Stairs != null)
1732 standOnFloorFraction = fraction;
1733 standOnFloorFixture = fixture;
1737 if (ignoreStairs) {
return -1; }
1739 case Physics.CollisionPlatform:
1744 if (colliderBottomDisplay.Y >= platform.Rect.Y - 16 || (
targetMovement.Y > 0.0f &&
Stairs ==
null))
1746 standOnFloorFraction = fraction;
1747 standOnFloorFixture = fixture;
1751 if (colliderBottomDisplay.Y < platform.Rect.Y - 16 && (
targetMovement.Y <= 0.0f ||
Stairs !=
null))
return -1;
1754 case Physics.CollisionWall:
1755 case Physics.CollisionLevel:
1756 if (!fixture.CollidesWith.HasFlag(Physics.CollisionCharacter)) { return -1; }
1757 if (fixture.Body.UserData is Submarine &&
character.
Submarine !=
null) { return -1; }
1758 if (fixture.IsSensor) { return -1; }
1759 if (fraction < standOnFloorFraction)
1761 standOnFloorFraction = fraction;
1762 standOnFloorFixture = fixture;
1766 System.Diagnostics.Debug.Assert(
false,
"Floor raycast should not have hit a fixture with the collision category " + fixture.CollisionCategories);
1770 if (fraction < closestFraction)
1773 closestFraction = fraction;
1776 return closestFraction;
1777 }, rayStart, rayEnd, Physics.CollisionStairs | Physics.CollisionPlatform | Physics.CollisionWall | Physics.CollisionLevel);
1779 if (standOnFloorFixture !=
null && !IsHangingWithRope)
1781 floorFixture = standOnFloorFixture;
1782 standOnFloorY = rayStart.Y + (rayEnd.Y - rayStart.Y) * standOnFloorFraction;
1783 if (rayStart.Y - standOnFloorY < Collider.Height * 0.5f + Collider.Radius + ColliderHeightFromFloor * 1.2f)
1786 if (standOnFloorFixture.CollisionCategories == Physics.CollisionStairs)
1788 Stairs = standOnFloorFixture.Body.UserData as
Structure;
1793 if (closestFraction >= 1)
1795 floorNormal = Vector2.UnitY;
1796 if (CurrentHull ==
null)
1802 float hullBottom = currentHull.
Rect.Y - currentHull.
Rect.Height;
1806 if (!gap.IsRoomToRoom || gap.Open < 1.0f || gap.ConnectedDoor !=
null || gap.IsHorizontal) {
continue; }
1807 if (WorldPosition.X > gap.WorldRect.X && WorldPosition.X < gap.WorldRect.Right && gap.WorldPosition.Y < WorldPosition.Y)
1809 var lowerHull = gap.linkedTo[0] == currentHull ? gap.
linkedTo[1] : gap.linkedTo[0];
1810 hullBottom = Math.Min(hullBottom, lowerHull.Rect.Y - lowerHull.Rect.Height);
1813 return ConvertUnits.ToSimUnits(hullBottom);
1818 return rayStart.Y + (rayEnd.Y - rayStart.Y) * closestFraction;
1827 return GetWaterSurfaceAndCeilingY().WaterSurfaceY;
1833 private (
float WaterSurfaceY,
float CeilingY) GetWaterSurfaceAndCeilingY()
1836 if (currentHull ==
null || character.CurrentHull ==
null)
1838 return (
float.PositiveInfinity,
float.PositiveInfinity);
1841 float surfaceY = currentHull.
Surface;
1842 float ceilingY = currentHull.
Rect.Y;
1843 float surfaceThreshold = ConvertUnits.ToDisplayUnits(Collider.SimPosition.Y + 1.0f);
1846 if (currentHull.
Rect.Y - currentHull.
Surface < 5.0f)
1848 GetSurfacePos(currentHull, ref surfaceY, ref ceilingY);
1849 void GetSurfacePos(Hull hull, ref
float prevSurfacePos, ref
float ceilingPos)
1851 if (prevSurfacePos > surfaceThreshold) {
return; }
1852 foreach (Gap gap
in hull.ConnectedGaps)
1854 if (gap.IsHorizontal || gap.Open <= 0.0f || gap.WorldPosition.Y < hull.WorldPosition.Y) {
continue; }
1855 if (Collider.SimPosition.X < ConvertUnits.ToSimUnits(gap.Rect.X) || Collider.SimPosition.X > ConvertUnits.ToSimUnits(gap.Rect.Right)) {
continue; }
1858 if (!gap.IsRoomToRoom && gap.Position.Y > hull.Position.Y)
1860 ceilingPos += 100000.0f;
1861 prevSurfacePos += 100000.0f;
1865 foreach (var linkedTo
in gap.linkedTo)
1867 if (linkedTo is Hull otherHull && otherHull != hull && otherHull != currentHull)
1869 prevSurfacePos = Math.Max(surfaceY, otherHull.Surface);
1870 ceilingPos = Math.Max(ceilingPos, otherHull.Rect.Y);
1871 GetSurfacePos(otherHull, ref prevSurfacePos, ref ceilingPos);
1878 return (surfaceY, ceilingY);
1881 public void SetPosition(Vector2 simPosition,
bool lerp =
false,
bool ignorePlatforms =
true,
bool forceMainLimbToCollider =
false,
bool moveLatchers =
true)
1883 if (!MathUtils.IsValid(simPosition))
1885 DebugConsole.ThrowError(
"Attempted to move a ragdoll (" + character.Name +
") to an invalid position (" + simPosition +
"). " + Environment.StackTrace.CleanupStackTrace());
1886 GameAnalyticsManager.AddErrorEventOnce(
1887 "Ragdoll.SetPosition:InvalidPosition",
1888 GameAnalyticsManager.ErrorSeverity.Error,
1889 "Attempted to move a ragdoll (" + character.SpeciesName +
") to an invalid position (" + simPosition +
"). " + Environment.StackTrace.CleanupStackTrace());
1892 if (MainLimb ==
null) {
return; }
1894 Vector2 limbMoveAmount = forceMainLimbToCollider ? simPosition - MainLimb.SimPosition : simPosition - Collider.SimPosition;
1899 const float ForceDeattachThreshold = 10.0f;
1900 if (limbMoveAmount.LengthSquared() > ForceDeattachThreshold * ForceDeattachThreshold &&
1903 var target = enemyAI.LatchOntoAI.TargetCharacter;
1906 target.Latchers.ForEachMod(l => l?.DeattachFromBody(reset:
true));
1907 target.Latchers.Clear();
1909 enemyAI.LatchOntoAI.DeattachFromBody(reset:
true);
1916 Collider.TargetPosition = simPosition;
1917 Collider.MoveToTargetPosition(
true);
1921 Collider.SetTransform(simPosition, Collider.Rotation);
1924 if (!MathUtils.NearlyEqual(limbMoveAmount, Vector2.Zero))
1926 foreach (
Limb limb
in Limbs)
1930 Vector2 movePos = limb.
SimPosition + limbMoveAmount;
1931 TrySetLimbPosition(limb, simPosition, movePos, limb.
Rotation, lerp, ignorePlatforms);
1939 public bool IsHoldingToRope {
get;
private set; }
1945 public bool IsHangingWithRope {
get;
private set; }
1951 public bool IsDraggedWithRope {
get;
private set; }
1956 shouldHangWithRope =
true;
1957 IsHangingWithRope =
true;
1960 levitatingCollider =
false;
1965 shouldHoldToRope =
true;
1966 IsHoldingToRope =
true;
1971 shouldBeDraggedWithRope =
true;
1972 IsDraggedWithRope =
true;
1977 shouldHangWithRope =
false;
1978 IsHangingWithRope =
false;
1983 shouldHoldToRope =
false;
1984 IsHoldingToRope =
false;
1989 shouldBeDraggedWithRope =
false;
1990 IsDraggedWithRope =
false;
1993 protected void TrySetLimbPosition(
Limb limb, Vector2 original, Vector2 simPosition,
float rotation,
bool lerp =
false,
bool ignorePlatforms =
true)
1995 Vector2 movePos = simPosition;
1997 if (Vector2.DistanceSquared(original, simPosition) > 0.0001f)
1999 Category collisionCategory = Physics.CollisionWall | Physics.CollisionLevel;
2000 if (!ignorePlatforms) { collisionCategory |= Physics.CollisionPlatform; }
2024 foreach (var attachedProjectile
in character.AttachedProjectiles)
2026 if (attachedProjectile.IsAttachedTo(limb.body))
2028 attachedProjectile.Item.SetTransform(
2029 attachedProjectile.Item.SimPosition + (movePos - prevPosition),
2030 attachedProjectile.Item.body.Rotation,
2031 findNewHull:
false);
2037 private bool collisionsDisabled;
2038 private double lastObstacleRayCastTime;
2042 float allowedDist = Math.Max(Math.Max(Collider.Radius, Collider.Width), Collider.Height) * 2.0f;
2043 allowedDist = Math.Max(allowedDist, 1.0f);
2044 float resetDist = allowedDist * 5.0f;
2046 float obstacleCheckDist = 0.3f;
2048 Vector2 diff = Collider.SimPosition - MainLimb.SimPosition;
2049 float distSqrd = diff.LengthSquared();
2051 bool shouldReset = distSqrd > resetDist * resetDist;
2052 if (!shouldReset && distSqrd > obstacleCheckDist * obstacleCheckDist)
2054 if (Timing.TotalTime > lastObstacleRayCastTime + 1 &&
2055 Submarine.
PickBody(Collider.SimPosition, MainLimb.SimPosition, collisionCategory: Physics.CollisionWall) !=
null)
2058 lastObstacleRayCastTime = Timing.TotalTime;
2065 SetPosition(Collider.SimPosition, lerp:
true, forceMainLimbToCollider:
true);
2067 else if (distSqrd > allowedDist * allowedDist)
2072 Vector2 forceDir = diff / (float)Math.Sqrt(distSqrd);
2073 foreach (
Limb limb
in Limbs)
2080 collisionsDisabled =
true;
2082 else if (collisionsDisabled)
2085 SetPosition(Collider.SimPosition, lerp:
true);
2086 collisionsDisabled =
false;
2088 prevCollisionCategory = Category.None;
2092 partial
void UpdateNetPlayerPositionProjSpecific(
float deltaTime,
float lowestSubPos);
2093 private void UpdateNetPlayerPosition(
float deltaTime)
2097 float lowestSubPos =
float.MaxValue;
2100 lowestSubPos = ConvertUnits.ToSimUnits(
Submarine.
Loaded.Min(s => s.HiddenSubPosition.Y - s.Borders.Height - 128.0f));
2101 for (
int i = 0; i < character.MemState.Count; i++)
2103 if (character.Submarine ==
null)
2106 if (character.MemState[i].Position.Y > lowestSubPos)
2107 character.MemState[i].TransformInToOutside();
2109 else if (currentHull?.Submarine !=
null)
2112 if (character.MemState[i].Position.Y < lowestSubPos)
2113 character.MemState[i].TransformOutToInside(currentHull.
Submarine);
2118 UpdateNetPlayerPositionProjSpecific(deltaTime, lowestSubPos);
2126 if (limbDictionary.TryGetValue(limbType, out
Limb limb))
2128 if (excludeSevered && limb.IsSevered)
2133 if (limb ==
null && HasMultipleLimbsOfSameType)
2136 foreach (var l
in limbs)
2138 if (l.type != limbType) {
continue; }
2139 if (!excludeSevered || !l.IsSevered)
2152 if (mouthLimb ==
null) {
return null; }
2153 float cos = (float)Math.Cos(mouthLimb.
Rotation);
2154 float sin = (float)Math.Sin(mouthLimb.
Rotation);
2156 Vector2 offset =
new Vector2(mouthLimb.
MouthPos.X * bodySize.X / 2, mouthLimb.
MouthPos.Y * bodySize.Y / 2);
2157 return mouthLimb.
SimPosition +
new Vector2(offset.X * cos - offset.Y * sin, offset.X * sin + offset.Y * cos);
2162 float offset = 0.0f;
2164 if (!character.IsDead && character.Stun <= 0.0f && !character.IsIncapacitated)
2166 offset = -ColliderHeightFromFloor;
2169 float lowestBound = Collider.SimPosition.Y;
2170 if (Collider.FarseerBody.FixtureList !=
null)
2172 for (
int i = 0; i < Collider.FarseerBody.FixtureList.Count; i++)
2174 Collider.FarseerBody.GetTransform(out FarseerPhysics.Common.Transform transform);
2175 Collider.FarseerBody.FixtureList[i].Shape.ComputeAABB(out FarseerPhysics.Collision.AABB aabb, ref transform, i);
2177 lowestBound = Math.Min(aabb.LowerBound.Y, lowestBound);
2180 return new Vector2(Collider.SimPosition.X, lowestBound + offset);
2185 Limb lowestLimb =
null;
2186 foreach (
Limb limb
in Limbs)
2189 if (lowestLimb ==
null)
2193 else if (limb.
SimPosition.Y < lowestLimb.SimPosition.Y)
2210 foreach (var limb
in Limbs)
2212 if (limb.
type == limbType)
2225 foreach (
Limb l
in Limbs)
2232 if (collider !=
null)
2241 if (LimbJoints !=
null)
2243 foreach (var joint
in LimbJoints)
2245 var j = joint?.Joint;
2259 for (
int i = list.Count - 1; i >= 0; i--)
2263 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, 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
void LoadHeadAttachments()
static Character? Controlled
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 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 CharacterEditor.CharacterEditorScreen CharacterEditorScreen
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)
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
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)
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()
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)
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.
abstract ? float TorsoPosition
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.
void ResetRagdoll(bool forceReload=false)
Resets the serializable data to the currently selected ragdoll params. Force reloading always loads t...
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.