05cc34afd8
commit 1473f77ba0c85b80d48dce3e0fca809a9d5da98c Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Fri Mar 22 12:39:03 2019 +0200 Server automatically ends rounds if there have been no players alive in 60 seconds and respawning is not allowed during the round. Otherwise campaign rounds can get "bricked" if all the clients die/disconnect and there's no-one who's allowed to manually end the round. Closes #1319 commit 7096983fb10e48c2866393d30420bfaa79a0719b Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Fri Mar 22 12:03:10 2019 +0200 Some extra error logging in an attempt to diagnose #1327 (seems that the server is trying to start a campaign round after the campaign has been disposed and another game mode selected). commit 2d7a3be83cd8865869837879b965fa9aeb046042 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Fri Mar 22 10:53:08 2019 +0200 Use null as the rotation and angular velocity of a character's collider in network state buffers if the collider is set to a fixed rotation (i.e. when standing). Previously the _current_ rotation and angular velocity was used if the server didn't send the values, which caused the collider to "wobble" when getting up from the ground, because the clients used the current rotation and angular velocity to determine if their predicted state is correct, even though they should've been using past states (or rather, not attempting to correct at all because both the rotation and ang velocity should be 0 when the rotation is fixed). commit 2e2fd7078798703bc5d6ae398f75fa580ecca566 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Thu Mar 21 22:44:11 2019 +0200 Disabled kicking clients out of the server if their Steam auth becomes invalid after they've already been authenticated. Got some reports from players about this occasionally happening for no apparent reason, and I don't see the need to kick the clients if they've already been successfully authenticated during that session.
857 lines
30 KiB
C#
857 lines
30 KiB
C#
using Barotrauma.Networking;
|
|
using FarseerPhysics;
|
|
using FarseerPhysics.Dynamics;
|
|
using FarseerPhysics.Factories;
|
|
using Lidgren.Network;
|
|
using Microsoft.Xna.Framework;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Xml.Linq;
|
|
|
|
namespace Barotrauma
|
|
{
|
|
class PosInfo
|
|
{
|
|
public Vector2 Position
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public float? Rotation
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public Vector2 LinearVelocity
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public float? AngularVelocity
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public readonly float Timestamp;
|
|
public readonly UInt16 ID;
|
|
|
|
public PosInfo(Vector2 pos, float? rotation, Vector2 linearVelocity, float? angularVelocity, float time)
|
|
: this(pos, rotation, linearVelocity, angularVelocity, 0, time)
|
|
{
|
|
}
|
|
|
|
public PosInfo(Vector2 pos, float? rotation, Vector2 linearVelocity, float? angularVelocity, UInt16 ID)
|
|
: this(pos, rotation, linearVelocity, angularVelocity, ID, 0.0f)
|
|
{
|
|
}
|
|
|
|
protected PosInfo(Vector2 pos, float? rotation, Vector2 linearVelocity, float? angularVelocity, UInt16 ID, float time)
|
|
{
|
|
Position = pos;
|
|
Rotation = rotation;
|
|
LinearVelocity = linearVelocity;
|
|
AngularVelocity = angularVelocity;
|
|
|
|
this.ID = ID;
|
|
Timestamp = time;
|
|
}
|
|
|
|
public void TransformOutToInside(Submarine submarine)
|
|
{
|
|
//transform outside coordinates to in-sub coordinates
|
|
Position -= ConvertUnits.ToSimUnits(submarine.Position);
|
|
}
|
|
|
|
public void TransformInToOutside()
|
|
{
|
|
var sub = Submarine.FindContaining(ConvertUnits.ToDisplayUnits(Position));
|
|
if (sub != null)
|
|
{
|
|
Position += ConvertUnits.ToSimUnits(sub.Position);
|
|
}
|
|
}
|
|
|
|
public void Translate(Vector2 posAmount,float rotationAmount)
|
|
{
|
|
Position += posAmount; Rotation += rotationAmount;
|
|
}
|
|
}
|
|
|
|
partial class PhysicsBody
|
|
{
|
|
public enum Shape
|
|
{
|
|
Circle, Rectangle, Capsule, HorizontalCapsule
|
|
};
|
|
|
|
private static List<PhysicsBody> list = new List<PhysicsBody>();
|
|
public static List<PhysicsBody> List
|
|
{
|
|
get { return list; }
|
|
}
|
|
|
|
//the farseer physics body of the item
|
|
private Body body;
|
|
protected Vector2 prevPosition;
|
|
protected float prevRotation;
|
|
|
|
protected Vector2? targetPosition;
|
|
protected float? targetRotation;
|
|
|
|
private Vector2 drawPosition;
|
|
private float drawRotation;
|
|
|
|
public Vector2 LastSentPosition
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
private Shape bodyShape;
|
|
public float height, width, radius;
|
|
|
|
private float density;
|
|
|
|
//the direction the item is facing (for example, a gun has to be
|
|
//flipped horizontally if the Character holding it turns around)
|
|
float dir = 1.0f;
|
|
|
|
private Vector2 drawOffset;
|
|
private float rotationOffset;
|
|
|
|
private float lastProcessedNetworkState;
|
|
|
|
public float? PositionSmoothingFactor;
|
|
|
|
public Shape BodyShape
|
|
{
|
|
get { return bodyShape; }
|
|
}
|
|
|
|
public Vector2? TargetPosition
|
|
{
|
|
get { return targetPosition; }
|
|
set
|
|
{
|
|
if (value == null)
|
|
{
|
|
targetPosition = null;
|
|
}
|
|
else
|
|
{
|
|
if (!IsValidValue(value.Value, "target position", -1e5f, 1e5f)) return;
|
|
targetPosition = new Vector2(
|
|
MathHelper.Clamp(((Vector2)value).X, -10000.0f, 10000.0f),
|
|
MathHelper.Clamp(((Vector2)value).Y, -10000.0f, 10000.0f));
|
|
}
|
|
}
|
|
}
|
|
|
|
public float? TargetRotation
|
|
{
|
|
get { return targetRotation; }
|
|
set
|
|
{
|
|
if (value == null)
|
|
{
|
|
targetRotation = null;
|
|
}
|
|
else
|
|
{
|
|
if (!IsValidValue(value.Value, "target rotation")) return;
|
|
targetRotation = value;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
public Vector2 DrawPosition
|
|
{
|
|
get { return Submarine == null ? drawPosition : drawPosition + Submarine.DrawPosition; }
|
|
}
|
|
|
|
public float DrawRotation
|
|
{
|
|
get { return drawRotation; }
|
|
}
|
|
|
|
public Submarine Submarine;
|
|
|
|
public float Dir
|
|
{
|
|
get { return dir; }
|
|
set { dir = value; }
|
|
}
|
|
|
|
private bool isEnabled = true;
|
|
private bool isPhysEnabled = true;
|
|
|
|
public bool Enabled
|
|
{
|
|
get { return isEnabled; }
|
|
set
|
|
{
|
|
isEnabled = value;
|
|
try
|
|
{
|
|
if (isEnabled) body.Enabled = isPhysEnabled; else body.Enabled = false;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
DebugConsole.ThrowError("Exception in PhysicsBody.Enabled = " + value + " (" + isPhysEnabled + ")", e);
|
|
if (UserData != null) DebugConsole.NewMessage("PhysicsBody UserData: " + UserData.GetType().ToString(), Color.Red);
|
|
if (GameMain.World.ContactManager == null) DebugConsole.NewMessage("ContactManager is null!", Color.Red);
|
|
else if (GameMain.World.ContactManager.BroadPhase == null) DebugConsole.NewMessage("Broadphase is null!", Color.Red);
|
|
if (body.FixtureList == null) DebugConsole.NewMessage("FixtureList is null!", Color.Red);
|
|
|
|
if (UserData is Entity entity)
|
|
{
|
|
DebugConsole.NewMessage("Entity \"" + entity.ToString() + "\" removed!", Color.Red);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool PhysEnabled
|
|
{
|
|
get { return body.Enabled; }
|
|
set { isPhysEnabled = value; if (Enabled) body.Enabled = value; }
|
|
}
|
|
|
|
public Vector2 SimPosition
|
|
{
|
|
get { return body.Position; }
|
|
}
|
|
|
|
public Vector2 Position
|
|
{
|
|
get { return ConvertUnits.ToDisplayUnits(body.Position); }
|
|
}
|
|
|
|
public Vector2 PrevPosition
|
|
{
|
|
get { return prevPosition; }
|
|
}
|
|
|
|
public float Rotation
|
|
{
|
|
get { return body.Rotation; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Takes flipping (Dir) into account.
|
|
/// </summary>
|
|
public float TransformedRotation => Dir < 0 ? Rotation - MathHelper.Pi : Rotation;
|
|
|
|
public Vector2 LinearVelocity
|
|
{
|
|
get { return body.LinearVelocity; }
|
|
set
|
|
{
|
|
if (!IsValidValue(value, "velocity", -1000.0f, 1000.0f)) return;
|
|
body.LinearVelocity = value;
|
|
}
|
|
}
|
|
|
|
public float AngularVelocity
|
|
{
|
|
get { return body.AngularVelocity; }
|
|
set
|
|
{
|
|
if (!IsValidValue(value, "angular velocity", -1000f, 1000f)) return;
|
|
body.AngularVelocity = value;
|
|
}
|
|
}
|
|
|
|
public float Mass
|
|
{
|
|
get { return body.Mass; }
|
|
}
|
|
|
|
public float Density
|
|
{
|
|
get { return density; }
|
|
}
|
|
|
|
public Body FarseerBody
|
|
{
|
|
get { return body; }
|
|
}
|
|
|
|
public object UserData
|
|
{
|
|
get { return body.UserData; }
|
|
set { body.UserData = value; }
|
|
}
|
|
|
|
public float Friction
|
|
{
|
|
set { body.Friction = value; }
|
|
}
|
|
|
|
public BodyType BodyType
|
|
{
|
|
get { return body.BodyType; }
|
|
set { body.BodyType = value; }
|
|
}
|
|
|
|
public Category CollisionCategories
|
|
{
|
|
set { body.CollisionCategories = value; }
|
|
}
|
|
|
|
public Category CollidesWith
|
|
{
|
|
set { body.CollidesWith = value; }
|
|
}
|
|
|
|
public PhysicsBody(XElement element, float scale = 1.0f) : this(element, Vector2.Zero, scale) { }
|
|
public PhysicsBody(ColliderParams cParams) : this(cParams, Vector2.Zero) { }
|
|
public PhysicsBody(LimbParams lParams) : this(lParams, Vector2.Zero) { }
|
|
|
|
public PhysicsBody(float width, float height, float radius, float density)
|
|
{
|
|
CreateBody(width, height, radius, density);
|
|
LastSentPosition = body.Position;
|
|
list.Add(this);
|
|
}
|
|
|
|
public PhysicsBody(Body farseerBody)
|
|
{
|
|
body = farseerBody;
|
|
if (body.UserData == null) body.UserData = this;
|
|
LastSentPosition = body.Position;
|
|
list.Add(this);
|
|
}
|
|
|
|
public PhysicsBody(ColliderParams colliderParams, Vector2 position)
|
|
{
|
|
float radius = ConvertUnits.ToSimUnits(colliderParams.Radius) * colliderParams.Ragdoll.LimbScale;
|
|
float height = ConvertUnits.ToSimUnits(colliderParams.Height) * colliderParams.Ragdoll.LimbScale;
|
|
float width = ConvertUnits.ToSimUnits(colliderParams.Width) * colliderParams.Ragdoll.LimbScale;
|
|
density = 10;
|
|
CreateBody(width, height, radius, density);
|
|
body.BodyType = BodyType.Dynamic;
|
|
body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
|
|
body.CollisionCategories = Physics.CollisionCharacter;
|
|
body.AngularDamping = 5.0f;
|
|
body.FixedRotation = true;
|
|
body.Friction = 0.05f;
|
|
body.Restitution = 0.05f;
|
|
SetTransformIgnoreContacts(position, 0.0f);
|
|
LastSentPosition = position;
|
|
list.Add(this);
|
|
}
|
|
|
|
public PhysicsBody(LimbParams limbParams, Vector2 position)
|
|
{
|
|
float radius = ConvertUnits.ToSimUnits(limbParams.Radius) * limbParams.Ragdoll.LimbScale;
|
|
float height = ConvertUnits.ToSimUnits(limbParams.Height) * limbParams.Ragdoll.LimbScale;
|
|
float width = ConvertUnits.ToSimUnits(limbParams.Width) * limbParams.Ragdoll.LimbScale;
|
|
density = limbParams.Density;
|
|
CreateBody(width, height, radius, density);
|
|
body.BodyType = BodyType.Dynamic;
|
|
body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
|
|
body.CollisionCategories = Physics.CollisionItem;
|
|
body.Friction = limbParams.Friction;
|
|
body.Restitution = limbParams.Restitution;
|
|
body.UserData = this;
|
|
SetTransformIgnoreContacts(position, 0.0f);
|
|
LastSentPosition = position;
|
|
list.Add(this);
|
|
}
|
|
|
|
public PhysicsBody(XElement element, Vector2 position, float scale=1.0f)
|
|
{
|
|
float radius = ConvertUnits.ToSimUnits(element.GetAttributeFloat("radius", 0.0f)) * scale;
|
|
float height = ConvertUnits.ToSimUnits(element.GetAttributeFloat("height", 0.0f)) * scale;
|
|
float width = ConvertUnits.ToSimUnits(element.GetAttributeFloat("width", 0.0f)) * scale;
|
|
density = element.GetAttributeFloat("density", 10.0f);
|
|
CreateBody(width, height, radius, density);
|
|
//Enum.TryParse(element.GetAttributeString("bodytype", "Dynamic"), out BodyType bodyType);
|
|
body.BodyType = BodyType.Dynamic;
|
|
body.CollisionCategories = Physics.CollisionItem;
|
|
body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
|
|
body.Friction = element.GetAttributeFloat("friction", 0.3f);
|
|
body.Restitution = element.GetAttributeFloat("restitution", 0.05f);
|
|
body.UserData = this;
|
|
SetTransformIgnoreContacts(position, 0.0f);
|
|
LastSentPosition = position;
|
|
list.Add(this);
|
|
}
|
|
|
|
private void CreateBody(float width, float height, float radius, float density)
|
|
{
|
|
if (IsValidShape(radius, height, width))
|
|
{
|
|
bodyShape = DefineBodyShape(radius, width, height);
|
|
switch (bodyShape)
|
|
{
|
|
case Shape.Capsule:
|
|
body = BodyFactory.CreateCapsule(GameMain.World, height, radius, density);
|
|
break;
|
|
case Shape.HorizontalCapsule:
|
|
body = BodyFactory.CreateCapsuleHorizontal(GameMain.World, width, radius, density);
|
|
break;
|
|
case Shape.Circle:
|
|
body = BodyFactory.CreateCircle(GameMain.World, radius, density);
|
|
break;
|
|
case Shape.Rectangle:
|
|
body = BodyFactory.CreateRectangle(GameMain.World, width, height, density);
|
|
break;
|
|
default:
|
|
throw new NotImplementedException(bodyShape.ToString());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DebugConsole.ThrowError("Invalid physics body dimensions (width: " + width + ", height: " + height + ", radius: " + radius + ")");
|
|
}
|
|
this.width = width;
|
|
this.height = height;
|
|
this.radius = radius;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the farthest point towards the forward of the body.
|
|
/// For capsules and circles, the front is at the top.
|
|
/// For horizontal capsules, the front is at the right-most point.
|
|
/// For rectangles, the front is either at the top or at the right, depending on which one of the two is greater: width or height.
|
|
/// The rotation is in radians.
|
|
/// </summary>
|
|
public Vector2 GetLocalFront(float spritesheetRotation = 0)
|
|
{
|
|
Vector2 pos;
|
|
switch (bodyShape)
|
|
{
|
|
case Shape.Capsule:
|
|
pos = new Vector2(0.0f, height / 2 + radius);
|
|
break;
|
|
case Shape.HorizontalCapsule:
|
|
pos = new Vector2(width / 2 + radius, 0.0f);
|
|
break;
|
|
case Shape.Circle:
|
|
pos = new Vector2(0.0f, radius);
|
|
break;
|
|
case Shape.Rectangle:
|
|
pos = new Vector2(0.0f, Math.Max(height, width) / 2.0f);
|
|
break;
|
|
default:
|
|
throw new NotImplementedException();
|
|
}
|
|
return spritesheetRotation == 0 ? pos : Vector2.Transform(pos, Matrix.CreateRotationZ(spritesheetRotation));
|
|
}
|
|
|
|
public float GetMaxExtent()
|
|
{
|
|
switch (bodyShape)
|
|
{
|
|
case Shape.Capsule:
|
|
return height / 2 + radius;
|
|
case Shape.HorizontalCapsule:
|
|
return width / 2 + radius;
|
|
case Shape.Circle:
|
|
return radius;
|
|
case Shape.Rectangle:
|
|
return new Vector2(width * 0.5f, height * 0.5f).Length();
|
|
default:
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
public Vector2 GetSize()
|
|
{
|
|
switch (bodyShape)
|
|
{
|
|
case Shape.Capsule:
|
|
return new Vector2(radius * 2, height + radius * 2);
|
|
case Shape.HorizontalCapsule:
|
|
return new Vector2(width + radius * 2, radius * 2);
|
|
case Shape.Circle:
|
|
return new Vector2(radius * 2);
|
|
case Shape.Rectangle:
|
|
return new Vector2(width, height);
|
|
default:
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
public void SetSize(Vector2 size)
|
|
{
|
|
switch (bodyShape)
|
|
{
|
|
case Shape.Capsule:
|
|
radius = Math.Max(size.X / 2, 0);
|
|
height = Math.Max(size.Y - size.X, 0);
|
|
width = 0;
|
|
break;
|
|
case Shape.HorizontalCapsule:
|
|
radius = Math.Max(size.Y / 2, 0);
|
|
width = Math.Max(size.X - size.Y, 0);
|
|
height = 0;
|
|
break;
|
|
case Shape.Circle:
|
|
radius = Math.Max(Math.Min(size.X, size.Y) / 2, 0);
|
|
width = 0;
|
|
height = 0;
|
|
break;
|
|
case Shape.Rectangle:
|
|
width = Math.Max(size.X, 0);
|
|
height = Math.Max(size.Y, 0);
|
|
radius = 0;
|
|
break;
|
|
default:
|
|
throw new NotImplementedException();
|
|
}
|
|
#if CLIENT
|
|
bodyShapeTexture = null;
|
|
#endif
|
|
}
|
|
|
|
public bool IsValidValue(float value, string valueName, float? minValue = null, float? maxValue = null)
|
|
{
|
|
if (!MathUtils.IsValid(value) ||
|
|
(minValue.HasValue && value < minValue.Value) ||
|
|
(maxValue.HasValue && value > maxValue.Value))
|
|
{
|
|
string userData = UserData == null ? "null" : UserData.ToString();
|
|
string errorMsg =
|
|
"Attempted to apply invalid " + valueName +
|
|
" to a physics body (userdata: " + userData +
|
|
"), value: " + value;
|
|
if (GameMain.NetworkMember != null)
|
|
{
|
|
errorMsg += GameMain.NetworkMember.IsClient ? " Playing as a client." : " Hosting a server.";
|
|
}
|
|
errorMsg += "\n" + Environment.StackTrace;
|
|
|
|
if (GameSettings.VerboseLogging) DebugConsole.ThrowError(errorMsg);
|
|
GameAnalyticsManager.AddErrorEventOnce(
|
|
"PhysicsBody.SetPosition:InvalidPosition" + userData,
|
|
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
|
errorMsg);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private bool IsValidValue(Vector2 value, string valueName, float? minValue = null, float? maxValue = null)
|
|
{
|
|
if (!MathUtils.IsValid(value) ||
|
|
(minValue.HasValue && (value.X < minValue.Value || value.Y < minValue.Value)) ||
|
|
(maxValue.HasValue && (value.X > maxValue.Value || value.Y > maxValue)))
|
|
{
|
|
string userData = UserData == null ? "null" : UserData.ToString();
|
|
string errorMsg =
|
|
"Attempted to apply invalid " + valueName +
|
|
" to a physics body (userdata: " + userData +
|
|
"), value: " + value;
|
|
if (GameMain.NetworkMember != null)
|
|
{
|
|
errorMsg += GameMain.NetworkMember.IsClient ? " Playing as a client." : " Hosting a server.";
|
|
}
|
|
errorMsg += "\n" + Environment.StackTrace;
|
|
|
|
if (GameSettings.VerboseLogging) DebugConsole.ThrowError(errorMsg);
|
|
GameAnalyticsManager.AddErrorEventOnce(
|
|
"PhysicsBody.SetPosition:InvalidPosition" + userData,
|
|
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
|
errorMsg);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public void ResetDynamics()
|
|
{
|
|
body.ResetDynamics();
|
|
}
|
|
|
|
public void ApplyLinearImpulse(Vector2 impulse)
|
|
{
|
|
if (!IsValidValue(impulse / body.Mass, "new velocity", -1000f, 1000f)) return;
|
|
if (!IsValidValue(impulse, "impulse", -1e10f, 1e10f)) return;
|
|
|
|
body.ApplyLinearImpulse(impulse);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply an impulse to the body without increasing it's velocity above a specific limit.
|
|
/// </summary>
|
|
public void ApplyLinearImpulse(Vector2 impulse, float maxVelocity)
|
|
{
|
|
if (!IsValidValue(impulse / body.Mass, "new velocity")) return;
|
|
if (!IsValidValue(impulse, "impulse", -1e10f, 1e10f)) return;
|
|
if (!IsValidValue(maxVelocity, "max velocity")) return;
|
|
|
|
float currSpeed = body.LinearVelocity.Length();
|
|
Vector2 velocityAddition = impulse / Mass;
|
|
Vector2 newVelocity = body.LinearVelocity + velocityAddition;
|
|
newVelocity = newVelocity.ClampLength(Math.Max(currSpeed, maxVelocity));
|
|
|
|
body.ApplyLinearImpulse((newVelocity - body.LinearVelocity) * Mass);
|
|
}
|
|
|
|
public void ApplyLinearImpulse(Vector2 impulse, Vector2 point)
|
|
{
|
|
if (!IsValidValue(impulse, "impulse", -1e10f, 1e10f)) return;
|
|
if (!IsValidValue(point, "point")) return;
|
|
if (!IsValidValue(impulse / body.Mass, "new velocity")) return;
|
|
body.ApplyLinearImpulse(impulse, point);
|
|
}
|
|
|
|
public void ApplyForce(Vector2 force)
|
|
{
|
|
if (!IsValidValue(force, "force", -1e10f, 1e10f)) return;
|
|
body.ApplyForce(force);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply an impulse to the body without increasing it's velocity above a specific limit.
|
|
/// </summary>
|
|
public void ApplyForce(Vector2 force, float maxVelocity)
|
|
{
|
|
if (!IsValidValue(force, "force", -1e10f, 1e10f)) return;
|
|
if (!IsValidValue(maxVelocity, "max velocity")) return;
|
|
|
|
float currSpeed = body.LinearVelocity.Length();
|
|
Vector2 velocityAddition = force / Mass * (float)Timing.Step;
|
|
Vector2 newVelocity = body.LinearVelocity + velocityAddition;
|
|
newVelocity = newVelocity.ClampLength(Math.Max(currSpeed, maxVelocity));
|
|
|
|
body.ApplyForce((newVelocity - body.LinearVelocity) * Mass / (float)Timing.Step);
|
|
}
|
|
|
|
public void ApplyForce(Vector2 force, Vector2 point)
|
|
{
|
|
if (!IsValidValue(force, "force", -1e10f, 1e10f)) return;
|
|
if (!IsValidValue(point, "point")) return;
|
|
body.ApplyForce(force, point);
|
|
}
|
|
|
|
public void ApplyTorque(float torque)
|
|
{
|
|
if (!IsValidValue(torque, "torque")) return;
|
|
body.ApplyTorque(torque);
|
|
}
|
|
|
|
public bool SetTransform(Vector2 simPosition, float rotation)
|
|
{
|
|
System.Diagnostics.Debug.Assert(MathUtils.IsValid(simPosition));
|
|
System.Diagnostics.Debug.Assert(Math.Abs(simPosition.X) < 1000000.0f);
|
|
System.Diagnostics.Debug.Assert(Math.Abs(simPosition.Y) < 1000000.0f);
|
|
|
|
if (!IsValidValue(simPosition, "position", -1e10f, 1e10f)) return false;
|
|
if (!IsValidValue(rotation, "rotation")) return false;
|
|
|
|
body.SetTransform(simPosition, rotation);
|
|
SetPrevTransform(simPosition, rotation);
|
|
return true;
|
|
}
|
|
|
|
public bool SetTransformIgnoreContacts(Vector2 simPosition, float rotation)
|
|
{
|
|
System.Diagnostics.Debug.Assert(MathUtils.IsValid(simPosition));
|
|
System.Diagnostics.Debug.Assert(Math.Abs(simPosition.X) < 1000000.0f);
|
|
System.Diagnostics.Debug.Assert(Math.Abs(simPosition.Y) < 1000000.0f);
|
|
|
|
if (!IsValidValue(simPosition, "position", -1e10f, 1e10f)) return false;
|
|
if (!IsValidValue(rotation, "rotation")) return false;
|
|
|
|
body.SetTransformIgnoreContacts(ref simPosition, rotation);
|
|
SetPrevTransform(simPosition, rotation);
|
|
return true;
|
|
}
|
|
|
|
public void SetPrevTransform(Vector2 simPosition, float rotation)
|
|
{
|
|
if (!IsValidValue(simPosition, "position", -1e10f, 1e10f)) return;
|
|
if (!IsValidValue(rotation, "rotation")) return;
|
|
|
|
prevPosition = simPosition;
|
|
prevRotation = rotation;
|
|
}
|
|
|
|
public void MoveToTargetPosition(bool lerp = true)
|
|
{
|
|
if (targetPosition == null) { return; }
|
|
|
|
if (lerp)
|
|
{
|
|
if (Vector2.DistanceSquared((Vector2)targetPosition, body.Position) < 10.0f * 10.0f)
|
|
{
|
|
drawOffset = -((Vector2)targetPosition - (body.Position + drawOffset));
|
|
prevPosition = (Vector2)targetPosition;
|
|
}
|
|
else
|
|
{
|
|
drawOffset = Vector2.Zero;
|
|
}
|
|
if (targetRotation.HasValue)
|
|
{
|
|
rotationOffset = -MathUtils.GetShortestAngle(body.Rotation + rotationOffset, targetRotation.Value);
|
|
}
|
|
}
|
|
|
|
SetTransform((Vector2)targetPosition, targetRotation == null ? body.Rotation : (float)targetRotation);
|
|
targetPosition = null;
|
|
targetRotation = null;
|
|
}
|
|
|
|
public void MoveToPos(Vector2 simPosition, float force, Vector2? pullPos = null)
|
|
{
|
|
if (pullPos == null) pullPos = body.Position;
|
|
|
|
if (!IsValidValue(simPosition, "position", -1e10f, 1e10f)) return;
|
|
if (!IsValidValue(force, "force")) return;
|
|
|
|
Vector2 vel = body.LinearVelocity;
|
|
Vector2 deltaPos = simPosition - (Vector2)pullPos;
|
|
deltaPos *= force;
|
|
body.ApplyLinearImpulse((deltaPos - vel * 0.5f) * body.Mass, (Vector2)pullPos);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies buoyancy, drag and angular drag caused by water
|
|
/// </summary>
|
|
public void ApplyWaterForces()
|
|
{
|
|
//buoyancy
|
|
Vector2 buoyancy = new Vector2(0, Mass * 9.6f);
|
|
|
|
Vector2 dragForce = Vector2.Zero;
|
|
|
|
if (LinearVelocity.LengthSquared() > 0.00001f)
|
|
{
|
|
//drag
|
|
Vector2 velDir = Vector2.Normalize(LinearVelocity);
|
|
|
|
float vel = LinearVelocity.Length() * 2.0f;
|
|
float drag = vel * vel * Math.Max(height + radius * 2, height);
|
|
dragForce = Math.Min(drag, Mass * 500.0f) * -velDir;
|
|
}
|
|
|
|
ApplyForce(dragForce + buoyancy);
|
|
ApplyTorque(body.AngularVelocity * body.Mass * -0.08f);
|
|
}
|
|
|
|
public void Update(float deltaTime)
|
|
{
|
|
if (drawOffset.LengthSquared() < 0.01f)
|
|
{
|
|
PositionSmoothingFactor = null;
|
|
}
|
|
drawOffset = NetConfig.InterpolateSimPositionError(drawOffset, PositionSmoothingFactor);
|
|
rotationOffset = NetConfig.InterpolateRotationError(rotationOffset);
|
|
}
|
|
|
|
public void UpdateDrawPosition()
|
|
{
|
|
drawPosition = Timing.Interpolate(prevPosition, body.Position);
|
|
drawPosition = ConvertUnits.ToDisplayUnits(drawPosition + drawOffset);
|
|
drawRotation = Timing.InterpolateRotation(prevRotation, body.Rotation) + rotationOffset;
|
|
}
|
|
|
|
public void CorrectPosition<T>(List<T> positionBuffer,
|
|
out Vector2 newPosition, out Vector2 newVelocity, out float newRotation, out float newAngularVelocity) where T : PosInfo
|
|
{
|
|
newVelocity = LinearVelocity;
|
|
newPosition = SimPosition;
|
|
newRotation = Rotation;
|
|
newAngularVelocity = AngularVelocity;
|
|
|
|
while (positionBuffer.Count > 0 && positionBuffer[0].Timestamp < lastProcessedNetworkState)
|
|
{
|
|
positionBuffer.RemoveAt(0);
|
|
}
|
|
|
|
if (positionBuffer.Count == 0) { return; }
|
|
|
|
lastProcessedNetworkState = positionBuffer[0].Timestamp;
|
|
|
|
newVelocity = positionBuffer[0].LinearVelocity;
|
|
newPosition = positionBuffer[0].Position;
|
|
newRotation = positionBuffer[0].Rotation ?? Rotation;
|
|
newAngularVelocity = positionBuffer[0].AngularVelocity ?? AngularVelocity;
|
|
|
|
positionBuffer.RemoveAt(0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rotate the body towards the target rotation in the "shortest direction", taking into account the current angular velocity to prevent overshooting.
|
|
/// </summary>
|
|
/// <param name="targetRotation">Desired rotation in radians</param>
|
|
/// <param name="force">How fast the body should be rotated. Does not represent any real unit, you may want to experiment with different values to get the desired effect.</param>
|
|
/// <param name="wrapAngle">Should the angles be wrapped. Set to false if it makes a difference whether the angle of the body is 0.0f or 360.0f.</param>
|
|
public void SmoothRotate(float targetRotation, float force = 10.0f, bool wrapAngle = true)
|
|
{
|
|
float nextAngle = body.Rotation + body.AngularVelocity * (float)Timing.Step;
|
|
float angle = wrapAngle ?
|
|
MathUtils.GetShortestAngle(nextAngle, targetRotation) :
|
|
MathHelper.Clamp(targetRotation - nextAngle, -MathHelper.Pi, MathHelper.Pi);
|
|
float torque = angle * 60.0f * (force / 100.0f);
|
|
|
|
if (body.IsKinematic)
|
|
{
|
|
if (!IsValidValue(torque, "torque")) return;
|
|
body.AngularVelocity = torque;
|
|
}
|
|
else
|
|
{
|
|
ApplyTorque(body.Mass * torque);
|
|
}
|
|
}
|
|
|
|
public void Remove()
|
|
{
|
|
list.Remove(this);
|
|
GameMain.World.RemoveBody(body);
|
|
|
|
DisposeProjSpecific();
|
|
}
|
|
|
|
public static void RemoveAll()
|
|
{
|
|
for (int i = list.Count - 1; i >= 0; i--)
|
|
{
|
|
list[i].Remove();
|
|
}
|
|
System.Diagnostics.Debug.Assert(list.Count == 0);
|
|
}
|
|
|
|
public static bool IsValidShape(float radius, float height, float width) => radius > 0 || (height > 0 && width > 0);
|
|
|
|
public static Shape DefineBodyShape(float radius, float width, float height)
|
|
{
|
|
Shape bodyShape;
|
|
if (width <= 0 && height <= 0 && radius > 0)
|
|
{
|
|
bodyShape = Shape.Circle;
|
|
}
|
|
else if (radius > 0)
|
|
{
|
|
if (width > height)
|
|
{
|
|
bodyShape = Shape.HorizontalCapsule;
|
|
}
|
|
else
|
|
{
|
|
bodyShape = Shape.Capsule;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bodyShape = Shape.Rectangle;
|
|
}
|
|
return bodyShape;
|
|
}
|
|
|
|
partial void DisposeProjSpecific();
|
|
|
|
}
|
|
}
|