(38b5d9aad) Experimental changes to syncing ragdolled (unconscious/dead) characters: - Higher error tolerance when syncing the positions. It's often hard to get the main limb exactly to the same position as the collider, because the positions of the limbs aren't synced and the pose of the ragdoll may differ between the server and clients. Increasing the tolerance makes it less likely for dead/unconscious characters to "twitch" when the game attempts to force the main limb to the position of the collider. - If the position of the ragdoll differs from the position of the collider so much that CheckDistFromCollider disables limb collisions, apply an additional force to all limbs to force the ragdoll to the correct position. Otherwise the ragdoll can occasionally start "hanging" midair, clipping through solid objects, because the main limb's pull joint doesn't necessarily have enough force to pull the entire ragdoll up to the collider.

This commit is contained in:
Joonas Rikkonen
2019-04-01 22:47:03 +03:00
parent 11f2bf4b38
commit ec7e23061b
130 changed files with 1359 additions and 2352 deletions
@@ -115,7 +115,7 @@ namespace Barotrauma
SonarLabel = element.GetAttributeString("sonarlabel", "");
}
public AITarget(Entity e, float sightRange = -1, float soundRange = 0)
public AITarget(Entity e)
{
Entity = e;
if (sightRange < 0)
File diff suppressed because it is too large Load Diff
@@ -193,7 +193,7 @@ namespace Barotrauma
// is not attached or is attached to something else
if (!IsAttached || IsAttached && attachJoints[0].BodyB == attachTargetBody)
{
if (Vector2.DistanceSquared(ConvertUnits.ToDisplayUnits(transformedAttachPos), enemyAI.AttackingLimb.WorldPosition) < enemyAI.AttackingLimb.attack.DamageRange * enemyAI.AttackingLimb.attack.DamageRange)
if (Vector2.DistanceSquared(ConvertUnits.ToDisplayUnits(transformedAttachPos), enemyAI.AttackingLimb.WorldPosition) < enemyAI.AttackingLimb.attack.Range * enemyAI.AttackingLimb.attack.Range)
{
AttachToBody(character.AnimController.Collider, attachLimb, attachTargetBody, transformedAttachPos);
}
@@ -115,8 +115,7 @@ namespace Barotrauma
unreachable.Add(goToObjective.Target as Hull);
}
goToObjective = null;
HumanAIController.ObjectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
//SteeringManager.SteeringWander();
SteeringManager.SteeringWander();
}
}
else if (currentHull != null)
@@ -99,8 +99,7 @@ namespace Barotrauma
FindTargetItem();
if (targetItem == null || moveToTarget == null)
{
HumanAIController.ObjectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
//SteeringManager.SteeringWander();
SteeringManager.SteeringWander();
return;
}
@@ -98,64 +98,60 @@ namespace Barotrauma
PathSteering.Reset();
return;
}
if (standStillTimer < -walkDuration)
{
standStillTimer = Rand.Range(standStillMin, standStillMax);
}
//steer away from edges of the hull
if (character.AnimController.CurrentHull != null && !character.IsClimbing)
{
standStillTimer = Rand.Range(standStillMin, standStillMax);
}
Wander(deltaTime);
return;
}
if (currentTarget != null)
{
character.AIController.SteeringManager.SteeringSeek(currentTarget.SimPosition);
}
}
public void Wander(float deltaTime)
{
//steer away from edges of the hull
if (character.AnimController.CurrentHull != null && !character.IsClimbing)
{
float leftDist = character.Position.X - character.AnimController.CurrentHull.Rect.X;
float rightDist = character.AnimController.CurrentHull.Rect.Right - character.Position.X;
if (leftDist < WallAvoidDistance && rightDist < WallAvoidDistance)
{
if (Math.Abs(rightDist - leftDist) > WallAvoidDistance / 2)
if (leftDist < WallAvoidDistance && rightDist < WallAvoidDistance)
{
PathSteering.SteeringManual(deltaTime, Vector2.UnitX * Math.Sign(rightDist - leftDist));
if (Math.Abs(rightDist - leftDist) > WallAvoidDistance / 2)
{
PathSteering.SteeringManual(deltaTime, Vector2.UnitX * Math.Sign(rightDist - leftDist));
}
else
{
PathSteering.Reset();
return;
}
}
else if (leftDist < WallAvoidDistance)
{
PathSteering.SteeringManual(deltaTime, Vector2.UnitX * (WallAvoidDistance-leftDist) / WallAvoidDistance);
PathSteering.WanderAngle = 0.0f;
return;
}
else
{
PathSteering.Reset();
PathSteering.SteeringManual(deltaTime, -Vector2.UnitX * (WallAvoidDistance-rightDist) / WallAvoidDistance);
PathSteering.WanderAngle = MathHelper.Pi;
return;
}
}
else if (leftDist < WallAvoidDistance)
character.AIController.SteeringManager.SteeringWander();
if (!character.IsClimbing && !character.AnimController.InWater)
{
//PathSteering.SteeringManual(deltaTime, Vector2.UnitX * (WallAvoidDistance - leftDist) / WallAvoidDistance);
PathSteering.SteeringManual(deltaTime, Vector2.UnitX);
PathSteering.WanderAngle = 0.0f;
}
else if (rightDist < WallAvoidDistance)
{
//PathSteering.SteeringManual(deltaTime, -Vector2.UnitX * (WallAvoidDistance - rightDist) / WallAvoidDistance);
PathSteering.SteeringManual(deltaTime, -Vector2.UnitX);
PathSteering.WanderAngle = MathHelper.Pi;
}
else
{
SteeringManager.SteeringWander();
}
//reset vertical steering to prevent dropping down from platforms etc
character.AIController.SteeringManager.ResetY();
}
return;
}
else
if (currentTarget != null)
{
SteeringManager.SteeringWander();
}
if (!character.IsClimbing && !character.AnimController.InWater)
{
//reset vertical steering to prevent dropping down from platforms etc
character.AIController.SteeringManager.ResetY();
character.AIController.SteeringManager.SteeringSeek(currentTarget.SimPosition);
}
}
@@ -369,18 +369,9 @@ namespace Barotrauma
return;
}
Vector2 transformedMovement = reverse ? -movement : movement;
float movementAngle = MathUtils.VectorToAngle(transformedMovement) - MathHelper.PiOver2;
float mainLimbAngle = 0;
if (MainLimb.type == LimbType.Torso && TorsoAngle.HasValue)
{
mainLimbAngle = TorsoAngle.Value;
}
else if (MainLimb.type == LimbType.Head && HeadAngle.HasValue)
{
mainLimbAngle = HeadAngle.Value;
}
mainLimbAngle *= Dir;
float movementAngle = MathUtils.VectorToAngle(movement) - MathHelper.PiOver2;
float mainLimbAngle = (MainLimb.type == LimbType.Torso ? TorsoAngle.Value : HeadAngle.Value) * Dir;
while (MainLimb.Rotation - (movementAngle + mainLimbAngle) > MathHelper.Pi)
{
movementAngle += MathHelper.TwoPi;
@@ -388,7 +379,7 @@ namespace Barotrauma
while (MainLimb.Rotation - (movementAngle + mainLimbAngle) < -MathHelper.Pi)
{
movementAngle -= MathHelper.TwoPi;
}
}
if (CurrentSwimParams.RotateTowardsMovement)
{
@@ -412,6 +403,7 @@ namespace Barotrauma
if (TailAngle.HasValue)
{
Limb tail = GetLimb(LimbType.Tail);
//tail?.body.SmoothRotate(movementAngle + TailAngle.Value * Dir, TailTorque);
if (tail != null)
{
SmoothRotateWithoutWrapping(tail, movementAngle + TailAngle.Value * Dir, MainLimb, TailTorque);
@@ -421,10 +413,6 @@ namespace Barotrauma
else
{
movementAngle = Dir > 0 ? -MathHelper.PiOver2 : MathHelper.PiOver2;
if (reverse)
{
movementAngle = MathUtils.WrapAngleTwoPi(movementAngle - MathHelper.Pi);
}
if (MainLimb.type == LimbType.Head && HeadAngle.HasValue)
{
Collider.SmoothRotate(HeadAngle.Value * Dir, CurrentSwimParams.SteerTorque);
@@ -454,7 +442,7 @@ namespace Barotrauma
var waveAmplitude = Math.Abs(CurrentSwimParams.WaveAmplitude);
if (waveLength > 0 && waveAmplitude > 0)
{
WalkPos -= transformedMovement.Length() / Math.Abs(waveLength);
WalkPos -= movement.Length() / Math.Abs(waveLength);
WalkPos = MathUtils.WrapAngleTwoPi(WalkPos);
}
@@ -697,6 +685,12 @@ namespace Barotrauma
limb.body.ApplyForce(diff * (float)(Math.Sin(WalkPos) * Math.Sqrt(limb.Mass)) * 30.0f * animStrength);
}
while (referenceLimb.Rotation - angle < -MathHelper.TwoPi)
{
angle -= MathHelper.TwoPi;
}
limb?.body.SmoothRotate(angle, torque, wrapAngle: false);
}
private void SmoothRotateWithoutWrapping(Limb limb, float angle, Limb referenceLimb, float torque)
@@ -1463,6 +1463,11 @@ namespace Barotrauma
targetRightHand.PullJointMaxForce = 5000.0f;
}
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
{
Collider.ResetDynamics();
}
target.AnimController.IgnorePlatforms = true;
}
else
@@ -99,7 +99,7 @@ namespace Barotrauma
public static string GetDefaultFolder(string speciesName) => $"Content/Characters/{speciesName.CapitaliseFirstInvariant()}/Animations/";
public static string GetDefaultFile(string speciesName, AnimationType animType) => $"{GetFolder(speciesName)}{GetDefaultFileName(speciesName, animType)}.xml";
public static string GetFolder(string speciesName)
protected static string GetFolder(string speciesName)
{
var folder = XMLExtensions.TryLoadXml(Character.GetConfigFile(speciesName))?.Root?.Element("animations")?.GetAttributeString("folder", string.Empty);
if (string.IsNullOrEmpty(folder) || folder.ToLowerInvariant() == "default")
@@ -79,7 +79,7 @@ namespace Barotrauma
new XAttribute("sourcerect", $"0, 0, 1, 1")))
};
public static string GetFolder(string speciesName)
protected static string GetFolder(string speciesName)
{
var folder = XMLExtensions.TryLoadXml(Character.GetConfigFile(speciesName))?.Root?.Element("ragdolls")?.GetAttributeString("folder", string.Empty);
if (string.IsNullOrEmpty(folder) || folder.ToLowerInvariant() == "default")
@@ -1035,6 +1035,8 @@ namespace Barotrauma
CheckValidity();
CheckValidity();
UpdateNetPlayerPosition(deltaTime);
CheckDistFromCollider();
UpdateCollisionCategories();
@@ -1295,42 +1297,17 @@ namespace Barotrauma
UpdateProjSpecific(deltaTime);
}
public bool Invalid { get; private set; }
private int validityResets;
private bool CheckValidity()
private void CheckValidity()
{
bool isColliderValid = CheckValidity(Collider);
bool limbsValid = true;
CheckValidity(Collider);
foreach (Limb limb in limbs)
{
if (limb.body == null || !limb.body.Enabled) { continue; }
if (!CheckValidity(limb.body))
{
limbsValid = false;
break;
}
CheckValidity(limb.body);
}
bool isValid = isColliderValid && limbsValid;
if (!isValid)
{
validityResets++;
if (validityResets > 1)
{
Invalid = true;
DebugConsole.ThrowError("Invalid ragdoll physics. Ragdoll freezed to prevent crashes.");
Collider.SetTransform(Vector2.Zero, 0.0f);
foreach (Limb limb in Limbs)
{
limb.body.SetTransform(Collider.SimPosition, 0.0f);
limb.body.ResetDynamics();
}
Frozen = true;
}
}
return isValid;
}
private bool CheckValidity(PhysicsBody body)
private void CheckValidity(PhysicsBody body)
{
string errorMsg = null;
string bodyName = body.UserData is Limb ? "Limb" : "Collider";
@@ -1382,7 +1359,7 @@ namespace Barotrauma
limb.body.ResetDynamics();
}
SetInitialLimbPositions();
return false;
return;
}
return true;
}
@@ -1524,7 +1501,8 @@ namespace Barotrauma
float allowedDist = Math.Max(Math.Max(Collider.radius, Collider.width), Collider.height) * 2.0f;
float resetDist = allowedDist * 5.0f;
float distSqrd = Vector2.DistanceSquared(Collider.SimPosition, MainLimb.SimPosition);
Vector2 diff = Collider.SimPosition - MainLimb.SimPosition;
float distSqrd = diff.LengthSquared();
if (distSqrd > resetDist * resetDist)
{
@@ -1535,10 +1513,13 @@ namespace Barotrauma
{
//ragdoll too far from the collider, disable collisions until it's close enough
//(in case the ragdoll has gotten stuck somewhere)
Vector2 forceDir = diff / (float)Math.Sqrt(distSqrd);
foreach (Limb limb in Limbs)
{
if (limb.IsSevered) continue;
limb.body.CollidesWith = Physics.CollisionNone;
limb.body.ApplyForce(forceDir * limb.Mass * 10.0f, maxVelocity: 10.0f);
}
collisionsDisabled = true;
@@ -28,7 +28,6 @@ namespace Barotrauma
public enum AIBehaviorAfterAttack
{
FallBack,
FallBackUntilCanAttack,
PursueIfCanAttack,
Pursue
}
@@ -82,9 +81,6 @@ namespace Barotrauma
[Serialize(AIBehaviorAfterAttack.FallBack, true), Editable(ToolTip = "The preferred AI behavior after the attack.")]
public AIBehaviorAfterAttack AfterAttack { get; private set; }
[Serialize(false, true), Editable(ToolTip = "Should the ai try to reverse when aiming with this attack?")]
public bool Reverse { get; private set; }
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 2000.0f, ToolTip = "Min distance from the attack limb to the target before the AI tries to attack.")]
public float Range { get; private set; }
@@ -100,9 +96,6 @@ namespace Barotrauma
[Serialize(0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, DecimalCount = 2, ToolTip = "Used as the attack cooldown between different kind of attacks. Does not have effect, if set to 0.")]
public float SecondaryCoolDown { get; private set; } = 0;
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1, DecimalCount = 2, ToolTip = "Random factor applied to all cooldowns. Example: 0.1 -> adds a random value between -10% and 10% of the cooldown. Min 0 (default), Max 1 (could disable or double the cooldown in extreme cases).")]
public float CoolDownRandomFactor { get; private set; } = 0;
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10000.0f)]
public float StructureDamage { get; private set; }
@@ -189,7 +182,7 @@ namespace Barotrauma
public readonly List<Affliction> Afflictions = new List<Affliction>();
/// <summary>
/// Only affects ai decision making. All the conditionals has to be met in order to select the attack. TODO: allow to define conditionals using any (implemented in StatusEffect -> move from there to PropertyConditional?)
/// Only affects ai decision making.
/// </summary>
public List<PropertyConditional> Conditionals { get; private set; } = new List<PropertyConditional>();
@@ -451,10 +444,8 @@ namespace Barotrauma
public void SetCoolDown()
{
float randomFraction = CoolDown * CoolDownRandomFactor;
CoolDownTimer = CoolDown + MathHelper.Lerp(-randomFraction, randomFraction, Rand.Value(Rand.RandSync.Server));
randomFraction = SecondaryCoolDown * CoolDownRandomFactor;
SecondaryCoolDownTimer = SecondaryCoolDown + MathHelper.Lerp(-randomFraction, randomFraction, Rand.Value(Rand.RandSync.Server));
CoolDownTimer = CoolDown;
SecondaryCoolDownTimer = SecondaryCoolDown;
}
public void ResetCoolDown()
@@ -810,7 +810,6 @@ namespace Barotrauma
public void LoadHeadAttachments()
{
if (Info == null) { return; }
if (AnimController == null) { return; }
var head = AnimController.GetLimb(LimbType.Head);
if (head == null) { return; }
@@ -1113,15 +1112,13 @@ namespace Barotrauma
ViewTarget = null;
if (!AllowInput) return;
if (Controlled == this || (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer))
Vector2 smoothedCursorDiff = cursorPosition - SmoothedCursorPosition;
if (Controlled == this)
{
SmoothedCursorPosition = cursorPosition;
}
else
{
//apply some smoothing to the cursor positions of remote players when playing as a client
//to make aiming look a little less choppy
Vector2 smoothedCursorDiff = cursorPosition - SmoothedCursorPosition;
smoothedCursorDiff = NetConfig.InterpolateCursorPositionError(smoothedCursorDiff);
SmoothedCursorPosition = cursorPosition - smoothedCursorDiff;
}
@@ -1188,10 +1185,6 @@ namespace Barotrauma
if (PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.F))
{
AnimController.ReleaseStuckLimbs();
if (AIController != null && AIController is EnemyAIController enemyAI)
{
enemyAI.LatchOntoAI?.DeattachFromBody();
}
}
#endif
@@ -1271,7 +1264,15 @@ namespace Barotrauma
if (IsKeyDown(InputType.Aim) && selectedItems[i] != null) selectedItems[i].SecondaryUse(deltaTime, this);
}
}
#if CLIENT
if (SelectedConstruction != null && SelectedConstruction.ActiveHUDs.Any(ic => ic.GuiFrame != null && HUD.CloseHUD(ic.GuiFrame.Rect)))
{
//emulate a Select input to get the character to deselect the item server-side
keys[(int)InputType.Select].Hit = true;
SelectedConstruction = null;
}
#endif
if (SelectedConstruction != null)
{
if (IsKeyDown(InputType.Use)) SelectedConstruction.Use(deltaTime, this);
@@ -1649,8 +1650,7 @@ namespace Barotrauma
#if CLIENT
if (isLocalPlayer)
{
if (GUI.MouseOn == null &&
(!CharacterInventory.IsMouseOnInventory() || CharacterInventory.DraggingItemToWorld))
if (GUI.MouseOn == null && !CharacterInventory.IsMouseOnInventory())
{
if (findFocusedTimer <= 0.0f || Screen.Selected == GameMain.SubEditorScreen)
{
@@ -1665,12 +1665,10 @@ namespace Barotrauma
focusedItem = null;
}
findFocusedTimer -= deltaTime;
}
}
#endif
//climb ladders automatically when pressing up/down inside their trigger area
Ladder currentLadder = SelectedConstruction?.GetComponent<Ladder>();
if ((SelectedConstruction == null || currentLadder != null) &&
!AnimController.InWater && Screen.Selected != GameMain.SubEditorScreen)
if (SelectedConstruction == null && !AnimController.InWater && Screen.Selected != GameMain.SubEditorScreen)
{
bool climbInput = IsKeyDown(InputType.Up) || IsKeyDown(InputType.Down);
bool isControlled = Controlled == this;
@@ -1681,19 +1679,6 @@ namespace Barotrauma
float minDist = float.PositiveInfinity;
foreach (Ladder ladder in Ladder.List)
{
if (ladder == currentLadder)
{
continue;
}
else if (currentLadder != null)
{
//only switch from ladder to another if the ladders are above the current ladders and pressing up, or vice versa
if (ladder.Item.WorldPosition.Y > currentLadder.Item.WorldPosition.Y != IsKeyDown(InputType.Up))
{
continue;
}
}
if (CanInteractWith(ladder.Item, out float dist, checkLinked: false) && dist < minDist)
{
minDist = dist;
@@ -1710,7 +1695,7 @@ namespace Barotrauma
}
}
if (SelectedCharacter != null && (IsKeyHit(InputType.Grab) || IsKeyHit(InputType.Health))) //Let people use ladders and buttons and stuff when dragging chars
if (SelectedCharacter != null && IsKeyHit(InputType.Grab)) //Let people use ladders and buttons and stuff when dragging chars
{
DeselectCharacter();
}
@@ -804,12 +804,6 @@ namespace Barotrauma
var newItem = Item.Load(itemElement, inventory.Owner.Submarine, createNetworkEvent: true);
if (newItem == null) { continue; }
if (!MathUtils.NearlyEqual(newItem.Condition, newItem.MaxCondition) &&
GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
GameMain.NetworkMember.CreateEntityEvent(newItem, new object[] { NetEntityEvent.Type.Status });
}
int[] slotIndices = itemElement.GetAttributeIntArray("i", new int[] { 0 });
if (!slotIndices.Any())
{
@@ -153,10 +153,7 @@ namespace Barotrauma
//how high the strength has to be for the affliction icon to be shown in the UI
public readonly float ShowIconThreshold = 0.05f;
public readonly float MaxStrength = 100.0f;
//how high the strength has to be for the affliction icon to be shown with a health scanner
public readonly float ShowInHealthScannerThreshold = 0.05f;
public float BurnOverlayAlpha;
public float DamageOverlayAlpha;
@@ -260,8 +257,6 @@ namespace Barotrauma
ShowIconThreshold = element.GetAttributeFloat("showiconthreshold", Math.Max(ActivationThreshold, 0.05f));
MaxStrength = element.GetAttributeFloat("maxstrength", 100.0f);
ShowInHealthScannerThreshold = element.GetAttributeFloat("showinhealthscannerthreshold", Math.Max(ActivationThreshold, 0.05f));
DamageOverlayAlpha = element.GetAttributeFloat("damageoverlayalpha", 0.0f);
BurnOverlayAlpha = element.GetAttributeFloat("burnoverlayalpha", 0.0f);
@@ -460,7 +460,6 @@ namespace Barotrauma
{
affliction.Strength = 0.0f;
}
CalculateVitality();
}
private void AddLimbAffliction(Limb limb, Affliction newAffliction)
@@ -520,15 +520,14 @@ namespace Barotrauma
// Ignore blocking on items, because it causes cases where a Mudraptor cannot hit the hatch, for example.
wasHit = true;
}
else if (damageTarget is Structure wall && structureBody != null &&
(structureBody.UserData is Structure || (structureBody.UserData is Submarine sub && sub == wall.Submarine)))
else if (damageTarget is Structure && structureBody?.UserData is Structure)
{
// If the attack is aimed to a structure (wall) and hits a structure or the sub, it's successful
// If the attack is aimed to a structure and hits a structure, it's successful
wasHit = true;
}
else
{
// If there is nothing between, the hit is successful
// If the attack is aimed to a character but hits a structure, the hit is blocked.
wasHit = structureBody == null;
}
}