Merge branch 'master' of https://github.com/Regalis11/Barotrauma into develop

This commit is contained in:
EvilFactory
2023-12-14 11:56:39 -03:00
376 changed files with 7775 additions and 2879 deletions
@@ -552,7 +552,8 @@ namespace Barotrauma
#if DEBUG
if (handlePos[i].LengthSquared() > ArmLength)
{
DebugConsole.AddWarning($"Aim position for the item {item.Name} may be incorrect (further than the length of the character's arm)");
DebugConsole.AddWarning($"Aim position for the item {item.Name} may be incorrect (further than the length of the character's arm)",
item.Prefab.ContentPackage);
}
#endif
HandIK(
@@ -10,6 +10,17 @@ namespace Barotrauma
{
class HumanoidAnimController : AnimController
{
private const float SteepestWalkableSlopeAngleDegrees = 50f;
private const float SlowlyWalkableSlopeAngleDegrees = 30f;
private static readonly float SteepestWalkableSlopeNormalX =
MathF.Sin(MathHelper.ToRadians(SteepestWalkableSlopeAngleDegrees));
private static readonly float SlowlyWalkableSlopeNormalX =
MathF.Sin(MathHelper.ToRadians(SlowlyWalkableSlopeAngleDegrees));
private const float MaxSpeedOnStairs = 1.7f;
private const float SteepSlopePushMagnitude = MaxSpeedOnStairs;
public override RagdollParams RagdollParams
{
get { return HumanRagdollParams; }
@@ -150,7 +161,7 @@ namespace Barotrauma
private readonly float movementLerp;
private float cprAnimTimer,cprPump;
private float cprAnimTimer, cprPumpTimer;
private float fallingProneAnimTimer;
const float FallingProneAnimDuration = 1.0f;
@@ -243,14 +254,17 @@ namespace Barotrauma
if (MainLimb == null) { return; }
levitatingCollider = !IsHanging;
if ((character.SelectedItem?.GetComponent<Controller>()?.ControlCharacterPose ?? false) ||
(character.SelectedSecondaryItem?.GetComponent<Controller>()?.ControlCharacterPose ?? false) ||
character.SelectedSecondaryItem?.GetComponent<Ladder>() != null ||
(ForceSelectAnimationType != AnimationType.Crouch && ForceSelectAnimationType != AnimationType.NotDefined))
if (onGround && character.CanMove)
{
Crouching = false;
if ((character.SelectedItem?.GetComponent<Controller>()?.ControlCharacterPose ?? false) ||
(character.SelectedSecondaryItem?.GetComponent<Controller>()?.ControlCharacterPose ?? false) ||
character.SelectedSecondaryItem?.GetComponent<Ladder>() != null ||
(ForceSelectAnimationType != AnimationType.Crouch && ForceSelectAnimationType != AnimationType.NotDefined))
{
Crouching = false;
}
ColliderIndex = Crouching && !swimming ? 1 : 0;
}
ColliderIndex = Crouching && !swimming ? 1 : 0;
//stun (= disable the animations) if the ragdoll receives a large enough impact
if (strongestImpact > 0.0f)
@@ -276,7 +290,7 @@ namespace Barotrauma
if (!character.CanMove)
{
if (fallingProneAnimTimer < FallingProneAnimDuration)
if (fallingProneAnimTimer < FallingProneAnimDuration && onGround)
{
fallingProneAnimTimer += deltaTime;
UpdateFallingProne(1.0f);
@@ -285,7 +299,12 @@ namespace Barotrauma
Collider.FarseerBody.FixedRotation = false;
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
{
Collider.Enabled = false;
if (Collider.Enabled)
{
//deactivating the collider -> make the main limb inherit the collider's velocity because it'll control the movement now
MainLimb.body.LinearVelocity = Collider.LinearVelocity;
Collider.Enabled = false;
}
Collider.LinearVelocity = MainLimb.LinearVelocity;
Collider.SetTransformIgnoreContacts(MainLimb.SimPosition, MainLimb.Rotation);
//reset pull joints to prevent the character from "hanging" mid-air if pull joints had been active when the character was still moving
@@ -386,6 +405,12 @@ namespace Barotrauma
DragCharacter(character.SelectedCharacter, deltaTime);
}
if (Anim != Animation.CPR)
{
cprAnimTimer = 0.0f;
cprPumpTimer = 0.0f;
}
switch (Anim)
{
case Animation.Climbing:
@@ -487,10 +512,14 @@ namespace Barotrauma
Limb leftLeg = GetLimb(LimbType.LeftLeg);
Limb rightLeg = GetLimb(LimbType.RightLeg);
bool onSlopeThatMakesSlow = Math.Abs(floorNormal.X) > SlowlyWalkableSlopeNormalX;
bool slowedDownBySlope = onSlopeThatMakesSlow && Math.Sign(floorNormal.X) == -Math.Sign(TargetMovement.X);
bool onSlopeTooSteepToClimb = Math.Abs(floorNormal.X) > SteepestWalkableSlopeNormalX;
float walkCycleMultiplier = 1.0f;
if (Stairs != null)
if (Stairs != null || slowedDownBySlope)
{
TargetMovement = new Vector2(MathHelper.Clamp(TargetMovement.X, -1.7f, 1.7f), TargetMovement.Y);
TargetMovement = new Vector2(MathHelper.Clamp(TargetMovement.X, -MaxSpeedOnStairs, MaxSpeedOnStairs), TargetMovement.Y);
walkCycleMultiplier *= 1.5f;
}
@@ -572,6 +601,15 @@ namespace Barotrauma
bool movingHorizontally = !MathUtils.NearlyEqual(TargetMovement.X, 0.0f);
if (Stairs == null && onSlopeTooSteepToClimb)
{
if (Math.Sign(targetMovement.X) != Math.Sign(floorNormal.X))
{
targetMovement.X = Math.Sign(floorNormal.X) * SteepSlopePushMagnitude;
movement = targetMovement;
}
}
if (Stairs != null || onSlope)
{
torso.PullJointWorldAnchorB = new Vector2(
@@ -648,14 +686,6 @@ namespace Barotrauma
if (!onGround)
{
Vector2 move = torso.PullJointWorldAnchorB - torso.SimPosition;
foreach (Limb limb in Limbs)
{
if (limb.IsSevered) { continue; }
MoveLimb(limb, limb.SimPosition + move, 15.0f, true);
}
return;
}
@@ -758,7 +788,7 @@ namespace Barotrauma
{
footPos = new Vector2(colliderPos.X + stepSize.X * i * 0.2f, colliderPos.Y - 0.1f);
}
if (Stairs == null)
if (Stairs == null && !onSlopeThatMakesSlow)
{
footPos.Y = Math.Max(Math.Min(FloorY, footPos.Y + 0.5f), footPos.Y);
}
@@ -1318,14 +1348,14 @@ namespace Barotrauma
}
}
void UpdateFallingProne(float strength)
void UpdateFallingProne(float strength, bool moveHands = true, bool moveTorso = true, bool moveLegs = true)
{
if (strength <= 0.0f) { return; }
Limb head = GetLimb(LimbType.Head);
Limb torso = GetLimb(LimbType.Torso);
if (head != null && head.LinearVelocity.LengthSquared() > 1.0f && !head.IsSevered)
if (moveHands && head != null && head.LinearVelocity.LengthSquared() > 1.0f && !head.IsSevered)
{
//if the head is moving, try to protect it with the hands
Limb leftHand = GetLimb(LimbType.LeftHand);
@@ -1347,7 +1377,7 @@ namespace Barotrauma
//make the torso tip over
//otherwise it tends to just drop straight down, pinning the characters legs in a weird pose
if (!InWater)
if (moveTorso && !InWater)
{
//prefer tipping over in the same direction the torso is rotating
//or moving
@@ -1358,27 +1388,30 @@ namespace Barotrauma
}
//attempt to make legs stay in a straight line with the torso to prevent the character from doing a split
for (int i = 0; i < 2; i++)
if (moveLegs)
{
var thigh = i == 0 ? GetLimb(LimbType.LeftThigh) : GetLimb(LimbType.RightThigh);
if (thigh == null) { continue; }
if (thigh.IsSevered) { continue; }
float thighDiff = Math.Abs(MathUtils.GetShortestAngle(torso.Rotation, thigh.Rotation));
float diff = torso.Rotation - thigh.Rotation;
if (MathUtils.IsValid(diff))
for (int i = 0; i < 2; i++)
{
float thighTorque = thighDiff * thigh.Mass * Math.Sign(diff) * 5.0f;
thigh.body.ApplyTorque(thighTorque * strength);
}
var thigh = i == 0 ? GetLimb(LimbType.LeftThigh) : GetLimb(LimbType.RightThigh);
if (thigh == null) { continue; }
if (thigh.IsSevered) { continue; }
float thighDiff = Math.Abs(MathUtils.GetShortestAngle(torso.Rotation, thigh.Rotation));
float diff = torso.Rotation - thigh.Rotation;
if (MathUtils.IsValid(diff))
{
float thighTorque = thighDiff * thigh.Mass * Math.Sign(diff) * 5.0f;
thigh.body.ApplyTorque(thighTorque * strength);
}
var leg = i == 0 ? GetLimb(LimbType.LeftLeg) : GetLimb(LimbType.RightLeg);
if (leg == null || leg.IsSevered) { continue; }
float legDiff = Math.Abs(MathUtils.GetShortestAngle(torso.Rotation, leg.Rotation));
diff = torso.Rotation - leg.Rotation;
if (MathUtils.IsValid(diff))
{
float legTorque = legDiff * leg.Mass * Math.Sign(diff) * 5.0f;
leg.body.ApplyTorque(legTorque * strength);
var leg = i == 0 ? GetLimb(LimbType.LeftLeg) : GetLimb(LimbType.RightLeg);
if (leg == null || leg.IsSevered) { continue; }
float legDiff = Math.Abs(MathUtils.GetShortestAngle(torso.Rotation, leg.Rotation));
diff = torso.Rotation - leg.Rotation;
if (MathUtils.IsValid(diff))
{
float legTorque = legDiff * leg.Mass * Math.Sign(diff) * 5.0f;
leg.body.ApplyTorque(legTorque * strength);
}
}
}
}
@@ -1398,7 +1431,8 @@ namespace Barotrauma
Crouching = true;
Vector2 diff = target.SimPosition - character.SimPosition;
Vector2 offset = Vector2.UnitX * -Dir * 0.75f;
Vector2 diff = (target.SimPosition + offset) - character.SimPosition;
Limb targetHead = target.AnimController.GetLimb(LimbType.Head);
Limb targetTorso = target.AnimController.GetLimb(LimbType.Torso);
if (targetTorso == null)
@@ -1412,7 +1446,23 @@ namespace Barotrauma
Vector2 headDiff = targetHead == null ? diff : targetHead.SimPosition - character.SimPosition;
targetMovement = new Vector2(diff.X, 0.0f);
const float CloseEnough = 0.1f;
if (Math.Abs(targetMovement.X) < CloseEnough)
{
targetMovement.X = 0.0f;
}
TargetDir = headDiff.X > 0.0f ? Direction.Right : Direction.Left;
//if the target's in some weird pose, we may not be able to flip it so it's facing up,
//so let's only try it once so we don't end up constantly flipping it
if (cprAnimTimer <= 0.0f && target.AnimController.Direction == TargetDir)
{
target.AnimController.Flip();
}
(target.AnimController as HumanoidAnimController)?.UpdateFallingProne(strength: 1.0f, moveHands: false, moveTorso: false);
head.Disabled = true;
torso.Disabled = true;
UpdateStanding();
@@ -1443,79 +1493,69 @@ namespace Barotrauma
}
}
//pump for 15 seconds (cprAnimTimer 0-15), then do mouth-to-mouth for 2 seconds (cprAnimTimer 15-17)
if (cprAnimTimer > 15.0f && targetHead != null && head != null)
//Serverside code
if (GameMain.NetworkMember is not { IsClient: true })
{
float yPos = (float)Math.Sin(cprAnimTimer) * 0.2f;
head.PullJointWorldAnchorB = new Vector2(targetHead.SimPosition.X, targetHead.SimPosition.Y + 0.3f + yPos);
if (target.Oxygen < -10.0f)
{
//stabilize the oxygen level but don't allow it to go positive and revive the character yet
float stabilizationAmount = skill * CPRSettings.Active.StabilizationPerSkill;
stabilizationAmount = MathHelper.Clamp(stabilizationAmount, CPRSettings.Active.StabilizationMin, CPRSettings.Active.StabilizationMax);
character.Oxygen -= 1.0f / stabilizationAmount * deltaTime; //Worse skill = more oxygen required
if (character.Oxygen > 0.0f) { target.Oxygen += stabilizationAmount * deltaTime; } //we didn't suffocate yet did we
}
}
if (targetHead != null && head != null)
{
head.PullJointWorldAnchorB = new Vector2(targetHead.SimPosition.X, targetHead.SimPosition.Y + 0.8f);
head.PullJointEnabled = true;
torso.PullJointWorldAnchorB = new Vector2(torso.SimPosition.X, colliderPos.Y + (TorsoPosition.Value - 0.2f));
torso.PullJointEnabled = true;
//Serverside code
if (GameMain.NetworkMember is not { IsClient: true })
{
if (target.Oxygen < -10.0f)
{
//stabilize the oxygen level but don't allow it to go positive and revive the character yet
float stabilizationAmount = skill * CPRSettings.Active.StabilizationPerSkill;
stabilizationAmount = MathHelper.Clamp(stabilizationAmount, CPRSettings.Active.StabilizationMin, CPRSettings.Active.StabilizationMax);
character.Oxygen -= 1.0f / stabilizationAmount * deltaTime; //Worse skill = more oxygen required
if (character.Oxygen > 0.0f) { target.Oxygen += stabilizationAmount * deltaTime; } //we didn't suffocate yet did we
}
}
}
else
torso.PullJointWorldAnchorB = new Vector2(torso.SimPosition.X, colliderPos.Y + (TorsoPosition.Value - 0.1f));
torso.PullJointEnabled = true;
if (cprPumpTimer >= 1)
{
if (targetHead != null && head != null)
torso.body.ApplyLinearImpulse(new Vector2(0, -20f), maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
targetTorso.body.ApplyLinearImpulse(new Vector2(0, -20f), maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
cprPumpTimer = 0;
if (skill < CPRSettings.Active.DamageSkillThreshold)
{
head.PullJointWorldAnchorB = new Vector2(targetHead.SimPosition.X, targetHead.SimPosition.Y + 0.8f);
head.PullJointEnabled = true;
target.LastDamageSource = null;
target.DamageLimb(
targetTorso.WorldPosition, targetTorso,
new[] { CPRSettings.Active.InsufficientSkillAffliction.Instantiate((CPRSettings.Active.DamageSkillThreshold - skill) * CPRSettings.Active.DamageSkillMultiplier, source: character) },
stun: 0.0f,
playSound: true,
attackImpulse: Vector2.Zero,
attacker: null);
}
torso.PullJointWorldAnchorB = new Vector2(torso.SimPosition.X, colliderPos.Y + (TorsoPosition.Value - 0.1f));
torso.PullJointEnabled = true;
if (cprPump >= 1)
//need to CPR for at least a couple of seconds before the target can be revived
//(reviving the target when the CPR has barely started looks strange)
if (cprAnimTimer > 2.0f && GameMain.NetworkMember is not { IsClient: true })
{
torso.body.ApplyLinearImpulse(new Vector2(0, -20f), maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
targetTorso.body.ApplyLinearImpulse(new Vector2(0, -20f), maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
cprPump = 0;
float reviveChance = skill * CPRSettings.Active.ReviveChancePerSkill;
reviveChance = (float)Math.Pow(reviveChance, CPRSettings.Active.ReviveChanceExponent);
reviveChance = MathHelper.Clamp(reviveChance, CPRSettings.Active.ReviveChanceMin, CPRSettings.Active.ReviveChanceMax);
reviveChance *= 1f + cprBoost;
if (skill < CPRSettings.Active.DamageSkillThreshold)
if (Rand.Range(0.0f, 1.0f, Rand.RandSync.ServerAndClient) <= reviveChance)
{
target.LastDamageSource = null;
target.DamageLimb(
targetTorso.WorldPosition, targetTorso,
new[] { CPRSettings.Active.InsufficientSkillAffliction.Instantiate((CPRSettings.Active.DamageSkillThreshold - skill) * CPRSettings.Active.DamageSkillMultiplier, source: character) },
0.0f, true, 0.0f, attacker: null);
//increase oxygen and clamp it above zero
// -> the character should be revived if there are no major afflictions in addition to lack of oxygen
target.Oxygen = Math.Max(target.Oxygen + 10.0f, 10.0f);
GameMain.LuaCs.Hook.Call("human.CPRSuccess", this);
}
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) //Serverside code
else
{
float reviveChance = skill * CPRSettings.Active.ReviveChancePerSkill;
reviveChance = (float)Math.Pow(reviveChance, CPRSettings.Active.ReviveChanceExponent);
reviveChance = MathHelper.Clamp(reviveChance, CPRSettings.Active.ReviveChanceMin, CPRSettings.Active.ReviveChanceMax);
reviveChance *= 1f + cprBoost;
if (Rand.Range(0.0f, 1.0f, Rand.RandSync.ServerAndClient) <= reviveChance)
{
//increase oxygen and clamp it above zero
// -> the character should be revived if there are no major afflictions in addition to lack of oxygen
target.Oxygen = Math.Max(target.Oxygen + 10.0f, 10.0f);
GameMain.LuaCs.Hook.Call("human.CPRSuccess", this);
}
else
{
GameMain.LuaCs.Hook.Call("human.CPRFailed", this);
}
GameMain.LuaCs.Hook.Call("human.CPRFailed", this);
}
}
cprPump += deltaTime;
}
cprAnimTimer = (cprAnimTimer + deltaTime) % 17;
cprPumpTimer += deltaTime;
cprAnimTimer += deltaTime;
//got the character back into a non-critical state, increase medical skill
//BUT only if it has been more than 10 seconds since the character revived someone
@@ -1525,7 +1565,7 @@ namespace Barotrauma
target.CharacterHealth.CalculateVitality();
if (wasCritical && target.Vitality > 0.0f && Timing.TotalTime > lastReviveTime + 10.0f)
{
character.Info?.IncreaseSkillLevel("medical".ToIdentifier(), SkillSettings.Current.SkillIncreasePerCprRevive);
character.Info?.ApplySkillGain(Tags.MedicalSkill, SkillSettings.Current.SkillIncreasePerCprRevive);
SteamAchievementManager.OnCharacterRevived(target, character);
lastReviveTime = (float)Timing.TotalTime;
#if SERVER
@@ -1730,7 +1770,23 @@ namespace Barotrauma
targetAnchor += target.Submarine.SimPosition;
}
}
pullLimb.PullJointWorldAnchorB = pullLimbAnchor;
if (Vector2.DistanceSquared(pullLimb.PullJointWorldAnchorA, pullLimbAnchor) > 50.0f * 50.0f)
{
//there's a similar error check in the PullJointWorldAnchorB setter, but we seem to be getting quite a lot of
//errors specifically from this method, so let's use a more consistent error message here to prevent clogging GA with
//different error messages that all include a different coordinate
string errorMsg =
$"Attempted to move the anchor B of a limb's pull joint extremely far from the limb in {nameof(DragCharacter)}. " +
$"Character in sub: {character.Submarine != null}, target in sub: {target.Submarine != null}.";
GameAnalyticsManager.AddErrorEventOnce("DragCharacter:PullJointTooFar", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
#if DEBUG
DebugConsole.ThrowError(errorMsg);
#endif
}
else
{
pullLimb.PullJointWorldAnchorB = pullLimbAnchor;
}
}
else
{
@@ -438,7 +438,18 @@ namespace Barotrauma
foreach (var huskAppendage in mainElement.GetChildElements("huskappendage"))
{
if (!inEditor && huskAppendage.GetAttributeBool("onlyfromafflictions", false)) { continue; }
AfflictionHusk.AttachHuskAppendage(character, huskAppendage.GetAttributeIdentifier("affliction", Identifier.Empty), huskAppendage, ragdoll: this);
Identifier afflictionIdentifier = huskAppendage.GetAttributeIdentifier("affliction", Identifier.Empty);
if (!AfflictionPrefab.Prefabs.TryGet(afflictionIdentifier, out AfflictionPrefab affliction) ||
affliction is not AfflictionPrefabHusk matchingAffliction)
{
DebugConsole.ThrowError($"Could not find an affliction of type 'huskinfection' that matches the affliction '{afflictionIdentifier}'!",
contentPackage: huskAppendage.ContentPackage);
}
else
{
AfflictionHusk.AttachHuskAppendage(character, matchingAffliction, huskAppendage, ragdoll: this);
}
}
}
}
@@ -562,6 +573,10 @@ namespace Barotrauma
public void AddJoint(JointParams jointParams)
{
if (!checkLimbIndex(jointParams.Limb2, "Limb1") || !checkLimbIndex(jointParams.Limb2, "Limb2"))
{
return;
}
LimbJoint joint = new LimbJoint(Limbs[jointParams.Limb1], Limbs[jointParams.Limb2], jointParams, this);
GameMain.World.Add(joint.Joint);
for (int i = 0; i < LimbJoints.Length; i++)
@@ -572,6 +587,21 @@ namespace Barotrauma
}
Array.Resize(ref LimbJoints, LimbJoints.Length + 1);
LimbJoints[LimbJoints.Length - 1] = joint;
bool checkLimbIndex(int index, string debugName)
{
if (index < 0 || index >= limbs.Length)
{
string errorMsg = $"Failed to add a joint to character {character.Name}. {debugName} out of bounds (index: {index}, limbs: {limbs.Length}.";
DebugConsole.ThrowError(errorMsg, contentPackage: jointParams.Element?.ContentPackage);
if (jointParams.Element?.ContentPackage == GameMain.VanillaContent)
{
GameAnalyticsManager.AddErrorEventOnce("Ragdoll.AddJoint:IndexOutOfRange", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
}
return false;
}
return true;
}
}
protected void AddLimb(LimbParams limbParams)
@@ -658,6 +688,13 @@ namespace Barotrauma
}
}
private enum LimbStairCollisionResponse
{
DontClimbStairs,
ClimbWithoutLimbCollision,
ClimbWithLimbCollision
}
public bool OnLimbCollision(Fixture f1, Fixture f2, Contact contact)
{
if (f2.Body.UserData is Submarine && character.Submarine == (Submarine)f2.Body.UserData) { return false; }
@@ -700,37 +737,53 @@ namespace Barotrauma
}
else if (structure.StairDirection != Direction.None)
{
Stairs = null;
//don't collider with stairs if
//1. bottom of the collider is at the bottom of the stairs and the character isn't trying to move upwards
float stairBottomPos = ConvertUnits.ToSimUnits(structure.Rect.Y - structure.Rect.Height + 10);
if (colliderBottom.Y < stairBottomPos && targetMovement.Y < 0.5f) { return false; }
//2. bottom of the collider is at the top of the stairs and the character isn't trying to move downwards
if (targetMovement.Y >= 0.0f && colliderBottom.Y >= ConvertUnits.ToSimUnits(structure.Rect.Y - Submarine.GridSize.Y * 5)) { return false; }
//3. collided with the stairs from below
if (contact.Manifold.LocalNormal.Y < 0.0f) { return false; }
//4. contact points is above the bottom half of the collider
contact.GetWorldManifold(out Vector2 normal, out FarseerPhysics.Common.FixedArray2<Vector2> points);
if (points[0].Y > Collider.SimPosition.Y) { return false; }
//5. in water
if (inWater && targetMovement.Y < 0.5f) { return false; }
//---------------
//set stairs to that of the one dragging us
if (character.SelectedBy != null)
{
Stairs = character.SelectedBy.AnimController.Stairs;
}
else
Stairs = structure;
{
var collisionResponse = handleLimbStairCollision();
if (collisionResponse == LimbStairCollisionResponse.ClimbWithLimbCollision)
{
Stairs = structure;
}
else
{
if (collisionResponse == LimbStairCollisionResponse.DontClimbStairs) { Stairs = null; }
if (Stairs == null)
return false;
return false;
}
}
LimbStairCollisionResponse handleLimbStairCollision()
{
//don't collide with stairs if
//1. bottom of the collider is at the bottom of the stairs and the character isn't trying to move upwards
float stairBottomPos = ConvertUnits.ToSimUnits(structure.Rect.Y - structure.Rect.Height + 10);
if (colliderBottom.Y < stairBottomPos && targetMovement.Y < 0.5f) { return LimbStairCollisionResponse.DontClimbStairs; }
//2. bottom of the collider is at the top of the stairs and the character isn't trying to move downwards
if (targetMovement.Y >= 0.0f && colliderBottom.Y >= ConvertUnits.ToSimUnits(structure.Rect.Y - Submarine.GridSize.Y * 5)) { return LimbStairCollisionResponse.DontClimbStairs; }
//3. collided with the stairs from below
if (contact.Manifold.LocalNormal.Y < 0.0f)
{
return Stairs != structure
? LimbStairCollisionResponse.DontClimbStairs
: LimbStairCollisionResponse.ClimbWithoutLimbCollision;
}
//4. contact points is above the bottom half of the collider
contact.GetWorldManifold(out _, out FarseerPhysics.Common.FixedArray2<Vector2> points);
if (points[0].Y > Collider.SimPosition.Y) { return LimbStairCollisionResponse.DontClimbStairs; }
//5. in water
if (inWater && targetMovement.Y < 0.5f) { return LimbStairCollisionResponse.DontClimbStairs; }
return LimbStairCollisionResponse.ClimbWithLimbCollision;
}
}
lock (impactQueue)
@@ -1332,17 +1385,28 @@ namespace Barotrauma
if (onGround && Collider.LinearVelocity.Y > -ImpactTolerance)
{
float targetY = standOnFloorY + ((float)Math.Abs(Math.Cos(Collider.Rotation)) * Collider.Height * 0.5f) + Collider.Radius + ColliderHeightFromFloor;
if (Math.Abs(Collider.SimPosition.Y - targetY) > 0.01f)
const float LevitationSpeedMultiplier = 5f;
// If the character is walking down a slope, target a position that moves along it
float slopePull = 0f;
if (floorNormal.Y is > 0f and < 1f
&& Math.Sign(movement.X) == Math.Sign(floorNormal.X))
{
if (Stairs != null)
slopePull = Math.Abs(movement.X * floorNormal.X / floorNormal.Y) / LevitationSpeedMultiplier;
}
if (Math.Abs(Collider.SimPosition.Y - targetY - slopePull) > 0.01f)
{
float yVelocity = (targetY - Collider.SimPosition.Y) * LevitationSpeedMultiplier;
if (Stairs != null && targetY < Collider.SimPosition.Y)
{
Collider.LinearVelocity = new Vector2(Collider.LinearVelocity.X,
(targetY < Collider.SimPosition.Y ? Math.Sign(targetY - Collider.SimPosition.Y) : (targetY - Collider.SimPosition.Y)) * 5.0f);
}
else
{
Collider.LinearVelocity = new Vector2(Collider.LinearVelocity.X, (targetY - Collider.SimPosition.Y) * 5.0f);
yVelocity = Math.Sign(yVelocity);
}
yVelocity -= slopePull * LevitationSpeedMultiplier;
Collider.LinearVelocity = new Vector2(Collider.LinearVelocity.X, yVelocity);
}
}
else
@@ -1590,10 +1654,10 @@ namespace Barotrauma
// Force check floor y at least once a second so that we'll drop through gaps that we are standing upon.
private const float FloorYStaleTime = 1;
private float floorYCheckTimer;
private void RefreshFloorY(float deltaTime, Limb refLimb = null, bool ignoreStairs = false)
private void RefreshFloorY(float deltaTime, bool ignoreStairs = false)
{
floorYCheckTimer -= deltaTime;
PhysicsBody refBody = refLimb == null ? Collider : refLimb.body;
PhysicsBody refBody = Collider;
if (floorYCheckTimer < 0 ||
lastFloorCheckIgnoreStairs != ignoreStairs ||
lastFloorCheckIgnorePlatforms != IgnorePlatforms ||
@@ -1618,7 +1682,7 @@ namespace Barotrauma
if (HeadPosition.HasValue && MathUtils.IsValid(HeadPosition.Value)) { height = Math.Max(height, HeadPosition.Value); }
if (TorsoPosition.HasValue && MathUtils.IsValid(TorsoPosition.Value)) { height = Math.Max(height, TorsoPosition.Value); }
Vector2 rayEnd = rayStart - new Vector2(0.0f, height);
Vector2 rayEnd = rayStart - new Vector2(0.0f, height * 2f);
Vector2 colliderBottomDisplay = ConvertUnits.ToDisplayUnits(GetColliderBottom());
Fixture standOnFloorFixture = null;
@@ -1697,7 +1761,7 @@ namespace Barotrauma
}
}
if (closestFraction == 1) //raycast didn't hit anything
if (closestFraction >= 1) //raycast didn't hit anything
{
floorNormal = Vector2.UnitY;
if (CurrentHull == null)