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.
517 lines
24 KiB
C#
517 lines
24 KiB
C#
using Barotrauma.Items.Components;
|
|
using Barotrauma.SpriteDeformations;
|
|
using Barotrauma.Extensions;
|
|
using FarseerPhysics;
|
|
using FarseerPhysics.Dynamics;
|
|
using FarseerPhysics.Dynamics.Joints;
|
|
using Microsoft.Xna.Framework;
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
using System;
|
|
using System.Linq;
|
|
using System.Collections.Generic;
|
|
using Barotrauma.Particles;
|
|
|
|
namespace Barotrauma
|
|
{
|
|
abstract partial class Ragdoll
|
|
{
|
|
public HashSet<SpriteDeformation> SpriteDeformations { get; protected set; } = new HashSet<SpriteDeformation>();
|
|
|
|
/// <summary>
|
|
/// Inversed draw order, which is used for drawing the limbs in 3d (deformable sprites).
|
|
/// </summary>
|
|
protected Limb[] inversedLimbDrawOrder;
|
|
|
|
partial void UpdateNetPlayerPositionProjSpecific(float deltaTime, float lowestSubPos)
|
|
{
|
|
if (character != GameMain.Client.Character || !character.AllowInput)
|
|
{
|
|
//remove states without a timestamp (there may still be ID-based states
|
|
//in the list when the controlled character switches to timestamp-based interpolation)
|
|
character.MemState.RemoveAll(m => m.Timestamp == 0.0f);
|
|
|
|
//use simple interpolation for other players' characters and characters that can't move
|
|
if (character.MemState.Count > 0)
|
|
{
|
|
CharacterStateInfo serverPos = character.MemState.Last();
|
|
if (!character.isSynced)
|
|
{
|
|
SetPosition(serverPos.Position, false);
|
|
Collider.LinearVelocity = Vector2.Zero;
|
|
character.MemLocalState.Clear();
|
|
character.LastNetworkUpdateID = serverPos.ID;
|
|
character.isSynced = true;
|
|
return;
|
|
}
|
|
|
|
if (character.MemState[0].Interact == null || character.MemState[0].Interact.Removed)
|
|
{
|
|
character.DeselectCharacter();
|
|
character.SelectedConstruction = null;
|
|
}
|
|
else if (character.MemState[0].Interact is Character)
|
|
{
|
|
character.SelectCharacter((Character)character.MemState[0].Interact);
|
|
}
|
|
else if (character.MemState[0].Interact is Item newSelectedConstruction)
|
|
{
|
|
if (newSelectedConstruction != null && character.SelectedConstruction != newSelectedConstruction)
|
|
{
|
|
foreach (var ic in newSelectedConstruction.Components)
|
|
{
|
|
if (ic.CanBeSelected) ic.Select(character);
|
|
}
|
|
}
|
|
character.SelectedConstruction = newSelectedConstruction;
|
|
}
|
|
|
|
if (character.MemState[0].Animation == AnimController.Animation.CPR)
|
|
{
|
|
character.AnimController.Anim = AnimController.Animation.CPR;
|
|
}
|
|
else if (character.AnimController.Anim == AnimController.Animation.CPR)
|
|
{
|
|
character.AnimController.Anim = AnimController.Animation.None;
|
|
}
|
|
|
|
Vector2 newVelocity = Collider.LinearVelocity;
|
|
Vector2 newPosition = Collider.SimPosition;
|
|
float newRotation = Collider.Rotation;
|
|
float newAngularVelocity = Collider.AngularVelocity;
|
|
Collider.CorrectPosition(character.MemState, out newPosition, out newVelocity, out newRotation, out newAngularVelocity);
|
|
|
|
newVelocity = newVelocity.ClampLength(100.0f);
|
|
if (!MathUtils.IsValid(newVelocity)) { newVelocity = Vector2.Zero; }
|
|
overrideTargetMovement = newVelocity.LengthSquared() > 0.01f ? newVelocity : Vector2.Zero;
|
|
|
|
Collider.LinearVelocity = newVelocity;
|
|
Collider.AngularVelocity = newAngularVelocity;
|
|
|
|
float distSqrd = Vector2.DistanceSquared(newPosition, Collider.SimPosition);
|
|
float errorTolerance = character.AllowInput ? 0.01f : 0.1f;
|
|
if (distSqrd > errorTolerance)
|
|
{
|
|
if (distSqrd > 10.0f || !character.AllowInput)
|
|
{
|
|
Collider.TargetRotation = newRotation;
|
|
SetPosition(newPosition, lerp: distSqrd < 1.0f);
|
|
}
|
|
else
|
|
{
|
|
Collider.TargetRotation = newRotation;
|
|
Collider.TargetPosition = newPosition;
|
|
Collider.MoveToTargetPosition(true);
|
|
}
|
|
}
|
|
|
|
//unconscious/dead characters can't correct their position using AnimController movement
|
|
// -> we need to correct it manually
|
|
if (!character.AllowInput)
|
|
{
|
|
MainLimb.PullJointWorldAnchorB = Collider.SimPosition;
|
|
MainLimb.PullJointEnabled = true;
|
|
}
|
|
}
|
|
character.MemLocalState.Clear();
|
|
}
|
|
else
|
|
{
|
|
//remove states with a timestamp (there may still timestamp-based states
|
|
//in the list if the controlled character switches from timestamp-based interpolation to ID-based)
|
|
character.MemState.RemoveAll(m => m.Timestamp > 0.0f);
|
|
|
|
for (int i = 0; i < character.MemLocalState.Count; i++)
|
|
{
|
|
if (character.Submarine == null)
|
|
{
|
|
//transform in-sub coordinates to outside coordinates
|
|
if (character.MemLocalState[i].Position.Y > lowestSubPos)
|
|
{
|
|
character.MemLocalState[i].TransformInToOutside();
|
|
}
|
|
}
|
|
else if (currentHull?.Submarine != null)
|
|
{
|
|
//transform outside coordinates to in-sub coordinates
|
|
if (character.MemLocalState[i].Position.Y < lowestSubPos)
|
|
{
|
|
character.MemLocalState[i].TransformOutToInside(currentHull.Submarine);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (character.MemState.Count < 1) return;
|
|
|
|
overrideTargetMovement = Vector2.Zero;
|
|
|
|
CharacterStateInfo serverPos = character.MemState.Last();
|
|
|
|
if (!character.isSynced)
|
|
{
|
|
SetPosition(serverPos.Position, false);
|
|
Collider.LinearVelocity = Vector2.Zero;
|
|
character.MemLocalState.Clear();
|
|
character.LastNetworkUpdateID = serverPos.ID;
|
|
character.isSynced = true;
|
|
return;
|
|
}
|
|
|
|
int localPosIndex = character.MemLocalState.FindIndex(m => m.ID == serverPos.ID);
|
|
if (localPosIndex > -1)
|
|
{
|
|
CharacterStateInfo localPos = character.MemLocalState[localPosIndex];
|
|
|
|
//the entity we're interacting with doesn't match the server's
|
|
if (localPos.Interact != serverPos.Interact)
|
|
{
|
|
if (serverPos.Interact == null || serverPos.Interact.Removed)
|
|
{
|
|
character.DeselectCharacter();
|
|
character.SelectedConstruction = null;
|
|
}
|
|
else if (serverPos.Interact is Character)
|
|
{
|
|
character.SelectCharacter((Character)serverPos.Interact);
|
|
}
|
|
else
|
|
{
|
|
var newSelectedConstruction = (Item)serverPos.Interact;
|
|
if (newSelectedConstruction != null && character.SelectedConstruction != newSelectedConstruction)
|
|
{
|
|
newSelectedConstruction.TryInteract(character, true, true);
|
|
}
|
|
character.SelectedConstruction = newSelectedConstruction;
|
|
}
|
|
}
|
|
|
|
if (localPos.Animation != serverPos.Animation)
|
|
{
|
|
if (serverPos.Animation == AnimController.Animation.CPR)
|
|
{
|
|
character.AnimController.Anim = AnimController.Animation.CPR;
|
|
}
|
|
else if (character.AnimController.Anim == AnimController.Animation.CPR)
|
|
{
|
|
character.AnimController.Anim = AnimController.Animation.None;
|
|
}
|
|
}
|
|
|
|
Hull serverHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(serverPos.Position), character.CurrentHull, serverPos.Position.Y < lowestSubPos);
|
|
Hull clientHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(localPos.Position), serverHull, localPos.Position.Y < lowestSubPos);
|
|
|
|
if (serverHull != null && clientHull != null && serverHull.Submarine != clientHull.Submarine)
|
|
{
|
|
//hull subs don't match => teleport the camera to the other sub
|
|
character.Submarine = serverHull.Submarine;
|
|
character.CurrentHull = currentHull = serverHull;
|
|
SetPosition(serverPos.Position);
|
|
character.MemLocalState.Clear();
|
|
}
|
|
else
|
|
{
|
|
Vector2 positionError = serverPos.Position - localPos.Position;
|
|
float rotationError = serverPos.Rotation.HasValue && localPos.Rotation.HasValue ?
|
|
serverPos.Rotation.Value - localPos.Rotation.Value :
|
|
0.0f;
|
|
|
|
for (int i = localPosIndex; i < character.MemLocalState.Count; i++)
|
|
{
|
|
Hull pointHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(character.MemLocalState[i].Position), clientHull, character.MemLocalState[i].Position.Y < lowestSubPos);
|
|
if (pointHull != clientHull && ((pointHull == null) || (clientHull == null) || (pointHull.Submarine == clientHull.Submarine))) break;
|
|
character.MemLocalState[i].Translate(positionError, rotationError);
|
|
}
|
|
|
|
float errorMagnitude = positionError.Length();
|
|
if (errorMagnitude > 0.01f)
|
|
{
|
|
Collider.TargetPosition = Collider.SimPosition + positionError;
|
|
Collider.TargetRotation = Collider.Rotation + rotationError;
|
|
Collider.MoveToTargetPosition(lerp: true);
|
|
if (errorMagnitude > 0.5f)
|
|
{
|
|
character.MemLocalState.Clear();
|
|
foreach (Limb limb in Limbs)
|
|
{
|
|
limb.body.TargetPosition = limb.body.SimPosition + positionError;
|
|
limb.body.MoveToTargetPosition(lerp: true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (character.MemLocalState.Count > 120) character.MemLocalState.RemoveRange(0, character.MemLocalState.Count - 120);
|
|
character.MemState.Clear();
|
|
}
|
|
}
|
|
|
|
partial void ImpactProjSpecific(float impact, Body body)
|
|
{
|
|
float volume = Math.Min(impact - 3.0f, 1.0f);
|
|
|
|
if (body.UserData is Limb && character.Stun <= 0f)
|
|
{
|
|
Limb limb = (Limb)body.UserData;
|
|
if (impact > 3.0f && limb.LastImpactSoundTime < Timing.TotalTime - Limb.SoundInterval)
|
|
{
|
|
limb.LastImpactSoundTime = (float)Timing.TotalTime;
|
|
if (!string.IsNullOrWhiteSpace(limb.HitSoundTag))
|
|
{
|
|
SoundPlayer.PlaySound(limb.HitSoundTag, volume, impact * 100.0f, limb.WorldPosition, character.CurrentHull);
|
|
}
|
|
foreach (WearableSprite wearable in limb.WearingItems)
|
|
{
|
|
if (limb.type == wearable.Limb && !string.IsNullOrWhiteSpace(wearable.Sound))
|
|
{
|
|
SoundPlayer.PlaySound(wearable.Sound, volume, impact * 100.0f, limb.WorldPosition, character.CurrentHull);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (body.UserData is Limb || body == Collider.FarseerBody)
|
|
{
|
|
if (!character.IsRemotePlayer)
|
|
{
|
|
if (impact > ImpactTolerance)
|
|
{
|
|
SoundPlayer.PlayDamageSound("LimbBlunt", strongestImpact, Collider);
|
|
}
|
|
}
|
|
}
|
|
if (Character.Controlled == character)
|
|
{
|
|
GameMain.GameScreen.Cam.Shake = Math.Min(Math.Max(strongestImpact, GameMain.GameScreen.Cam.Shake), 3.0f);
|
|
}
|
|
}
|
|
|
|
partial void Splash(Limb limb, Hull limbHull)
|
|
{
|
|
//create a splash particle
|
|
for (int i = 0; i < MathHelper.Clamp(Math.Abs(limb.LinearVelocity.Y), 1.0f, 5.0f); i++)
|
|
{
|
|
var splash = GameMain.ParticleManager.CreateParticle("watersplash",
|
|
new Vector2(limb.WorldPosition.X, limbHull.WorldSurface),
|
|
new Vector2(0.0f, Math.Abs(-limb.LinearVelocity.Y * 20.0f)) + Rand.Vector(Math.Abs(limb.LinearVelocity.Y * 10)),
|
|
Rand.Range(0.0f, MathHelper.TwoPi), limbHull);
|
|
|
|
if (splash != null)
|
|
{
|
|
splash.Size *= MathHelper.Clamp(Math.Abs(limb.LinearVelocity.Y) * 0.1f, 1.0f, 2.0f);
|
|
}
|
|
}
|
|
|
|
GameMain.ParticleManager.CreateParticle("bubbles",
|
|
new Vector2(limb.WorldPosition.X, limbHull.WorldSurface),
|
|
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;
|
|
}
|
|
|
|
//+ some extra bubbles to follow the character underwater
|
|
GameMain.ParticleManager.CreateParticle("bubbles",
|
|
new Vector2(limb.WorldPosition.X, limbHull.WorldSurface),
|
|
limb.LinearVelocity * 10.0f,
|
|
0.0f, limbHull);
|
|
}
|
|
}
|
|
|
|
partial void SetupDrawOrder()
|
|
{
|
|
//make sure every character gets drawn at a distinct "layer"
|
|
//(instead of having some of the limbs appear behind and some in front of other characters)
|
|
float startDepth = 0.1f;
|
|
float increment = 0.001f;
|
|
foreach (Character otherCharacter in Character.CharacterList)
|
|
{
|
|
if (otherCharacter == character) continue;
|
|
startDepth += increment;
|
|
}
|
|
//make sure each limb has a distinct depth value
|
|
List<Limb> depthSortedLimbs = Limbs.OrderBy(l => l.ActiveSprite == null ? 0.0f : l.ActiveSprite.Depth).ToList();
|
|
foreach (Limb limb in Limbs)
|
|
{
|
|
if (limb.ActiveSprite != null)
|
|
limb.ActiveSprite.Depth = startDepth + depthSortedLimbs.IndexOf(limb) * 0.00001f;
|
|
}
|
|
depthSortedLimbs.Reverse();
|
|
inversedLimbDrawOrder = depthSortedLimbs.ToArray();
|
|
}
|
|
|
|
partial void UpdateProjSpecific(float deltaTime)
|
|
{
|
|
LimbJoints.ForEach(j => j.UpdateDeformations(deltaTime));
|
|
foreach (var deformation in SpriteDeformations)
|
|
{
|
|
if (deformation.DeformationParams.UseMovementSine)
|
|
{
|
|
if (this is AnimController animator)
|
|
{
|
|
//deformation.Phase = MathUtils.WrapAngleTwoPi(animator.WalkPos + MathHelper.Pi);
|
|
deformation.Phase = MathUtils.WrapAngleTwoPi(animator.WalkPos * deformation.DeformationParams.Frequency + MathHelper.Pi * deformation.DeformationParams.SineOffset);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
deformation.Update(deltaTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
partial void FlipProjSpecific()
|
|
{
|
|
foreach (Limb limb in Limbs)
|
|
{
|
|
if (limb == null || limb.IsSevered || limb.ActiveSprite == null) continue;
|
|
|
|
Vector2 spriteOrigin = limb.ActiveSprite.Origin;
|
|
spriteOrigin.X = limb.ActiveSprite.SourceRect.Width - spriteOrigin.X;
|
|
limb.ActiveSprite.Origin = spriteOrigin;
|
|
}
|
|
}
|
|
|
|
partial void SeverLimbJointProjSpecific(LimbJoint limbJoint)
|
|
{
|
|
foreach (Limb limb in new Limb[] { limbJoint.LimbA, limbJoint.LimbB })
|
|
{
|
|
float gibParticleAmount = MathHelper.Clamp(limb.Mass / character.AnimController.Mass, 0.1f, 1.0f);
|
|
foreach (ParticleEmitter emitter in character.GibEmitters)
|
|
{
|
|
if (inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Air) continue;
|
|
if (!inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) continue;
|
|
|
|
emitter.Emit(1.0f, limb.WorldPosition, character.CurrentHull, amountMultiplier: gibParticleAmount);
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(character.BloodDecalName))
|
|
{
|
|
character.CurrentHull?.AddDecal(character.BloodDecalName, limb.WorldPosition, MathHelper.Clamp(limb.Mass, 0.5f, 2.0f));
|
|
}
|
|
}
|
|
}
|
|
|
|
public virtual void Draw(SpriteBatch spriteBatch, Camera cam)
|
|
{
|
|
if (simplePhysicsEnabled) return;
|
|
|
|
Collider.UpdateDrawPosition();
|
|
|
|
if (Limbs == null)
|
|
{
|
|
DebugConsole.ThrowError("Failed to draw a ragdoll, limbs have been removed. Character: \"" + character.Name + "\", removed: " + character.Removed + "\n" + Environment.StackTrace);
|
|
GameAnalyticsManager.AddErrorEventOnce("Ragdoll.Draw:LimbsRemoved",
|
|
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
|
"Failed to draw a ragdoll, limbs have been removed. Character: \"" + character.Name + "\", removed: " + character.Removed + "\n" + Environment.StackTrace);
|
|
return;
|
|
}
|
|
|
|
//foreach (Limb limb in Limbs)
|
|
//{
|
|
// limb.Draw(spriteBatch, cam);
|
|
//}
|
|
|
|
for (int i = 0; i < limbs.Length; i++)
|
|
{
|
|
inversedLimbDrawOrder[i].Draw(spriteBatch, cam);
|
|
}
|
|
LimbJoints.ForEach(j => j.Draw(spriteBatch));
|
|
}
|
|
|
|
public void DebugDraw(SpriteBatch spriteBatch)
|
|
{
|
|
if (!GameMain.DebugDraw || !character.Enabled) return;
|
|
if (simplePhysicsEnabled) return;
|
|
|
|
foreach (Limb limb in Limbs)
|
|
{
|
|
if (limb.PullJointEnabled)
|
|
{
|
|
Vector2 pos = ConvertUnits.ToDisplayUnits(limb.PullJointWorldAnchorA);
|
|
if (currentHull?.Submarine != 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));
|
|
GUI.Font.DrawString(spriteBatch, Collider.LinearVelocity.X.FormatSingleDecimal(), 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 != null)
|
|
{
|
|
Vector2 pos = ConvertUnits.ToDisplayUnits((Vector2)limb.body.TargetPosition);
|
|
if (currentHull?.Submarine != 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 (this is HumanoidAnimController humanoid)
|
|
{
|
|
Vector2 pos = ConvertUnits.ToDisplayUnits(humanoid.RightHandIKPos);
|
|
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 4, 4), Color.Green, true);
|
|
pos = ConvertUnits.ToDisplayUnits(humanoid.LeftHandIKPos);
|
|
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 4, 4), Color.Green, true);
|
|
}
|
|
|
|
if (outsideCollisionBlocker.Enabled && currentHull?.Submarine != null)
|
|
{
|
|
var edgeShape = outsideCollisionBlocker.FixtureList[0].Shape as FarseerPhysics.Collision.Shapes.EdgeShape;
|
|
Vector2 startPos = ConvertUnits.ToDisplayUnits(outsideCollisionBlocker.GetWorldPoint(edgeShape.Vertex1)) + currentHull.Submarine.Position;
|
|
Vector2 endPos = ConvertUnits.ToDisplayUnits(outsideCollisionBlocker.GetWorldPoint(edgeShape.Vertex2)) + currentHull.Submarine.Position;
|
|
startPos.Y = -startPos.Y;
|
|
endPos.Y = -endPos.Y;
|
|
GUI.DrawLine(spriteBatch, startPos, endPos, Color.Gray, 0, 5);
|
|
}
|
|
|
|
if (character.MemState.Count > 1)
|
|
{
|
|
Vector2 prevPos = ConvertUnits.ToDisplayUnits(character.MemState[0].Position);
|
|
if (currentHull?.Submarine != null) prevPos += currentHull.Submarine.DrawPosition;
|
|
prevPos.Y = -prevPos.Y;
|
|
|
|
for (int i = 1; i < character.MemState.Count; i++)
|
|
{
|
|
Vector2 currPos = ConvertUnits.ToDisplayUnits(character.MemState[i].Position);
|
|
if (currentHull?.Submarine != 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);
|
|
}
|
|
}
|
|
}
|
|
}
|