using Barotrauma.Networking;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
namespace Barotrauma
{
partial class PhysicsBody
{
///
/// Last known state the server has told us about.
///
public PosInfo LastServerState;
///
/// An offset used to corrections to positional errors look smoother. When a large positional correction needs to be done in multiplayer,
/// the body is immediately moved to the correct position, but the draw position is interpolated to make the correction visually smoother.
/// This value means the offset from the "actual" corrected position of the body to the "fake", interpolated draw position.
///
public Vector2 NetworkPositionErrorOffset => drawOffset;
public void Draw(DeformableSprite deformSprite, Camera cam, Vector2 scale, Color color, bool invert = false)
{
if (!Enabled) { return; }
UpdateDrawPosition();
deformSprite?.Draw(cam,
new Vector3(DrawPosition, MathHelper.Clamp(deformSprite.Sprite.Depth, 0, 1)),
deformSprite.Origin,
-DrawRotation,
scale, color, Dir < 0, invert);
}
public void Draw(SpriteBatch spriteBatch, Sprite sprite, Color color, float? depth = null, float scale = 1.0f, bool mirrorX = false, bool mirrorY = false, Vector2? origin = null)
{
if (!Enabled) { return; }
UpdateDrawPosition();
if (sprite == null) { return; }
SpriteEffects spriteEffect = (Dir == 1.0f) ? SpriteEffects.None : SpriteEffects.FlipHorizontally;
if (mirrorX)
{
spriteEffect = spriteEffect == SpriteEffects.None ? SpriteEffects.FlipHorizontally : SpriteEffects.None;
}
if (mirrorY)
{
spriteEffect |= SpriteEffects.FlipVertically;
}
sprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y), color, origin ?? sprite.Origin, - drawRotation, scale, spriteEffect, depth);
}
public void DebugDraw(SpriteBatch spriteBatch, Color color, bool forceColor = false)
{
if (!forceColor)
{
if (!FarseerBody.Enabled)
{
color = Color.Black;
}
else if (!FarseerBody.Awake)
{
color = Color.Blue;
}
}
if (targetPosition != null)
{
Vector2 pos = ConvertUnits.ToDisplayUnits((Vector2)targetPosition);
if (Submarine != null) pos += Submarine.DrawPosition;
GUI.DrawRectangle(spriteBatch,
new Vector2(pos.X - 5, -(pos.Y + 5)),
Vector2.One * 10.0f, GUIStyle.Red, false, 0, 3);
}
if (drawOffset != Vector2.Zero)
{
Vector2 pos = ConvertUnits.ToDisplayUnits(FarseerBody.Position);
if (Submarine != null) { pos += Submarine.DrawPosition; }
GUI.DrawLine(spriteBatch,
new Vector2(pos.X, -pos.Y),
new Vector2(DrawPosition.X, -DrawPosition.Y),
Color.Purple * 0.5f, 0, 5);
}
if (IsValidShape(Radius, Height, Width))
{
DrawShape(DrawPosition, DrawRotation, color);
}
if (LastServerState != null)
{
Vector2 drawPos = ConvertUnits.ToDisplayUnits(LastServerState.Position);
if (Submarine != null)
{
drawPos += Submarine.DrawPosition;
}
float rotation = LastServerState.Rotation ?? 0.0f;
DrawShape(drawPos, rotation, Color.Purple * 0.75f);
}
void DrawShape(Vector2 position, float rotation, Color color)
{
float radius = ConvertUnits.ToDisplayUnits(Radius);
float height = ConvertUnits.ToDisplayUnits(Height);
float width = ConvertUnits.ToDisplayUnits(Width);
switch (BodyShape)
{
case Shape.Rectangle:
GUI.DrawRectangle(spriteBatch, position.FlipY(), new Vector2(width, height), new Vector2(width, height) / 2, -rotation, color);
break;
case Shape.Capsule:
GUI.DrawCapsule(spriteBatch, position.FlipY(), height, radius, -rotation - MathHelper.PiOver2, color);
break;
case Shape.HorizontalCapsule:
GUI.DrawCapsule(spriteBatch, position.FlipY(), width, radius, -rotation, color);
break;
case Shape.Circle:
GUI.DrawDonutSection(spriteBatch, position.FlipY(), new Range(radius - 0.5f, radius + 0.5f), MathHelper.TwoPi, color, 0, -rotation);
break;
default:
throw new NotImplementedException();
}
}
}
public PosInfo ClientRead(IReadMessage msg, float sendingTime, string parentDebugName)
{
float MaxVel = NetConfig.MaxPhysicsBodyVelocity;
float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity;
Vector2 newPosition = SimPosition;
float? newRotation = null;
bool awake = FarseerBody.Awake;
Vector2 newVelocity = LinearVelocity;
float? newAngularVelocity = null;
newPosition = new Vector2(
msg.ReadSingle(),
msg.ReadSingle());
awake = msg.ReadBoolean();
bool fixedRotation = msg.ReadBoolean();
if (!fixedRotation)
{
newRotation = msg.ReadRangedSingle(0.0f, MathHelper.TwoPi, 8);
}
if (awake)
{
newVelocity = new Vector2(
msg.ReadRangedSingle(-MaxVel, MaxVel, 12),
msg.ReadRangedSingle(-MaxVel, MaxVel, 12));
newVelocity = NetConfig.Quantize(newVelocity, -MaxVel, MaxVel, 12);
if (!fixedRotation)
{
newAngularVelocity = msg.ReadRangedSingle(-MaxAngularVel, MaxAngularVel, 8);
newAngularVelocity = NetConfig.Quantize(newAngularVelocity.Value, -MaxAngularVel, MaxAngularVel, 8);
}
}
msg.ReadPadBits();
if (!MathUtils.IsValid(newPosition) ||
!MathUtils.IsValid(newVelocity) ||
(newRotation.HasValue && !MathUtils.IsValid(newRotation.Value)) ||
(newAngularVelocity.HasValue && !MathUtils.IsValid(newAngularVelocity.Value)))
{
string errorMsg = "Received invalid position data for \"" + parentDebugName
+ "\" (position: " + newPosition + ", rotation: " + (newRotation ?? 0) + ", velocity: " + newVelocity + ", angular velocity: " + (newAngularVelocity ?? 0) + ")";
#if DEBUG
DebugConsole.ThrowError(errorMsg);
#endif
GameAnalyticsManager.AddErrorEventOnce("PhysicsBody.ClientRead:InvalidData" + parentDebugName,
GameAnalyticsManager.ErrorSeverity.Error,
errorMsg);
return null;
}
if (lastProcessedNetworkState > sendingTime) { return null; }
LastServerState = new PosInfo(newPosition, newRotation, newVelocity, newAngularVelocity, sendingTime);
return LastServerState;
}
}
}