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; } } }