Files
LuaCsForBarotraumaEP/Subsurface/Source/Characters/Animation/Ragdoll.cs
juanjp600 0569a665f4 Fixed a server-side off-by-one error in the player input IDs
I'm guessing this one comes from the player's position not being updated immediately after the input is processed, so that's where most of the remaining error came from.
Also added some rounding to the horizontal velocity when approaching 0, and changed something that depended on the head limb to use the collider instead, which should hopefully further reduce the chance of syncing errors.
2017-01-05 22:37:14 -03:00

1353 lines
49 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
using FarseerPhysics.Dynamics.Joints;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Barotrauma.Networking;
using Lidgren.Network;
namespace Barotrauma
{
class Ragdoll
{
public static List<Ragdoll> list = new List<Ragdoll>();
protected Hull currentHull;
public Limb[] Limbs;
private bool frozen;
public bool Frozen
{
get { return frozen; }
set
{
if (frozen == value) return;
frozen = value;
foreach (Limb l in Limbs)
{
l.body.PhysEnabled = !frozen;
}
Collider.PhysEnabled = !frozen;
}
}
private Dictionary<LimbType, Limb> limbDictionary;
public RevoluteJoint[] limbJoints;
private bool simplePhysicsEnabled;
private Character character;
protected float strongestImpact;
public float headPosition, headAngle;
public float torsoPosition, torsoAngle;
protected double onFloorTimer;
private float splashSoundTimer;
//the movement speed of the ragdoll
public Vector2 movement;
//the target speed towards which movement is interpolated
protected Vector2 targetMovement;
//a movement vector that overrides targetmovement if trying to steer
//a Character to the position sent by server in multiplayer mode
protected Vector2 overrideTargetMovement;
protected float floorY;
protected float surfaceY;
protected bool inWater, headInWater;
public bool onGround;
private bool ignorePlatforms;
protected float colliderHeightFromFloor;
protected Structure stairs;
protected Direction dir;
public Direction TargetDir;
protected List<PhysicsBody> collider;
protected int colliderIndex = 0;
public PhysicsBody Collider
{
get
{
return collider[colliderIndex];
}
}
public int ColliderIndex
{
get
{
return colliderIndex;
}
set
{
if (value == colliderIndex) return;
if (value >= collider.Count) return;
if (collider[colliderIndex].height<collider[value].height)
{
Vector2 pos1 = collider[colliderIndex].SimPosition;
pos1.Y -= collider[colliderIndex].height * colliderHeightFromFloor;
Vector2 pos2 = pos1;
pos2.Y += collider[value].height * 1.1f;
if (GameMain.World.RayCast(pos1, pos2).Any(f => f.CollisionCategories.HasFlag(Physics.CollisionWall))) return;
}
collider[value].LinearVelocity = collider[colliderIndex].LinearVelocity;
collider[value].AngularVelocity = collider[colliderIndex].AngularVelocity;
Vector2 pos = collider[colliderIndex].SimPosition;
pos.Y -= collider[colliderIndex].height * 0.5f;
pos.Y += collider[value].height * 0.5f;
collider[value].SetTransform(pos, collider[colliderIndex].Rotation);
collider[value].PhysEnabled = !frozen;
collider[value].Enabled = !simplePhysicsEnabled;
collider[value].Submarine = collider[colliderIndex].Submarine;
collider[colliderIndex].PhysEnabled = false;
colliderIndex = value;
}
}
public float FloorY
{
get { return floorY; }
}
public float Mass
{
get;
private set;
}
public Limb MainLimb
{
get;
private set;
}
public Vector2 WorldPosition
{
get
{
return character.Submarine == null ?
ConvertUnits.ToDisplayUnits(Collider.SimPosition) :
ConvertUnits.ToDisplayUnits(Collider.SimPosition) + character.Submarine.Position;
}
}
public bool SimplePhysicsEnabled
{
get { return simplePhysicsEnabled; }
set
{
if (value == simplePhysicsEnabled) return;
simplePhysicsEnabled = value;
foreach (Limb limb in Limbs)
{
limb.body.Enabled = !simplePhysicsEnabled;
}
foreach (RevoluteJoint joint in limbJoints)
{
joint.Enabled = !simplePhysicsEnabled;
}
if (!simplePhysicsEnabled)
{
foreach (Limb limb in Limbs)
{
limb.body.SetTransform(Collider.SimPosition, Collider.Rotation);
}
}
}
}
public Vector2 TargetMovement
{
get
{
return (overrideTargetMovement == Vector2.Zero) ? targetMovement : overrideTargetMovement;
}
set
{
if (!MathUtils.IsValid(value)) return;
targetMovement.X = MathHelper.Clamp(value.X, -5.0f, 5.0f);
targetMovement.Y = MathHelper.Clamp(value.Y, -5.0f, 5.0f);
}
}
protected virtual float HeadPosition
{
get { return headPosition; }
}
protected virtual float HeadAngle
{
get { return headAngle; }
}
protected virtual float TorsoPosition
{
get { return torsoPosition; }
}
protected virtual float TorsoAngle
{
get { return torsoAngle; }
}
public float Dir
{
get { return ((dir == Direction.Left) ? -1.0f : 1.0f); }
}
public bool InWater
{
get { return inWater; }
}
public bool HeadInWater
{
get { return headInWater; }
}
public readonly bool CanEnterSubmarine;
public Hull CurrentHull
{
get { return currentHull; }
set
{
if (value == currentHull) return;
currentHull = value;
Submarine currSubmarine = currentHull == null ? null : currentHull.Submarine;
foreach (Limb limb in Limbs)
{
limb.body.Submarine = currSubmarine;
}
Collider.Submarine = currSubmarine;
}
}
public bool IgnorePlatforms
{
get { return ignorePlatforms; }
set
{
if (ignorePlatforms == value) return;
ignorePlatforms = value;
UpdateCollisionCategories();
}
}
public float ImpactTolerance
{
get;
private set;
}
public float StrongestImpact
{
get { return strongestImpact; }
set { strongestImpact = Math.Max(value, strongestImpact); }
}
public Structure Stairs
{
get { return stairs; }
}
public Ragdoll(Character character, XElement element)
{
list.Add(this);
this.character = character;
dir = Direction.Right;
float scale = ToolBox.GetAttributeFloat(element, "scale", 1.0f);
Limbs = new Limb[element.Elements("limb").Count()];
limbJoints = new RevoluteJoint[element.Elements("joint").Count()];
limbDictionary = new Dictionary<LimbType, Limb>();
headPosition = ToolBox.GetAttributeFloat(element, "headposition", 50.0f);
headPosition = ConvertUnits.ToSimUnits(headPosition);
headAngle = MathHelper.ToRadians(ToolBox.GetAttributeFloat(element, "headangle", 0.0f));
torsoPosition = ToolBox.GetAttributeFloat(element, "torsoposition", 50.0f);
torsoPosition = ConvertUnits.ToSimUnits(torsoPosition);
torsoAngle = MathHelper.ToRadians(ToolBox.GetAttributeFloat(element, "torsoangle", 0.0f));
ImpactTolerance = ToolBox.GetAttributeFloat(element, "impacttolerance", 50.0f);
CanEnterSubmarine = ToolBox.GetAttributeBool(element, "canentersubmarine", true);
colliderHeightFromFloor = ToolBox.GetAttributeFloat(element, "colliderheightfromfloor", 45.0f);
colliderHeightFromFloor = ConvertUnits.ToSimUnits(colliderHeightFromFloor);
collider = new List<PhysicsBody>();
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString())
{
case "limb":
byte ID = Convert.ToByte(subElement.Attribute("id").Value);
Limb limb = new Limb(character, subElement, scale);
limb.body.FarseerBody.OnCollision += OnLimbCollision;
Limbs[ID] = limb;
Mass += limb.Mass;
if (!limbDictionary.ContainsKey(limb.type)) limbDictionary.Add(limb.type, limb);
break;
case "joint":
AddJoint(subElement, scale);
break;
case "collider":
collider.Add(new PhysicsBody(subElement, scale));
collider[collider.Count - 1].FarseerBody.Friction = 0.05f;
collider[collider.Count - 1].FarseerBody.Restitution = 0.05f;
collider[collider.Count - 1].FarseerBody.FixedRotation = true;
collider[collider.Count - 1].CollisionCategories = Physics.CollisionCharacter;
collider[collider.Count - 1].FarseerBody.AngularDamping = 5.0f;
collider[collider.Count - 1].FarseerBody.FixedRotation = true;
collider[collider.Count - 1].FarseerBody.OnCollision += OnLimbCollision;
if (collider.Count > 1) collider[collider.Count - 1].PhysEnabled = false;
break;
}
}
if (collider[0] == null)
{
DebugConsole.ThrowError("No collider configured for \""+character.Name+"\"!");
collider[0] = new PhysicsBody(0.0f, 0.0f, 0.5f, 5.0f);
collider[0].BodyType = BodyType.Dynamic;
collider[0].CollisionCategories = Physics.CollisionCharacter;
collider[0].FarseerBody.AngularDamping = 5.0f;
collider[0].FarseerBody.FixedRotation = true;
collider[0].FarseerBody.OnCollision += OnLimbCollision;
}
UpdateCollisionCategories();
foreach (var joint in limbJoints)
{
joint.BodyB.SetTransform(
joint.BodyA.Position + (joint.LocalAnchorA - joint.LocalAnchorB)*0.1f,
(joint.LowerLimit + joint.UpperLimit) / 2.0f);
}
float startDepth = 0.1f;
float increment = 0.001f;
foreach (Character otherCharacter in Character.CharacterList)
{
if (otherCharacter==character) continue;
startDepth+=increment;
}
foreach (Limb limb in Limbs)
{
if (limb.sprite != null)
limb.sprite.Depth = startDepth + limb.sprite.Depth * 0.0001f;
}
Limb torso = GetLimb(LimbType.Torso);
Limb head = GetLimb(LimbType.Head);
MainLimb = torso == null ? head : torso;
}
public void AddJoint(XElement subElement, float scale = 1.0f)
{
byte limb1ID = Convert.ToByte(subElement.Attribute("limb1").Value);
byte limb2ID = Convert.ToByte(subElement.Attribute("limb2").Value);
Vector2 limb1Pos = ToolBox.GetAttributeVector2(subElement, "limb1anchor", Vector2.Zero) * scale;
limb1Pos = ConvertUnits.ToSimUnits(limb1Pos);
Vector2 limb2Pos = ToolBox.GetAttributeVector2(subElement, "limb2anchor", Vector2.Zero) * scale;
limb2Pos = ConvertUnits.ToSimUnits(limb2Pos);
RevoluteJoint joint = new RevoluteJoint(Limbs[limb1ID].body.FarseerBody, Limbs[limb2ID].body.FarseerBody, limb1Pos, limb2Pos);
joint.CollideConnected = false;
if (subElement.Attribute("lowerlimit") != null)
{
joint.LimitEnabled = true;
joint.LowerLimit = float.Parse(subElement.Attribute("lowerlimit").Value) * ((float)Math.PI / 180.0f);
joint.UpperLimit = float.Parse(subElement.Attribute("upperlimit").Value) * ((float)Math.PI / 180.0f);
}
joint.MotorEnabled = true;
joint.MaxMotorTorque = 0.25f;
GameMain.World.AddJoint(joint);
for (int i = 0; i < limbJoints.Length; i++)
{
if (limbJoints[i] != null) continue;
limbJoints[i] = joint;
return;
}
Array.Resize(ref limbJoints, limbJoints.Length + 1);
limbJoints[limbJoints.Length - 1] = joint;
}
public void AddLimb(Limb limb)
{
limb.body.FarseerBody.OnCollision += OnLimbCollision;
Array.Resize(ref Limbs, Limbs.Length + 1);
Limbs[Limbs.Length-1] = limb;
Mass += limb.Mass;
if (!limbDictionary.ContainsKey(limb.type)) limbDictionary.Add(limb.type, limb);
}
public bool OnLimbCollision(Fixture f1, Fixture f2, Contact contact)
{
Structure structure = f2.Body.UserData as Structure;
if (f2.Body.UserData is Submarine && character.Submarine == (Submarine)f2.Body.UserData) return false;
//always collides with bodies other than structures
if (structure == null)
{
CalculateImpact(f1, f2, contact);
return true;
}
Vector2 colliderBottom = GetColliderBottom();
if (structure.IsPlatform)
{
if (ignorePlatforms) return false;
//the collision is ignored if the lowest limb is under the platform
//if (lowestLimb==null || lowestLimb.Position.Y < structure.Rect.Y) return false;
if (colliderBottom.Y < ConvertUnits.ToSimUnits(structure.Rect.Y - 5)) return false;
if (f1.Body.Position.Y < ConvertUnits.ToSimUnits(structure.Rect.Y - 5)) return false;
}
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
Vector2 normal; FarseerPhysics.Common.FixedArray2<Vector2> points;
contact.GetWorldManifold(out normal, out points);
if (points[0].Y > Collider.SimPosition.Y) return false;
//5. in water
if (inWater && targetMovement.Y < 0.5f) return false;
//---------------
stairs = structure;
}
CalculateImpact(f1, f2, contact);
return true;
}
private void CalculateImpact(Fixture f1, Fixture f2, Contact contact)
{
if (character.DisableImpactDamageTimer > 0.0f) return;
Vector2 normal = contact.Manifold.LocalNormal;
//Vector2 avgVelocity = Vector2.Zero;
//foreach (Limb limb in Limbs)
//{
// avgVelocity += limb.LinearVelocity;
//}
Vector2 velocity = f1.Body.LinearVelocity;
if (character.Submarine == null && f2.Body.UserData is Submarine) velocity -= ((Submarine)f2.Body.UserData).Velocity;
float impact = Vector2.Dot(velocity, -normal);
float volume = Math.Min(impact-3.0f, 1.0f);
if (f1.Body.UserData is Limb)
{
Limb limb = (Limb)f1.Body.UserData;
if (impact > 3.0f && limb.HitSound != null && limb.soundTimer <= 0.0f)
{
limb.soundTimer = Limb.SoundInterval;
limb.HitSound.Play(volume, impact * 100.0f, limb.WorldPosition);
}
}
else if (f1.Body == Collider.FarseerBody)
{
if (!character.IsRemotePlayer || GameMain.Server != null)
{
if (impact > ImpactTolerance)
{
character.AddDamage(CauseOfDeath.Damage, impact - ImpactTolerance, null);
SoundPlayer.PlayDamageSound(DamageSoundType.LimbBlunt, strongestImpact, Collider);
strongestImpact = Math.Max(strongestImpact, impact - ImpactTolerance);
}
}
if (Character.Controlled == character) GameMain.GameScreen.Cam.Shake = strongestImpact;
}
}
public virtual void Draw(SpriteBatch spriteBatch)
{
if (simplePhysicsEnabled) return;
Collider.UpdateDrawPosition();
foreach (Limb limb in Limbs)
{
limb.Draw(spriteBatch);
}
}
public void DebugDraw(SpriteBatch spriteBatch)
{
if (!GameMain.DebugDraw || !character.Enabled) return;
if (simplePhysicsEnabled) return;
foreach (Limb limb in Limbs)
{
if (limb.pullJoint != null)
{
Vector2 pos = ConvertUnits.ToDisplayUnits(limb.pullJoint.WorldAnchorA);
if (currentHull != null) pos += currentHull.Submarine.DrawPosition;
pos.Y = -pos.Y;
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)pos.Y, 5, 5), Color.Red, true, 0.01f);
}
limb.body.DebugDraw(spriteBatch, inWater ? Color.Cyan : Color.White);
}
Collider.DebugDraw(spriteBatch, frozen ? Color.Red : (inWater ? Color.SkyBlue : Color.Gray));
spriteBatch.DrawString(GUI.Font, Collider.LinearVelocity.X.ToString(), new Vector2(Collider.DrawPosition.X, -Collider.DrawPosition.Y), Color.Orange);
foreach (RevoluteJoint joint in limbJoints)
{
Vector2 pos = ConvertUnits.ToDisplayUnits(joint.WorldAnchorA);
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 5, 5), Color.White, true);
pos = ConvertUnits.ToDisplayUnits(joint.WorldAnchorB);
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 5, 5), Color.White, true);
}
foreach (Limb limb in Limbs)
{
if (limb.body.TargetPosition != Vector2.Zero)
{
Vector2 pos = ConvertUnits.ToDisplayUnits(limb.body.TargetPosition);
if (currentHull != null) pos += currentHull.Submarine.DrawPosition;
pos.Y = -pos.Y;
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X - 10, (int)pos.Y - 10, 20, 20), Color.Cyan, false, 0.01f);
GUI.DrawLine(spriteBatch, pos, new Vector2(limb.WorldPosition.X, -limb.WorldPosition.Y), Color.Cyan);
}
}
if (character.MemPos.Count > 1)
{
Vector2 prevPos = ConvertUnits.ToDisplayUnits(character.MemPos[0].Position);
if (currentHull != null) prevPos += currentHull.Submarine.DrawPosition;
prevPos.Y = -prevPos.Y;
for (int i = 1; i < character.MemPos.Count; i++ )
{
Vector2 currPos = ConvertUnits.ToDisplayUnits(character.MemPos[i].Position);
if (currentHull != null) currPos += currentHull.Submarine.DrawPosition;
currPos.Y = -currPos.Y;
GUI.DrawRectangle(spriteBatch, new Rectangle((int)currPos.X - 3, (int)currPos.Y - 3, 6, 6), Color.Cyan*0.6f, true, 0.01f);
GUI.DrawLine(spriteBatch, prevPos, currPos, Color.Cyan*0.6f, 0, 3);
prevPos = currPos;
}
}
if (ignorePlatforms)
{
GUI.DrawLine(spriteBatch,
new Vector2(Collider.DrawPosition.X, -Collider.DrawPosition.Y),
new Vector2(Collider.DrawPosition.X, -Collider.DrawPosition.Y + 50),
Color.Orange, 0, 5);
}
}
public virtual void Flip()
{
dir = (dir == Direction.Left) ? Direction.Right : Direction.Left;
for (int i = 0; i < limbJoints.Length; i++)
{
float lowerLimit = -limbJoints[i].UpperLimit;
float upperLimit = -limbJoints[i].LowerLimit;
limbJoints[i].LowerLimit = lowerLimit;
limbJoints[i].UpperLimit = upperLimit;
limbJoints[i].LocalAnchorA = new Vector2(-limbJoints[i].LocalAnchorA.X, limbJoints[i].LocalAnchorA.Y);
limbJoints[i].LocalAnchorB = new Vector2(-limbJoints[i].LocalAnchorB.X, limbJoints[i].LocalAnchorB.Y);
}
foreach (Limb limb in Limbs)
{
if (limb == null) continue;
if (limb.sprite != null)
{
Vector2 spriteOrigin = limb.sprite.Origin;
spriteOrigin.X = limb.sprite.SourceRect.Width - spriteOrigin.X;
limb.sprite.Origin = spriteOrigin;
}
limb.Dir = Dir;
if (limb.LightSource != null)
{
limb.LightSource.FlipX();
}
if (limb.pullJoint != null)
{
limb.pullJoint.LocalAnchorA =
new Vector2(
-limb.pullJoint.LocalAnchorA.X,
limb.pullJoint.LocalAnchorA.Y);
}
}
}
public Vector2 GetCenterOfMass()
{
Vector2 centerOfMass = Vector2.Zero;
foreach (Limb limb in Limbs)
{
centerOfMass += limb.Mass * limb.SimPosition;
}
centerOfMass /= Mass;
return centerOfMass;
}
/// <param name="pullFromCenter">if false, force is applied to the position of pullJoint</param>
protected void MoveLimb(Limb limb, Vector2 pos, float amount, bool pullFromCenter = false)
{
limb.MoveToPos(pos, amount, pullFromCenter);
}
public void ResetPullJoints()
{
for (int i = 0; i < Limbs.Length; i++)
{
if (Limbs[i] == null || Limbs[i].pullJoint == null) continue;
Limbs[i].pullJoint.Enabled = false;
}
}
public static void UpdateAll(Camera cam, float deltaTime)
{
foreach (Ragdoll r in list)
{
r.Update(cam, deltaTime);
}
}
public void FindHull(Vector2? worldPosition = null, bool setSubmarine = true)
{
if (!CanEnterSubmarine)
{
return;
}
Vector2 findPos = worldPosition==null ? this.WorldPosition : (Vector2)worldPosition;
Hull newHull = Hull.FindHull(findPos, currentHull);
if (newHull == currentHull) return;
if (setSubmarine)
{
//in -> out
if (newHull == null && currentHull.Submarine != null)
{
for (int i = -1; i < 2; i += 2)
{
//don't teleport outside the sub if right next to a hull
if (Hull.FindHull(findPos + new Vector2(Submarine.GridSize.X * 4.0f * i, 0.0f), currentHull) != null) return;
if (Hull.FindHull(findPos + new Vector2(0.0f, Submarine.GridSize.Y * 4.0f * i), currentHull) != null) return;
}
if (Gap.FindAdjacent(currentHull.ConnectedGaps, findPos, 150.0f) != null) return;
Teleport(ConvertUnits.ToSimUnits(currentHull.Submarine.Position), currentHull.Submarine.Velocity);
}
//out -> in
else if (currentHull == null && newHull.Submarine != null)
{
Teleport(-ConvertUnits.ToSimUnits(newHull.Submarine.Position), -newHull.Submarine.Velocity);
}
//from one sub to another
else if (newHull != null && currentHull != null && newHull.Submarine != currentHull.Submarine)
{
Teleport(ConvertUnits.ToSimUnits(currentHull.Submarine.Position - newHull.Submarine.Position),
Vector2.Zero);
}
}
CurrentHull = newHull;
character.Submarine = currentHull == null ? null : currentHull.Submarine;
UpdateCollisionCategories();
}
public void Teleport(Vector2 moveAmount, Vector2 velocityChange)
{
foreach (Limb limb in Limbs)
{
if (limb.body.FarseerBody.ContactList == null) continue;
ContactEdge ce = limb.body.FarseerBody.ContactList;
while (ce != null && ce.Contact != null)
{
ce.Contact.Enabled = false;
ce = ce.Next;
}
}
foreach (Limb limb in Limbs)
{
limb.body.LinearVelocity += velocityChange;
}
//character.Stun = 0.1f;
character.DisableImpactDamageTimer = 0.25f;
SetPosition(Collider.SimPosition + moveAmount);
character.CursorPosition += moveAmount;
}
private void UpdateCollisionCategories()
{
Category wall = currentHull == null ?
Physics.CollisionLevel | Physics.CollisionWall
: Physics.CollisionWall;
Category collisionCategory = (ignorePlatforms) ?
wall | Physics.CollisionProjectile | Physics.CollisionStairs
: wall | Physics.CollisionProjectile | Physics.CollisionPlatform | Physics.CollisionStairs;
Collider.CollidesWith = collisionCategory;
foreach (Limb limb in Limbs)
{
if (limb.ignoreCollisions) continue;
try
{
limb.body.CollidesWith = collisionCategory;
}
catch (Exception e)
{
DebugConsole.ThrowError("Failed to update ragdoll limb collisioncategories", e);
}
}
}
protected bool levitatingCollider = true;
public void Update(Camera cam, float deltaTime)
{
if (!character.Enabled || Frozen) return;
UpdateNetPlayerPosition(deltaTime);
CheckDistFromCollider();
Vector2 flowForce = Vector2.Zero;
FindHull();
splashSoundTimer -= deltaTime;
//ragdoll isn't in any room -> it's in the water
if (currentHull == null)
{
inWater = true;
headInWater = true;
}
else
{
flowForce = GetFlowForce();
inWater = false;
headInWater = false;
float waterSurface = ConvertUnits.ToSimUnits(currentHull.Surface);
float floorY = GetFloorY();
if (currentHull.Volume > currentHull.FullVolume * 0.95f ||
(waterSurface - floorY > HeadPosition * 0.95f && Collider.SimPosition.Y < waterSurface))
inWater = true;
}
if (flowForce.LengthSquared() > 0.001f)
{
Collider.ApplyForce(flowForce);
}
if (currentHull == null ||
currentHull.Volume > currentHull.FullVolume * 0.95f ||
ConvertUnits.ToSimUnits(currentHull.Surface) > Collider.SimPosition.Y)
{
Collider.ApplyWaterForces();
}
foreach (Limb limb in Limbs)
{
//find the room which the limb is in
//the room where the ragdoll is in is used as the "guess", meaning that it's checked first
Hull limbHull = currentHull == null ? null : Hull.FindHull(limb.WorldPosition, currentHull);
bool prevInWater = limb.inWater;
limb.inWater = false;
if (limbHull == null)
{
//limb isn't in any room -> it's in the water
limb.inWater = true;
if (limb.type == LimbType.Head) headInWater = true;
}
else if (limbHull.Volume > 0.0f && Submarine.RectContains(limbHull.Rect, limb.Position))
{
if (limb.Position.Y < limbHull.Surface)
{
limb.inWater = true;
if (flowForce.LengthSquared() > 0.001f)
{
limb.body.ApplyForce(flowForce);
}
surfaceY = limbHull.Surface;
if (limb.type == LimbType.Head)
{
headInWater = true;
}
}
//the limb has gone through the surface of the water
if (Math.Abs(limb.LinearVelocity.Y) > 5.0f && limb.inWater != prevInWater)
{
//create a splash particle
GameMain.ParticleManager.CreateParticle("watersplash",
new Vector2(limb.Position.X, limbHull.Surface) + limbHull.Submarine.Position,
new Vector2(0.0f, Math.Abs(-limb.LinearVelocity.Y * 20.0f)),
0.0f, limbHull);
GameMain.ParticleManager.CreateParticle("bubbles",
new Vector2(limb.Position.X, limbHull.Surface) + limbHull.Submarine.Position,
limb.LinearVelocity * 0.001f,
0.0f, limbHull);
//if the Character dropped into water, create a wave
if (limb.LinearVelocity.Y < 0.0f)
{
if (splashSoundTimer <= 0.0f)
{
SoundPlayer.PlaySplashSound(limb.WorldPosition, Math.Abs(limb.LinearVelocity.Y) + Rand.Range(-5.0f, 0.0f));
splashSoundTimer = 0.5f;
}
//1.0 when the limb is parallel to the surface of the water
// = big splash and a large impact
float parallel = (float)Math.Abs(Math.Sin(limb.Rotation));
Vector2 impulse = Vector2.Multiply(limb.LinearVelocity, -parallel * limb.Mass);
//limb.body.ApplyLinearImpulse(impulse);
int n = (int)((limb.Position.X - limbHull.Rect.X) / Hull.WaveWidth);
limbHull.WaveVel[n] = Math.Min(impulse.Y * 1.0f, 5.0f);
}
}
}
if (limb.LightSource != null)
{
limb.LightSource.Rotation = limb.Rotation;
}
limb.Update(deltaTime);
}
bool onStairs = stairs != null;
stairs = null;
var contacts = Collider.FarseerBody.ContactList;
while (Collider.FarseerBody.Enabled && contacts != null && contacts.Contact != null)
{
if (contacts.Contact.Enabled && contacts.Contact.IsTouching)
{
Vector2 normal;
FarseerPhysics.Common.FixedArray2<Vector2> points;
contacts.Contact.GetWorldManifold(out normal, out points);
switch (contacts.Contact.FixtureA.CollisionCategories)
{
case Physics.CollisionStairs:
Structure structure = contacts.Contact.FixtureA.Body.UserData as Structure;
if (structure != null && onStairs)
{
stairs = structure;
}
break;
}
// case Physics.CollisionPlatform:
// Structure platform = contacts.Contact.FixtureA.Body.UserData as Structure;
// if (IgnorePlatforms || colliderBottom.Y < ConvertUnits.ToSimUnits(platform.Rect.Y - 15))
// {
// contacts = contacts.Next;
// continue;
// }
// break;
// case Physics.CollisionWall:
// break;
// default:
// contacts = contacts.Next;
// continue;
//}
if (points[0].Y < Collider.SimPosition.Y)
{
floorY = Math.Max(floorY, points[0].Y);
onGround = true;
onFloorTimer = 0.1f;
}
}
contacts = contacts.Next;
}
//the ragdoll "stays on ground" for 50 millisecs after separation
if (onFloorTimer <= 0.0f)
{
onGround = false;
}
else
{
onFloorTimer -= deltaTime;
}
Vector2 rayStart = Collider.SimPosition;
Vector2 rayEnd = rayStart;
rayEnd.Y -= Collider.height * 0.5f + Collider.radius + colliderHeightFromFloor*1.2f;
Vector2 colliderBottomDisplay = ConvertUnits.ToDisplayUnits(GetColliderBottom());
if (!inWater && !character.IsDead && !character.IsUnconscious && levitatingCollider && Collider.LinearVelocity.Y>-ImpactTolerance)
{
float closestFraction = 1.0f;
Fixture closestFixture = null;
GameMain.World.RayCast((fixture, point, normal, fraction) =>
{
switch (fixture.CollisionCategories)
{
case Physics.CollisionStairs:
Structure structure = fixture.Body.UserData as Structure;
if (colliderBottomDisplay.Y < structure.Rect.Y - structure.Rect.Height + 30 && TargetMovement.Y < 0.5f) return -1;
break;
case Physics.CollisionPlatform:
Structure platform = fixture.Body.UserData as Structure;
if (IgnorePlatforms || colliderBottomDisplay.Y < platform.Rect.Y - 16) return -1;
break;
case Physics.CollisionWall:
break;
default:
return -1;
}
if (fraction < closestFraction)
{
closestFraction = fraction;
closestFixture = fixture;
}
return closestFraction;
}
, rayStart, rayEnd);
if (closestFraction < 1.0f && closestFixture!=null)
{
bool forceImmediate = false;
onGround = true;
switch (closestFixture.CollisionCategories)
{
case Physics.CollisionStairs:
stairs = closestFixture.Body.UserData as Structure;
onStairs = true;
forceImmediate = true;
break;
}
float tfloorY = rayStart.Y + (rayEnd.Y - rayStart.Y) * closestFraction;
float targetY = tfloorY + Collider.height * 0.5f + Collider.radius + colliderHeightFromFloor;
if (Math.Abs(Collider.SimPosition.Y - targetY) > 0.01f && Collider.SimPosition.Y<targetY && !forceImmediate)
{
Vector2 newSpeed = Collider.LinearVelocity;
newSpeed.Y = (targetY - Collider.SimPosition.Y)*5.0f;
Collider.LinearVelocity = newSpeed;
}
else
{
Vector2 newSpeed = Collider.LinearVelocity;
newSpeed.Y = 0.0f;
Collider.LinearVelocity = newSpeed;
Vector2 newPos = Collider.SimPosition;
newPos.Y = targetY;
Collider.SetTransform(newPos, Collider.Rotation);
}
}
}
}
protected float GetFloorY(Limb refLimb = null)
{
PhysicsBody refBody = refLimb == null ? Collider : refLimb.body;
Vector2 rayStart = refBody.SimPosition;
Vector2 rayEnd = rayStart - new Vector2(0.0f, TorsoPosition);
var lowestLimb = FindLowestLimb();
float closestFraction = 1;
GameMain.World.RayCast((fixture, point, normal, fraction) =>
{
switch (fixture.CollisionCategories)
{
case Physics.CollisionStairs:
if (inWater && TargetMovement.Y < 0.5f) return -1;
break;
case Physics.CollisionPlatform:
Structure platform = fixture.Body.UserData as Structure;
if (IgnorePlatforms || lowestLimb.Position.Y < platform.Rect.Y) return -1;
break;
case Physics.CollisionWall:
break;
default:
return -1;
}
if (fraction < closestFraction)
{
closestFraction = fraction;
}
return closestFraction;
}
, rayStart, rayEnd);
if (closestFraction == 1) //raycast didn't hit anything
{
return (currentHull == null) ? -1000.0f : ConvertUnits.ToSimUnits(currentHull.Rect.Y - currentHull.Rect.Height);
}
else
{
return rayStart.Y + (rayEnd.Y - rayStart.Y) * closestFraction;
}
}
public void SetPosition(Vector2 simPosition, bool lerp = false)
{
Vector2 moveAmount = simPosition - Collider.SimPosition;
Collider.SetTransform(simPosition, Collider.Rotation);
foreach (Limb limb in Limbs)
{
//check visibility from the new position of the collider to the new position of this limb
Vector2 movePos = limb.SimPosition + moveAmount;
TrySetLimbPosition(limb, simPosition, movePos, lerp);
}
}
protected void TrySetLimbPosition(Limb limb, Vector2 original, Vector2 simPosition, bool lerp = false)
{
Vector2 movePos = simPosition;
if (original != simPosition)
{
Category collisionCategory = Physics.CollisionWall | Physics.CollisionLevel;
//if (!ignorePlatforms) collisionCategory |= Physics.CollisionPlatform;
Body body = Submarine.PickBody(original, simPosition, null, collisionCategory);
//if there's something in between the limbs
if (body != null)
{
//move the limb close to the position where the raycast hit something
movePos = original + ((simPosition - original) * Submarine.LastPickedFraction * 0.9f);
}
}
if (lerp)
{
limb.body.TargetPosition = movePos;
limb.body.MoveToTargetPosition(Vector2.DistanceSquared(limb.SimPosition, movePos) < 100.0f);
}
else
{
limb.body.SetTransform(movePos, limb.Rotation);
if (limb.pullJoint != null)
{
limb.pullJoint.WorldAnchorB = limb.pullJoint.WorldAnchorA;
limb.pullJoint.Enabled = false;
}
}
}
private bool collisionsDisabled;
protected void CheckDistFromCollider()
{
float allowedDist = Math.Max(Math.Max(Collider.radius, Collider.width), Collider.height) * 2.0f;
float resetDist = allowedDist * 10.0f;
float distSqrd = Vector2.DistanceSquared(Collider.SimPosition, MainLimb.SimPosition);
if (distSqrd > resetDist * resetDist)
{
//ragdoll way too far, reset position
SetPosition(Collider.SimPosition, true);
}
if (distSqrd > allowedDist * allowedDist)
{
//ragdoll too far from the collider, disable collisions until it's close enough
//(in case the ragdoll has gotten stuck somewhere)
foreach (Limb limb in Limbs)
{
limb.body.CollidesWith = Physics.CollisionNone;
}
collisionsDisabled = true;
}
else if (collisionsDisabled)
{
//set the position of the ragdoll to make sure limbs don't get stuck inside walls when re-enabling collisions
SetPosition(Collider.SimPosition, true);
UpdateCollisionCategories();
collisionsDisabled = false;
}
}
private void UpdateNetPlayerPosition(float deltaTime)
{
if (GameMain.NetworkMember == null) return;
if (character != GameMain.NetworkMember.Character || !character.AllowMovement)
{
//use simple interpolation for other players' characters and characters that can't move
if (character.MemPos.Count > 0)
{
Collider.LinearVelocity = Vector2.Zero;
Collider.CorrectPosition(character.MemPos, deltaTime, out overrideTargetMovement);
//unconscious/dead characters can't correct their position using AnimController movement
// -> we need to correct it manually
if (!character.AllowMovement)
{
Collider.LinearVelocity = overrideTargetMovement;
MainLimb.pullJoint.WorldAnchorB = Collider.SimPosition;
MainLimb.pullJoint.Enabled = true;
}
}
character.MemLocalPos.Clear();
}
else
{
if (character.MemPos.Count < 1) return;
overrideTargetMovement = Vector2.Zero;
PosInfo serverPos = character.MemPos.Last();
int localPosIndex = character.MemLocalPos.FindIndex(m => m.ID == serverPos.ID);
if (localPosIndex > -1)
{
PosInfo localPos = character.MemLocalPos[localPosIndex];
Vector2 positionError = serverPos.Position - localPos.Position;
float errorMagnitude = positionError.Length();
if (errorMagnitude > 2.0f)
{
//predicted position was way off, reset completely
Collider.SetTransform(serverPos.Position, Collider.Rotation);
//local positions are incorrect now -> just clear the list
character.MemLocalPos.Clear();
}
else if (errorMagnitude > Collider.LinearVelocity.Length() / 10.0f + 0.02f)
{
//our prediction differs from the server position
//-> we need to move the saved local position and all the positions saved after it
//(a better way to do this would be to move the character back to
//serverPos and "replay" inputs to see where the character should be now)
for (int i = localPosIndex; i < character.MemLocalPos.Count; i++)
{
character.MemLocalPos[i] =
new PosInfo(
character.MemLocalPos[i].Position + positionError,
character.MemLocalPos[i].Direction,
character.MemLocalPos[i].ID);
}
Collider.SetTransform(character.MemLocalPos.Last().Position, Collider.Rotation);
}
}
if (character.MemLocalPos.Count > 120) character.MemLocalPos.RemoveRange(0, character.MemLocalPos.Count - 120);
character.MemPos.Clear();
}
}
private Vector2 GetFlowForce()
{
Vector2 limbPos = ConvertUnits.ToDisplayUnits(Limbs[0].SimPosition);
Vector2 force = Vector2.Zero;
foreach (MapEntity e in MapEntity.mapEntityList)
{
Gap gap = e as Gap;
if (gap == null || gap.FlowTargetHull != currentHull || gap.LerpedFlowForce == Vector2.Zero) continue;
Vector2 gapPos = gap.SimPosition;
float dist = Vector2.Distance(limbPos, gapPos);
force += Vector2.Normalize(gap.LerpedFlowForce) * (Math.Max(gap.LerpedFlowForce.Length() - dist, 0.0f) / 500.0f);
}
if (force.Length() > 20.0f) return force;
return force;
}
public Limb GetLimb(LimbType limbType)
{
Limb limb = null;
limbDictionary.TryGetValue(limbType, out limb);
return limb;
}
public Vector2 GetColliderBottom()
{
float halfHeight = Collider.height * 0.5f + Collider.radius;
Vector2 offset = Vector2.Zero;
offset.Y = -colliderHeightFromFloor;
return Collider.SimPosition + offset +
new Vector2((float)Math.Sin(Collider.Rotation), -(float)Math.Cos(Collider.Rotation)) * halfHeight;
}
public Limb FindLowestLimb()
{
Limb lowestLimb = null;
foreach (Limb limb in Limbs)
{
if (lowestLimb == null)
lowestLimb = limb;
else if (limb.SimPosition.Y < lowestLimb.SimPosition.Y)
lowestLimb = limb;
}
return lowestLimb;
}
public void Remove()
{
foreach (Limb l in Limbs)
{
l.Remove();
}
Limbs = null;
foreach (PhysicsBody b in collider)
{
b.Remove();
}
foreach (RevoluteJoint joint in limbJoints)
{
GameMain.World.RemoveJoint(joint);
}
limbJoints = null;
list.Remove(this);
}
}
}