Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs
2019-03-27 20:52:47 +02:00

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