38f1ddb...178a853: v0.8.9.1, removed content folder
This commit is contained in:
@@ -1,74 +1,222 @@
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Xml.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AnimController : Ragdoll
|
||||
abstract class AnimController : Ragdoll
|
||||
{
|
||||
public enum Animation { None, Climbing, UsingConstruction, Struggle, CPR };
|
||||
public Animation Anim;
|
||||
public abstract GroundedMovementParams WalkParams { get; set; }
|
||||
public abstract GroundedMovementParams RunParams { get; set; }
|
||||
public abstract SwimParams SwimSlowParams { get; set; }
|
||||
public abstract SwimParams SwimFastParams { get; set; }
|
||||
|
||||
public LimbType GrabLimb;
|
||||
|
||||
protected Character character;
|
||||
|
||||
protected float walkSpeed, swimSpeed;
|
||||
|
||||
protected float walkPos;
|
||||
|
||||
protected readonly Vector2 stepSize;
|
||||
protected readonly float legTorque;
|
||||
|
||||
public float RunSpeedMultiplier
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public float SwimSpeedMultiplier
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Vector2 AimSourcePos
|
||||
{
|
||||
get { return ConvertUnits.ToDisplayUnits(AimSourceSimPos); }
|
||||
}
|
||||
|
||||
public virtual Vector2 AimSourceSimPos
|
||||
public AnimationParams CurrentAnimationParams
|
||||
{
|
||||
get
|
||||
{
|
||||
return Collider.SimPosition;
|
||||
if (ForceSelectAnimationType == AnimationType.NotDefined)
|
||||
{
|
||||
return (InWater || !CanWalk) ? (AnimationParams)CurrentSwimParams : CurrentGroundedParams;
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetAnimationParamsFromType(ForceSelectAnimationType);
|
||||
}
|
||||
}
|
||||
}
|
||||
public AnimationType ForceSelectAnimationType { get; set; }
|
||||
public GroundedMovementParams CurrentGroundedParams
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ForceSelectAnimationType != AnimationType.NotDefined)
|
||||
{
|
||||
return GetAnimationParamsFromType(ForceSelectAnimationType) as GroundedMovementParams;
|
||||
}
|
||||
if (!CanWalk)
|
||||
{
|
||||
DebugConsole.ThrowError($"{character.SpeciesName} cannot walk!");
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return IsMovingFast ? RunParams : WalkParams;
|
||||
}
|
||||
}
|
||||
}
|
||||
public SwimParams CurrentSwimParams
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ForceSelectAnimationType != AnimationType.NotDefined)
|
||||
{
|
||||
return GetAnimationParamsFromType(ForceSelectAnimationType) as SwimParams;
|
||||
}
|
||||
else
|
||||
{
|
||||
return IsMovingFast? SwimFastParams : SwimSlowParams;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AnimController(Character character, XElement element)
|
||||
: base(character, element)
|
||||
public bool CanWalk => CanEnterSubmarine;
|
||||
public bool IsMovingBackwards => !InWater && Math.Sign(targetMovement.X) == -Math.Sign(Dir);
|
||||
|
||||
// TODO: define death anim duration in XML
|
||||
protected float deathAnimTimer, deathAnimDuration = 5.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Note: Presupposes that the slow speed is lower than the high speed. Otherwise will give invalid results.
|
||||
/// </summary>
|
||||
public bool IsMovingFast
|
||||
{
|
||||
this.character = character;
|
||||
|
||||
stepSize = element.GetAttributeVector2("stepsize", Vector2.One);
|
||||
stepSize = ConvertUnits.ToSimUnits(stepSize);
|
||||
|
||||
walkSpeed = element.GetAttributeFloat("walkspeed", 1.0f);
|
||||
swimSpeed = element.GetAttributeFloat("swimspeed", 1.0f);
|
||||
|
||||
RunSpeedMultiplier = element.GetAttributeFloat("runspeedmultiplier", 2f);
|
||||
SwimSpeedMultiplier = element.GetAttributeFloat("swimspeedmultiplier", 1.5f);
|
||||
|
||||
legTorque = element.GetAttributeFloat("legtorque", 0.0f);
|
||||
get
|
||||
{
|
||||
if (InWater || !CanWalk)
|
||||
{
|
||||
return TargetMovement.Length() > (SwimSlowParams.MovementSpeed + SwimFastParams.MovementSpeed) / 2.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Math.Abs(TargetMovement.X) > (WalkParams.MovementSpeed + RunParams.MovementSpeed) / 2.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note: creates a new list every time, because the params might have changed. If there is a need to access the property frequently, change the implementation to an array, where the slot is updated when the param is updated(?)
|
||||
/// Currently it's not simple to implement, since the properties are not implemented here, but in the derived classes. Would require to change the params virtual and to call the base property getter/setter or something.
|
||||
/// </summary>
|
||||
public List<AnimationParams> AllAnimParams
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CanWalk)
|
||||
{
|
||||
return new List<AnimationParams> { WalkParams, RunParams, SwimSlowParams, SwimFastParams };
|
||||
}
|
||||
else
|
||||
{
|
||||
return new List<AnimationParams> { SwimSlowParams, SwimFastParams };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum Animation { None, Climbing, UsingConstruction, Struggle, CPR };
|
||||
public Animation Anim;
|
||||
|
||||
public Vector2 AimSourcePos => ConvertUnits.ToDisplayUnits(AimSourceSimPos);
|
||||
public virtual Vector2 AimSourceSimPos => Collider.SimPosition;
|
||||
|
||||
protected float? GetValidOrNull(AnimationParams p, float? v)
|
||||
{
|
||||
if (p == null) { return null; }
|
||||
if (v == null) { return null; }
|
||||
if (!MathUtils.IsValid(v.Value)) { return null; }
|
||||
return v.Value;
|
||||
}
|
||||
protected Vector2? GetValidOrNull(AnimationParams p, Vector2 v)
|
||||
{
|
||||
if (p == null) { return null; }
|
||||
return v;
|
||||
}
|
||||
|
||||
public override float? HeadPosition => GetValidOrNull(CurrentGroundedParams, CurrentGroundedParams?.HeadPosition * RagdollParams.JointScale);
|
||||
public override float? TorsoPosition => GetValidOrNull(CurrentGroundedParams, CurrentGroundedParams?.TorsoPosition * RagdollParams.JointScale);
|
||||
public override float? HeadAngle => GetValidOrNull(CurrentAnimationParams, CurrentAnimationParams?.HeadAngleInRadians);
|
||||
public override float? TorsoAngle => GetValidOrNull(CurrentAnimationParams, CurrentAnimationParams?.TorsoAngleInRadians);
|
||||
public virtual Vector2? StepSize => GetValidOrNull(CurrentGroundedParams, CurrentGroundedParams.StepSize * RagdollParams.JointScale);
|
||||
|
||||
public bool AnimationTestPose { get; set; }
|
||||
|
||||
public float WalkPos { get; protected set; }
|
||||
|
||||
public AnimController(Character character, string seed, RagdollParams ragdollParams = null) : base(character, seed, ragdollParams) { }
|
||||
|
||||
public virtual void UpdateAnim(float deltaTime) { }
|
||||
|
||||
public virtual void HoldItem(float deltaTime, Item item, Vector2[] handlePos, Vector2 holdPos, Vector2 aimPos, bool aim, float holdAngle) { }
|
||||
public virtual void HoldItem(float deltaTime, Item item, Vector2[] handlePos, Vector2 holdPos, Vector2 aimPos, bool aim, float holdAngle, float itemAngleRelativeToHoldAngle = 0.0f) { }
|
||||
|
||||
public virtual void DragCharacter(Character target) { }
|
||||
public virtual void DragCharacter(Character target, float deltaTime) { }
|
||||
|
||||
public virtual void UpdateUseItem(bool allowMovement, Vector2 handPos) { }
|
||||
public virtual void UpdateUseItem(bool allowMovement, Vector2 handWorldPos) { }
|
||||
|
||||
}
|
||||
public float GetSpeed(AnimationType type)
|
||||
{
|
||||
GroundedMovementParams movementParams;
|
||||
switch (type)
|
||||
{
|
||||
case AnimationType.Walk:
|
||||
if (!CanWalk)
|
||||
{
|
||||
DebugConsole.ThrowError($"{character.SpeciesName} cannot walk!");
|
||||
return 0;
|
||||
}
|
||||
movementParams = WalkParams;
|
||||
break;
|
||||
case AnimationType.Run:
|
||||
if (!CanWalk)
|
||||
{
|
||||
DebugConsole.ThrowError($"{character.SpeciesName} cannot run!");
|
||||
return 0;
|
||||
}
|
||||
movementParams = RunParams;
|
||||
break;
|
||||
case AnimationType.SwimSlow:
|
||||
return SwimSlowParams.MovementSpeed;
|
||||
case AnimationType.SwimFast:
|
||||
return SwimFastParams.MovementSpeed;
|
||||
default:
|
||||
throw new NotImplementedException(type.ToString());
|
||||
}
|
||||
return IsMovingBackwards ? movementParams.MovementSpeed * movementParams.BackwardsMovementMultiplier : movementParams.MovementSpeed;
|
||||
}
|
||||
|
||||
public float GetCurrentSpeed(bool useMaxSpeed)
|
||||
{
|
||||
AnimationType animType;
|
||||
if (InWater || !CanWalk)
|
||||
{
|
||||
if (useMaxSpeed)
|
||||
{
|
||||
animType = AnimationType.SwimFast;
|
||||
}
|
||||
else
|
||||
{
|
||||
animType = AnimationType.SwimSlow;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (useMaxSpeed)
|
||||
{
|
||||
animType = AnimationType.Run;
|
||||
}
|
||||
else
|
||||
{
|
||||
animType = AnimationType.Walk;
|
||||
}
|
||||
}
|
||||
return GetSpeed(animType);
|
||||
}
|
||||
|
||||
public AnimationParams GetAnimationParamsFromType(AnimationType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case AnimationType.Walk:
|
||||
return WalkParams;
|
||||
case AnimationType.Run:
|
||||
return RunParams;
|
||||
case AnimationType.SwimSlow:
|
||||
return SwimSlowParams;
|
||||
case AnimationType.SwimFast:
|
||||
return SwimFastParams;
|
||||
default:
|
||||
throw new NotImplementedException(type.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,52 +2,130 @@
|
||||
using FarseerPhysics.Dynamics.Joints;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class FishAnimController : AnimController
|
||||
{
|
||||
//amplitude and wave length of the "sine wave" swimming animation
|
||||
//if amplitude = 0, sine wave animation isn't used
|
||||
private float waveAmplitude;
|
||||
private float waveLength;
|
||||
public override RagdollParams RagdollParams
|
||||
{
|
||||
get { return FishRagdollParams; }
|
||||
protected set { FishRagdollParams = value as FishRagdollParams; }
|
||||
}
|
||||
|
||||
private float steerTorque;
|
||||
private FishRagdollParams _ragdollParams;
|
||||
public FishRagdollParams FishRagdollParams
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_ragdollParams == null)
|
||||
{
|
||||
_ragdollParams = FishRagdollParams.GetDefaultRagdollParams(character.SpeciesName);
|
||||
}
|
||||
return _ragdollParams;
|
||||
}
|
||||
protected set
|
||||
{
|
||||
_ragdollParams = value;
|
||||
}
|
||||
}
|
||||
|
||||
private bool rotateTowardsMovement;
|
||||
private FishWalkParams _fishWalkParams;
|
||||
public FishWalkParams FishWalkParams
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_fishWalkParams == null)
|
||||
{
|
||||
_fishWalkParams = FishWalkParams.GetDefaultAnimParams(character);
|
||||
}
|
||||
return _fishWalkParams;
|
||||
}
|
||||
set { _fishWalkParams = value; }
|
||||
}
|
||||
|
||||
private bool mirror, flip;
|
||||
private FishRunParams _fishRunParams;
|
||||
public FishRunParams FishRunParams
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_fishRunParams == null)
|
||||
{
|
||||
_fishRunParams = FishRunParams.GetDefaultAnimParams(character);
|
||||
}
|
||||
return _fishRunParams;
|
||||
}
|
||||
set { _fishRunParams = value; }
|
||||
}
|
||||
|
||||
private FishSwimSlowParams _fishSwimSlowParams;
|
||||
public FishSwimSlowParams FishSwimSlowParams
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_fishSwimSlowParams == null)
|
||||
{
|
||||
_fishSwimSlowParams = FishSwimSlowParams.GetDefaultAnimParams(character);
|
||||
}
|
||||
return _fishSwimSlowParams;
|
||||
}
|
||||
set { _fishSwimSlowParams = value; }
|
||||
}
|
||||
|
||||
private FishSwimFastParams _fishSwimFastParams;
|
||||
public FishSwimFastParams FishSwimFastParams
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_fishSwimFastParams == null)
|
||||
{
|
||||
_fishSwimFastParams = FishSwimFastParams.GetDefaultAnimParams(character);
|
||||
}
|
||||
return _fishSwimFastParams;
|
||||
}
|
||||
set { _fishSwimFastParams = value; }
|
||||
}
|
||||
|
||||
public IFishAnimation CurrentFishAnimation => CurrentAnimationParams as IFishAnimation;
|
||||
public new FishGroundedParams CurrentGroundedParams => base.CurrentGroundedParams as FishGroundedParams;
|
||||
public new FishSwimParams CurrentSwimParams => base.CurrentSwimParams as FishSwimParams;
|
||||
|
||||
public float? TailAngle => GetValidOrNull(CurrentAnimationParams, CurrentFishAnimation?.TailAngleInRadians);
|
||||
public float FootTorque => CurrentFishAnimation.FootTorque;
|
||||
public float HeadTorque => CurrentFishAnimation.HeadTorque;
|
||||
public float TorsoTorque => CurrentFishAnimation.TorsoTorque;
|
||||
public float TailTorque => CurrentFishAnimation.TailTorque;
|
||||
public float HeadMoveForce => CurrentGroundedParams.HeadMoveForce;
|
||||
public float TorsoMoveForce => CurrentGroundedParams.TorsoMoveForce;
|
||||
public float FootMoveForce => CurrentGroundedParams.FootMoveForce;
|
||||
|
||||
public override GroundedMovementParams WalkParams
|
||||
{
|
||||
get { return FishWalkParams; }
|
||||
set { FishWalkParams = value as FishWalkParams; }
|
||||
}
|
||||
|
||||
public override GroundedMovementParams RunParams
|
||||
{
|
||||
get { return FishRunParams; }
|
||||
set { FishRunParams = value as FishRunParams; }
|
||||
}
|
||||
|
||||
public override SwimParams SwimSlowParams
|
||||
{
|
||||
get { return FishSwimSlowParams; }
|
||||
set { FishSwimSlowParams = value as FishSwimSlowParams; }
|
||||
}
|
||||
|
||||
public override SwimParams SwimFastParams
|
||||
{
|
||||
get { return FishSwimFastParams; }
|
||||
set { FishSwimFastParams = value as FishSwimFastParams; }
|
||||
}
|
||||
|
||||
private float flipTimer;
|
||||
|
||||
private float? footRotation;
|
||||
|
||||
private float deathAnimTimer, deathAnimDuration = 5.0f;
|
||||
|
||||
public FishAnimController(Character character, XElement element)
|
||||
: base(character, element)
|
||||
{
|
||||
waveAmplitude = ConvertUnits.ToSimUnits(element.GetAttributeFloat("waveamplitude", 0.0f));
|
||||
waveLength = ConvertUnits.ToSimUnits(element.GetAttributeFloat("wavelength", 0.0f));
|
||||
|
||||
steerTorque = element.GetAttributeFloat("steertorque", 25.0f);
|
||||
|
||||
flip = element.GetAttributeBool("flip", true);
|
||||
mirror = element.GetAttributeBool("mirror", false);
|
||||
|
||||
float footRot = element.GetAttributeFloat("footrotation", float.NaN);
|
||||
if (float.IsNaN(footRot))
|
||||
{
|
||||
footRotation = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
footRotation = MathHelper.ToRadians(footRot);
|
||||
}
|
||||
|
||||
rotateTowardsMovement = element.GetAttributeBool("rotatetowardsmovement", true);
|
||||
}
|
||||
public FishAnimController(Character character, string seed, FishRagdollParams ragdollParams = null) : base(character, seed, ragdollParams) { }
|
||||
|
||||
public override void UpdateAnim(float deltaTime)
|
||||
{
|
||||
@@ -55,38 +133,25 @@ namespace Barotrauma
|
||||
|
||||
if (character.IsDead || character.IsUnconscious || character.Stun > 0.0f)
|
||||
{
|
||||
Collider.Enabled = false;
|
||||
Collider.FarseerBody.FixedRotation = false;
|
||||
|
||||
if (character.IsRemotePlayer)
|
||||
{
|
||||
if (!SimplePhysicsEnabled)
|
||||
{
|
||||
MainLimb.PullJointWorldAnchorB = Collider.SimPosition;
|
||||
MainLimb.PullJointEnabled = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 diff = (MainLimb.SimPosition - Collider.SimPosition);
|
||||
if (diff.LengthSquared() > 10.0f * 10.0f)
|
||||
{
|
||||
Collider.SetTransform(MainLimb.SimPosition, MainLimb.Rotation);
|
||||
}
|
||||
else
|
||||
{
|
||||
Collider.LinearVelocity = diff * 60.0f;
|
||||
Collider.SmoothRotate(MainLimb.Rotation);
|
||||
}
|
||||
}
|
||||
//set linear velocity even though the collider is disabled,
|
||||
//because the character won't be able to switch back from ragdoll mode until the velocity of the collider is low enough
|
||||
Collider.LinearVelocity = MainLimb.LinearVelocity;
|
||||
Collider.SetTransformIgnoreContacts(MainLimb.SimPosition, MainLimb.Rotation);
|
||||
|
||||
if (character.IsDead && deathAnimTimer < deathAnimDuration)
|
||||
{
|
||||
deathAnimTimer += deltaTime;
|
||||
UpdateDying(deltaTime);
|
||||
UpdateDying(deltaTime);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
deathAnimTimer = 0.0f;
|
||||
}
|
||||
|
||||
//re-enable collider
|
||||
if (!Collider.Enabled)
|
||||
@@ -109,18 +174,18 @@ namespace Barotrauma
|
||||
strongestImpact = 0.0f;
|
||||
}
|
||||
|
||||
|
||||
if (inWater)
|
||||
if (inWater && !forceStanding)
|
||||
{
|
||||
Collider.FarseerBody.FixedRotation = false;
|
||||
UpdateSineAnim(deltaTime);
|
||||
}
|
||||
else if (currentHull != null && CanEnterSubmarine)
|
||||
else if (CanEnterSubmarine && (currentHull != null || forceStanding) && CurrentGroundedParams != null)
|
||||
{
|
||||
if (Math.Abs(MathUtils.GetShortestAngle(Collider.Rotation, 0.0f)) > 0.001f)
|
||||
//rotate collider back upright
|
||||
float standAngle = dir == Direction.Right ? CurrentGroundedParams.ColliderStandAngleInRadians : -CurrentGroundedParams.ColliderStandAngleInRadians;
|
||||
if (Math.Abs(MathUtils.GetShortestAngle(Collider.Rotation, standAngle)) > 0.001f)
|
||||
{
|
||||
//rotate collider back upright
|
||||
Collider.AngularVelocity = MathUtils.GetShortestAngle(Collider.Rotation, 0.0f) * 60.0f;
|
||||
Collider.AngularVelocity = MathUtils.GetShortestAngle(Collider.Rotation, standAngle) * 60.0f;
|
||||
Collider.FarseerBody.FixedRotation = false;
|
||||
}
|
||||
else
|
||||
@@ -134,26 +199,37 @@ namespace Barotrauma
|
||||
//don't flip or drag when simply physics is enabled
|
||||
if (SimplePhysicsEnabled) { return; }
|
||||
|
||||
if (!character.IsRemotePlayer)
|
||||
if (!character.IsRemotePlayer && (character.AIController == null || character.AIController.CanFlip))
|
||||
{
|
||||
if (mirror || !inWater)
|
||||
if (!inWater || (CurrentSwimParams != null && CurrentSwimParams.Mirror))
|
||||
{
|
||||
if (targetMovement.X > 0.1f && targetMovement.X > Math.Abs(targetMovement.Y) * 0.5f)
|
||||
if (targetMovement.X > 0.1f && targetMovement.X > Math.Abs(targetMovement.Y) * 0.2f)
|
||||
{
|
||||
TargetDir = Direction.Right;
|
||||
}
|
||||
else if (targetMovement.X < -0.1f && targetMovement.X < -Math.Abs(targetMovement.Y) * 0.5f)
|
||||
else if (targetMovement.X < -0.1f && targetMovement.X < -Math.Abs(targetMovement.Y) * 0.2f)
|
||||
{
|
||||
TargetDir = Direction.Left;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Limb head = GetLimb(LimbType.Head);
|
||||
if (head == null) head = GetLimb(LimbType.Torso);
|
||||
float refAngle = 0.0f;
|
||||
Limb refLimb = GetLimb(LimbType.Head);
|
||||
if (refLimb == null)
|
||||
{
|
||||
refAngle = CurrentAnimationParams.TorsoAngleInRadians;
|
||||
refLimb = GetLimb(LimbType.Torso);
|
||||
}
|
||||
else
|
||||
{
|
||||
refAngle = CurrentAnimationParams.HeadAngleInRadians;
|
||||
}
|
||||
|
||||
float rotation = MathUtils.WrapAngleTwoPi(head.Rotation);
|
||||
rotation = MathHelper.ToDegrees(rotation);
|
||||
float rotation = refLimb.Rotation;
|
||||
if (!float.IsNaN(refAngle)) { rotation -= refAngle * Dir; }
|
||||
|
||||
rotation = MathHelper.ToDegrees(MathUtils.WrapAngleTwoPi(rotation));
|
||||
|
||||
if (rotation < 0.0f) rotation += 360;
|
||||
|
||||
@@ -168,9 +244,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (character.SelectedCharacter != null) DragCharacter(character.SelectedCharacter);
|
||||
if (character.SelectedCharacter != null) DragCharacter(character.SelectedCharacter, deltaTime);
|
||||
|
||||
if (!flip) return;
|
||||
if (!CurrentFishAnimation.Flip || IsStuck) return;
|
||||
if (character.AIController != null && !character.AIController.CanFlip) return;
|
||||
|
||||
flipTimer += deltaTime;
|
||||
|
||||
@@ -179,7 +256,10 @@ namespace Barotrauma
|
||||
if (flipTimer > 1.0f || character.IsRemotePlayer)
|
||||
{
|
||||
Flip();
|
||||
if (mirror || !inWater) Mirror();
|
||||
if (!inWater || (CurrentSwimParams != null && CurrentSwimParams.Mirror))
|
||||
{
|
||||
Mirror();
|
||||
}
|
||||
flipTimer = 0.0f;
|
||||
}
|
||||
}
|
||||
@@ -187,7 +267,7 @@ namespace Barotrauma
|
||||
|
||||
private float eatTimer = 0.0f;
|
||||
|
||||
public override void DragCharacter(Character target)
|
||||
public override void DragCharacter(Character target, float deltaTime)
|
||||
{
|
||||
if (target == null) return;
|
||||
|
||||
@@ -202,20 +282,9 @@ namespace Barotrauma
|
||||
|
||||
Character targetCharacter = target;
|
||||
float eatSpeed = character.Mass / targetCharacter.Mass * 0.1f;
|
||||
eatTimer += deltaTime * eatSpeed;
|
||||
|
||||
eatTimer += (float)Timing.Step * eatSpeed;
|
||||
|
||||
Vector2 mouthPos = mouthLimb.SimPosition;
|
||||
if (mouthLimb.MouthPos.HasValue)
|
||||
{
|
||||
float cos = (float)Math.Cos(mouthLimb.Rotation);
|
||||
float sin = (float)Math.Sin(mouthLimb.Rotation);
|
||||
|
||||
mouthPos += new Vector2(
|
||||
mouthLimb.MouthPos.Value.X * cos - mouthLimb.MouthPos.Value.Y * sin,
|
||||
mouthLimb.MouthPos.Value.X * sin + mouthLimb.MouthPos.Value.Y * cos);
|
||||
}
|
||||
|
||||
Vector2 mouthPos = GetMouthPosition().Value;
|
||||
Vector2 attackSimPosition = character.Submarine == null ? ConvertUnits.ToSimUnits(target.WorldPosition) : target.SimPosition;
|
||||
|
||||
Vector2 limbDiff = attackSimPosition - mouthPos;
|
||||
@@ -232,19 +301,21 @@ namespace Barotrauma
|
||||
float pullStrength = (float)(Math.Sin(eatTimer) * Math.Max(Math.Sin(eatTimer * 0.5f), 0.0f));
|
||||
mouthLimb.body.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f * pullStrength);
|
||||
|
||||
if (eatTimer % 1.0f < 0.5f && (eatTimer - (float)Timing.Step * eatSpeed) % 1.0f > 0.5f)
|
||||
character.ApplyStatusEffects(ActionType.OnEating, deltaTime);
|
||||
|
||||
if (eatTimer % 1.0f < 0.5f && (eatTimer - deltaTime * eatSpeed) % 1.0f > 0.5f)
|
||||
{
|
||||
//apply damage to the target character to get some blood particles flying
|
||||
targetCharacter.AnimController.MainLimb.AddDamage(targetCharacter.SimPosition, DamageType.None, Rand.Range(10.0f, 25.0f), 10.0f, false);
|
||||
targetCharacter.AnimController.MainLimb.AddDamage(targetCharacter.SimPosition, 0.0f, 20.0f, 0.0f, false);
|
||||
|
||||
//keep severing joints until there is only one limb left
|
||||
LimbJoint[] nonSeveredJoints = Array.FindAll(targetCharacter.AnimController.LimbJoints, l => !l.IsSevered && l.CanBeSevered);
|
||||
LimbJoint[] nonSeveredJoints = Array.FindAll(targetCharacter.AnimController.LimbJoints,
|
||||
l => !l.IsSevered && l.CanBeSevered && l.LimbA != null && !l.LimbA.IsSevered && l.LimbB != null && !l.LimbB.IsSevered);
|
||||
if (nonSeveredJoints.Length == 0)
|
||||
{
|
||||
//only one limb left, the character is now full eaten
|
||||
Entity.Spawner.AddToRemoveQueue(targetCharacter);
|
||||
character.SelectedCharacter = null;
|
||||
character.Health += 10.0f;
|
||||
}
|
||||
else //sever a random joint
|
||||
{
|
||||
@@ -260,42 +331,124 @@ namespace Barotrauma
|
||||
|
||||
void UpdateSineAnim(float deltaTime)
|
||||
{
|
||||
movement = TargetMovement * swimSpeed;
|
||||
if (CurrentSwimParams == null) { return; }
|
||||
movement = TargetMovement;
|
||||
|
||||
Collider.LinearVelocity = Vector2.Lerp(Collider.LinearVelocity, movement, 0.5f);
|
||||
if (movement.LengthSquared() > 0.00001f)
|
||||
{
|
||||
Collider.LinearVelocity = Vector2.Lerp(Collider.LinearVelocity, movement, 0.5f);
|
||||
}
|
||||
|
||||
//limbs are disabled when simple physics is enabled, no need to move them
|
||||
if (SimplePhysicsEnabled) { return; }
|
||||
|
||||
MainLimb.PullJointEnabled = true;
|
||||
MainLimb.PullJointWorldAnchorB = Collider.SimPosition;
|
||||
|
||||
if (movement.LengthSquared() < 0.00001f) return;
|
||||
//MainLimb.PullJointWorldAnchorB = Collider.SimPosition;
|
||||
|
||||
if (movement.LengthSquared() < 0.00001f)
|
||||
{
|
||||
WalkPos = MathHelper.SmoothStep(WalkPos, MathHelper.PiOver2, deltaTime * 5);
|
||||
MainLimb.PullJointWorldAnchorB = Vector2.Lerp(MainLimb.PullJointWorldAnchorB, Collider.SimPosition, 0.5f);
|
||||
return;
|
||||
}
|
||||
|
||||
float movementAngle = MathUtils.VectorToAngle(movement) - MathHelper.PiOver2;
|
||||
|
||||
if (rotateTowardsMovement)
|
||||
|
||||
float mainLimbAngle = (MainLimb.type == LimbType.Torso ? TorsoAngle.Value : HeadAngle.Value) * Dir;
|
||||
while (MainLimb.Rotation - (movementAngle + mainLimbAngle) > MathHelper.Pi)
|
||||
{
|
||||
Collider.SmoothRotate(movementAngle, 25.0f);
|
||||
MainLimb.body.SmoothRotate(movementAngle, steerTorque);
|
||||
movementAngle += MathHelper.TwoPi;
|
||||
}
|
||||
while (MainLimb.Rotation - (movementAngle + mainLimbAngle) < -MathHelper.Pi)
|
||||
{
|
||||
movementAngle -= MathHelper.TwoPi;
|
||||
}
|
||||
|
||||
if (CurrentSwimParams.RotateTowardsMovement)
|
||||
{
|
||||
Collider.SmoothRotate(movementAngle, CurrentSwimParams.SteerTorque);
|
||||
if (TorsoAngle.HasValue)
|
||||
{
|
||||
Limb torso = GetLimb(LimbType.Torso);
|
||||
if (torso != null)
|
||||
{
|
||||
SmoothRotateWithoutWrapping(torso, movementAngle + TorsoAngle.Value * Dir, MainLimb, TorsoTorque);
|
||||
}
|
||||
}
|
||||
if (HeadAngle.HasValue)
|
||||
{
|
||||
Limb head = GetLimb(LimbType.Head);
|
||||
if (head != null)
|
||||
{
|
||||
SmoothRotateWithoutWrapping(head, movementAngle + HeadAngle.Value * Dir, MainLimb, HeadTorque);
|
||||
}
|
||||
}
|
||||
if (TailAngle.HasValue)
|
||||
{
|
||||
Limb tail = GetLimb(LimbType.Tail);
|
||||
//tail?.body.SmoothRotate(movementAngle + TailAngle.Value * Dir, TailTorque);
|
||||
if (tail != null)
|
||||
{
|
||||
SmoothRotateWithoutWrapping(tail, movementAngle + TailAngle.Value * Dir, MainLimb, TailTorque);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Collider.SmoothRotate(HeadAngle * Dir, 25.0f);
|
||||
MainLimb.body.SmoothRotate(HeadAngle * Dir, steerTorque);
|
||||
movementAngle = Dir > 0 ? -MathHelper.PiOver2 : MathHelper.PiOver2;
|
||||
if (MainLimb.type == LimbType.Head && HeadAngle.HasValue)
|
||||
{
|
||||
Collider.SmoothRotate(HeadAngle.Value * Dir, CurrentSwimParams.SteerTorque);
|
||||
}
|
||||
else if (MainLimb.type == LimbType.Torso && TorsoAngle.HasValue)
|
||||
{
|
||||
Collider.SmoothRotate(TorsoAngle.Value * Dir, CurrentSwimParams.SteerTorque);
|
||||
}
|
||||
if (TorsoAngle.HasValue)
|
||||
{
|
||||
Limb torso = GetLimb(LimbType.Torso);
|
||||
torso?.body.SmoothRotate(TorsoAngle.Value * Dir, TorsoTorque);
|
||||
}
|
||||
if (HeadAngle.HasValue)
|
||||
{
|
||||
Limb head = GetLimb(LimbType.Head);
|
||||
head?.body.SmoothRotate(HeadAngle.Value * Dir, HeadTorque);
|
||||
}
|
||||
if (TailAngle.HasValue)
|
||||
{
|
||||
Limb tail = GetLimb(LimbType.Tail);
|
||||
tail?.body.SmoothRotate(TailAngle.Value * Dir, TailTorque);
|
||||
}
|
||||
}
|
||||
|
||||
Limb tail = GetLimb(LimbType.Tail);
|
||||
if (tail != null && waveAmplitude > 0.0f)
|
||||
var waveLength = Math.Abs(CurrentSwimParams.WaveLength * RagdollParams.JointScale);
|
||||
var waveAmplitude = Math.Abs(CurrentSwimParams.WaveAmplitude);
|
||||
if (waveLength > 0 && waveAmplitude > 0)
|
||||
{
|
||||
walkPos -= movement.Length();
|
||||
|
||||
float waveRotation = (float)Math.Sin(walkPos / waveLength);
|
||||
|
||||
tail.body.ApplyTorque(waveRotation * tail.Mass * 100.0f * waveAmplitude);
|
||||
WalkPos -= movement.Length() / Math.Abs(waveLength);
|
||||
WalkPos = MathUtils.WrapAngleTwoPi(WalkPos);
|
||||
}
|
||||
|
||||
foreach (var limb in Limbs)
|
||||
{
|
||||
switch (limb.type)
|
||||
{
|
||||
case LimbType.LeftFoot:
|
||||
case LimbType.RightFoot:
|
||||
if (CurrentSwimParams.FootAnglesInRadians.ContainsKey(limb.limbParams.ID))
|
||||
{
|
||||
SmoothRotateWithoutWrapping(limb, movementAngle + CurrentSwimParams.FootAnglesInRadians[limb.limbParams.ID] * Dir, MainLimb, FootTorque);
|
||||
}
|
||||
break;
|
||||
case LimbType.Tail:
|
||||
if (waveLength > 0 && waveAmplitude > 0)
|
||||
{
|
||||
float waveRotation = (float)Math.Sin(WalkPos);
|
||||
limb.body.ApplyTorque(waveRotation * limb.Mass * CurrentSwimParams.TailTorque * waveAmplitude);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < Limbs.Length; i++)
|
||||
{
|
||||
@@ -304,13 +457,24 @@ namespace Barotrauma
|
||||
Vector2 pullPos = Limbs[i].PullJointWorldAnchorA;
|
||||
Limbs[i].body.ApplyForce(movement * Limbs[i].SteerForce * Limbs[i].Mass, pullPos);
|
||||
}
|
||||
|
||||
|
||||
if (CurrentSwimParams.UseSineMovement)
|
||||
{
|
||||
MainLimb.PullJointWorldAnchorB = Vector2.SmoothStep(MainLimb.PullJointWorldAnchorB, Collider.SimPosition, (float)Math.Abs(Math.Sin(WalkPos)));
|
||||
}
|
||||
else
|
||||
{
|
||||
//MainLimb.PullJointWorldAnchorB = Collider.SimPosition;
|
||||
MainLimb.PullJointWorldAnchorB = Vector2.Lerp(MainLimb.PullJointWorldAnchorB, Collider.SimPosition, 0.5f);
|
||||
}
|
||||
|
||||
floorY = Limbs[0].SimPosition.Y;
|
||||
}
|
||||
|
||||
void UpdateWalkAnim(float deltaTime)
|
||||
{
|
||||
movement = MathUtils.SmoothStep(movement, TargetMovement * walkSpeed, 0.2f);
|
||||
if (CurrentGroundedParams == null) { return; }
|
||||
movement = MathUtils.SmoothStep(movement, TargetMovement, 0.2f);
|
||||
|
||||
Collider.LinearVelocity = new Vector2(
|
||||
movement.X,
|
||||
@@ -319,30 +483,83 @@ namespace Barotrauma
|
||||
//limbs are disabled when simple physics is enabled, no need to move them
|
||||
if (SimplePhysicsEnabled) { return; }
|
||||
|
||||
float mainLimbHeight, mainLimbAngle;
|
||||
if (MainLimb.type == LimbType.Torso)
|
||||
float mainLimbHeight = ColliderHeightFromFloor;
|
||||
|
||||
Vector2 colliderBottom = GetColliderBottom();
|
||||
|
||||
float movementAngle = 0.0f;
|
||||
float mainLimbAngle = (MainLimb.type == LimbType.Torso ? TorsoAngle.Value : HeadAngle.Value) * Dir;
|
||||
while (MainLimb.Rotation - (movementAngle + mainLimbAngle) > MathHelper.Pi)
|
||||
{
|
||||
mainLimbHeight = TorsoPosition;
|
||||
mainLimbAngle = torsoAngle;
|
||||
movementAngle += MathHelper.TwoPi;
|
||||
}
|
||||
else
|
||||
while (MainLimb.Rotation - (movementAngle + mainLimbAngle) < -MathHelper.Pi)
|
||||
{
|
||||
mainLimbHeight = HeadPosition;
|
||||
mainLimbAngle = headAngle;
|
||||
movementAngle -= MathHelper.TwoPi;
|
||||
}
|
||||
|
||||
MainLimb.body.SmoothRotate(mainLimbAngle * Dir, 50.0f);
|
||||
Limb torso = GetLimb(LimbType.Torso);
|
||||
if (torso != null)
|
||||
{
|
||||
if (TorsoAngle.HasValue)
|
||||
{
|
||||
SmoothRotateWithoutWrapping(torso, movementAngle + TorsoAngle.Value * Dir, MainLimb, TorsoTorque);
|
||||
}
|
||||
if (TorsoPosition.HasValue)
|
||||
{
|
||||
Vector2 pos = colliderBottom + Vector2.UnitY * TorsoPosition.Value;
|
||||
|
||||
MainLimb.MoveToPos(GetColliderBottom() + Vector2.UnitY * mainLimbHeight, 10.0f);
|
||||
|
||||
MainLimb.PullJointEnabled = true;
|
||||
MainLimb.PullJointWorldAnchorB = GetColliderBottom() + Vector2.UnitY * mainLimbHeight;
|
||||
if (torso != MainLimb)
|
||||
pos.X = torso.SimPosition.X;
|
||||
else
|
||||
mainLimbHeight = TorsoPosition.Value;
|
||||
|
||||
walkPos -= MainLimb.LinearVelocity.X * 0.05f;
|
||||
torso.MoveToPos(pos, TorsoMoveForce);
|
||||
torso.PullJointEnabled = true;
|
||||
torso.PullJointWorldAnchorB = pos;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 transformedStepSize = new Vector2(
|
||||
(float)Math.Cos(walkPos) * stepSize.X * 3.0f,
|
||||
(float)Math.Sin(walkPos) * stepSize.Y * 2.0f);
|
||||
Limb head = GetLimb(LimbType.Head);
|
||||
if (head != null)
|
||||
{
|
||||
if (HeadAngle.HasValue)
|
||||
{
|
||||
SmoothRotateWithoutWrapping(head, movementAngle + HeadAngle.Value * Dir, MainLimb, HeadTorque);
|
||||
}
|
||||
if (HeadPosition.HasValue)
|
||||
{
|
||||
Vector2 pos = colliderBottom + Vector2.UnitY * HeadPosition.Value;
|
||||
|
||||
if (head != MainLimb)
|
||||
pos.X = head.SimPosition.X;
|
||||
else
|
||||
mainLimbHeight = HeadPosition.Value;
|
||||
|
||||
head.MoveToPos(pos, HeadMoveForce);
|
||||
head.PullJointEnabled = true;
|
||||
head.PullJointWorldAnchorB = pos;
|
||||
}
|
||||
}
|
||||
|
||||
if (TailAngle.HasValue)
|
||||
{
|
||||
var tail = GetLimb(LimbType.Tail);
|
||||
if (tail != null)
|
||||
{
|
||||
SmoothRotateWithoutWrapping(tail, movementAngle + TailAngle.Value * Dir, MainLimb, TailTorque);
|
||||
}
|
||||
}
|
||||
|
||||
WalkPos -= MainLimb.LinearVelocity.X * (CurrentAnimationParams.CycleSpeed / RagdollParams.JointScale / 100.0f);
|
||||
|
||||
Vector2 transformedStepSize = Vector2.Zero;
|
||||
if (Math.Abs(TargetMovement.X) > 0.01f)
|
||||
{
|
||||
transformedStepSize = new Vector2(
|
||||
(float)Math.Cos(WalkPos) * StepSize.Value.X * 3.0f,
|
||||
(float)Math.Sin(WalkPos) * StepSize.Value.Y * 2.0f);
|
||||
}
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
@@ -350,56 +567,78 @@ namespace Barotrauma
|
||||
{
|
||||
case LimbType.LeftFoot:
|
||||
case LimbType.RightFoot:
|
||||
Vector2 footPos = new Vector2(limb.SimPosition.X, MainLimb.SimPosition.Y - mainLimbHeight);
|
||||
Vector2 footPos = new Vector2(limb.SimPosition.X, colliderBottom.Y);
|
||||
|
||||
if (limb.RefJointIndex>-1)
|
||||
if (limb.RefJointIndex > -1)
|
||||
{
|
||||
RevoluteJoint refJoint = LimbJoints[limb.RefJointIndex];
|
||||
footPos.X = refJoint.WorldAnchorA.X;
|
||||
if (LimbJoints.Length <= limb.RefJointIndex)
|
||||
{
|
||||
DebugConsole.ThrowError($"Reference joint index {limb.RefJointIndex} is out of array. This is probably due to a missing joint. If you just deleted a joint, don't do that without first removing the reference joint indices from the limbs. If this is not the case, please ensure that you have defined the index to the right joint.");
|
||||
}
|
||||
else
|
||||
{
|
||||
footPos.X = LimbJoints[limb.RefJointIndex].WorldAnchorA.X;
|
||||
}
|
||||
}
|
||||
footPos.X += limb.StepOffset.X * Dir;
|
||||
footPos.Y += limb.StepOffset.Y;
|
||||
|
||||
if (limb.type == LimbType.LeftFoot)
|
||||
{
|
||||
limb.MoveToPos(footPos +new Vector2(
|
||||
limb.DebugRefPos = footPos + Vector2.UnitX * movement.X * 0.1f;
|
||||
limb.DebugTargetPos = footPos + new Vector2(
|
||||
transformedStepSize.X + movement.X * 0.1f,
|
||||
(transformedStepSize.Y > 0.0f) ? transformedStepSize.Y : 0.0f),
|
||||
8.0f);
|
||||
(transformedStepSize.Y > 0.0f) ? transformedStepSize.Y : 0.0f);
|
||||
limb.MoveToPos(limb.DebugTargetPos, FootMoveForce);
|
||||
}
|
||||
else if (limb.type == LimbType.RightFoot)
|
||||
{
|
||||
limb.MoveToPos(footPos + new Vector2(
|
||||
limb.DebugRefPos = footPos + Vector2.UnitX * movement.X * 0.1f;
|
||||
limb.DebugTargetPos = footPos + new Vector2(
|
||||
-transformedStepSize.X + movement.X * 0.1f,
|
||||
(-transformedStepSize.Y > 0.0f) ? -transformedStepSize.Y : 0.0f),
|
||||
8.0f);
|
||||
(-transformedStepSize.Y > 0.0f) ? -transformedStepSize.Y : 0.0f);
|
||||
limb.MoveToPos(limb.DebugTargetPos, FootMoveForce);
|
||||
}
|
||||
|
||||
if (footRotation != null) limb.body.SmoothRotate((float)footRotation * Dir, 50.0f);
|
||||
|
||||
if (CurrentGroundedParams.FootAnglesInRadians.ContainsKey(limb.limbParams.ID))
|
||||
{
|
||||
SmoothRotateWithoutWrapping(limb,
|
||||
movementAngle + CurrentGroundedParams.FootAnglesInRadians[limb.limbParams.ID] * Dir,
|
||||
MainLimb, FootTorque);
|
||||
}
|
||||
break;
|
||||
case LimbType.LeftLeg:
|
||||
case LimbType.RightLeg:
|
||||
if (legTorque != 0.0f) limb.body.ApplyTorque(limb.Mass * legTorque * Dir);
|
||||
if (Math.Abs(CurrentGroundedParams.LegTorque) > 0.001f) limb.body.ApplyTorque(limb.Mass * CurrentGroundedParams.LegTorque * Dir);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void UpdateDying(float deltaTime)
|
||||
{
|
||||
if (deathAnimDuration <= 0.0f) return;
|
||||
|
||||
float animStrength = (1.0f - deathAnimTimer / deathAnimDuration);
|
||||
|
||||
Limb head = GetLimb(LimbType.Head);
|
||||
Limb tail = GetLimb(LimbType.Tail);
|
||||
|
||||
if (head != null && !head.IsSevered) head.body.ApplyTorque((float)(Math.Sqrt(head.Mass) * Dir * Math.Sin(walkPos)) * 10.0f);
|
||||
if (tail != null && !tail.IsSevered) tail.body.ApplyTorque((float)(Math.Sqrt(tail.Mass) * -Dir * (float)Math.Sin(walkPos)) * 10.0f);
|
||||
if (head != null && !head.IsSevered) head.body.ApplyTorque((float)(Math.Sqrt(head.Mass) * Dir * Math.Sin(WalkPos)) * 30.0f * animStrength);
|
||||
if (tail != null && !tail.IsSevered) tail.body.ApplyTorque((float)(Math.Sqrt(tail.Mass) * -Dir * Math.Sin(WalkPos)) * 30.0f * animStrength);
|
||||
|
||||
walkPos += deltaTime * 5.0f;
|
||||
WalkPos += deltaTime * 10.0f * animStrength;
|
||||
|
||||
Vector2 centerOfMass = GetCenterOfMass();
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
#if CLIENT
|
||||
if (limb.LightSource != null)
|
||||
{
|
||||
limb.LightSource.Color = Color.Lerp(limb.InitialLightSourceColor, Color.TransparentBlack, deathAnimTimer / deathAnimDuration);
|
||||
}
|
||||
#endif
|
||||
if (limb.type == LimbType.Head || limb.type == LimbType.Tail || limb.IsSevered || !limb.body.Enabled) continue;
|
||||
if (limb.Mass <= 0.0f)
|
||||
{
|
||||
@@ -420,20 +659,33 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
limb.body.ApplyForce(diff * (float)(Math.Sin(walkPos) * Math.Sqrt(limb.Mass)) * 10.0f);
|
||||
limb.body.ApplyForce(diff * (float)(Math.Sin(WalkPos) * Math.Sqrt(limb.Mass)) * 30.0f * animStrength);
|
||||
}
|
||||
}
|
||||
|
||||
private void SmoothRotateWithoutWrapping(Limb limb, float angle, Limb referenceLimb, float torque)
|
||||
{
|
||||
//make sure the angle "has the same number of revolutions" as the reference limb
|
||||
//(e.g. we don't want to rotate the legs to 0 if the torso is at 360, because that'd blow up the hip joints)
|
||||
while (referenceLimb.Rotation - angle > MathHelper.TwoPi)
|
||||
{
|
||||
angle += MathHelper.TwoPi;
|
||||
}
|
||||
while (referenceLimb.Rotation - angle < -MathHelper.TwoPi)
|
||||
{
|
||||
angle -= MathHelper.TwoPi;
|
||||
}
|
||||
|
||||
limb?.body.SmoothRotate(angle, torque, wrapAngle: false);
|
||||
}
|
||||
|
||||
public override void Flip()
|
||||
{
|
||||
base.Flip();
|
||||
|
||||
foreach (Limb l in Limbs)
|
||||
{
|
||||
if (!l.DoesFlip) continue;
|
||||
|
||||
l.body.SetTransform(l.SimPosition,
|
||||
-l.body.Rotation);
|
||||
l.body.SetTransform(l.SimPosition, -l.body.Rotation);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+803
-368
File diff suppressed because it is too large
Load Diff
+398
@@ -0,0 +1,398 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public enum AnimationType
|
||||
{
|
||||
NotDefined,
|
||||
Walk,
|
||||
Run,
|
||||
SwimSlow,
|
||||
SwimFast
|
||||
}
|
||||
|
||||
abstract class GroundedMovementParams : AnimationParams
|
||||
{
|
||||
[Serialize("1.0, 1.0", true), Editable(DecimalCount = 2, ToolTip = "How big steps the character takes.")]
|
||||
public Vector2 StepSize
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0f, true), Editable(DecimalCount = 2, ToolTip = "How high above the ground the character's head is positioned.")]
|
||||
public float HeadPosition { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable(DecimalCount = 2, ToolTip = "How high above the ground the character's torso is positioned.")]
|
||||
public float TorsoPosition { get; set; }
|
||||
|
||||
[Serialize(0.75f, true), Editable(MinValueFloat = 0.1f, MaxValueFloat = 0.99f, DecimalCount = 2, ToolTip = "The character's movement speed is multiplied with this value when moving backwards.")]
|
||||
public float BackwardsMovementMultiplier { get; set; }
|
||||
}
|
||||
|
||||
abstract class SwimParams : AnimationParams
|
||||
{
|
||||
[Serialize(25.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
|
||||
public float SteerTorque { get; set; }
|
||||
}
|
||||
|
||||
abstract class AnimationParams : EditableParams
|
||||
{
|
||||
public string SpeciesName { get; private set; }
|
||||
public bool IsGroundedAnimation => AnimationType == AnimationType.Walk || AnimationType == AnimationType.Run;
|
||||
public bool IsSwimAnimation => AnimationType == AnimationType.SwimSlow || AnimationType == AnimationType.SwimFast;
|
||||
|
||||
protected static Dictionary<string, Dictionary<string, AnimationParams>> allAnimations = new Dictionary<string, Dictionary<string, AnimationParams>>();
|
||||
|
||||
[Serialize(1.0f, true), Editable(DecimalCount = 2)]
|
||||
public float MovementSpeed { get; set; }
|
||||
|
||||
[Serialize(1.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2,
|
||||
ToolTip = "The speed of the \"animation cycle\", i.e. how fast the character takes steps or moves the tail/legs/arms (the outcome depends what the clip is about)")]
|
||||
public float CycleSpeed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// In degrees.
|
||||
/// </summary>
|
||||
[Serialize(float.NaN, true), Editable(-360f, 360f)]
|
||||
public float HeadAngle
|
||||
{
|
||||
get => float.IsNaN(HeadAngleInRadians) ? float.NaN : MathHelper.ToDegrees(HeadAngleInRadians);
|
||||
set
|
||||
{
|
||||
if (!float.IsNaN(value))
|
||||
{
|
||||
HeadAngleInRadians = MathHelper.ToRadians(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
public float HeadAngleInRadians { get; private set; } = float.NaN;
|
||||
|
||||
/// <summary>
|
||||
/// In degrees.
|
||||
/// </summary>
|
||||
[Serialize(float.NaN, true), Editable(-360f, 360f)]
|
||||
public float TorsoAngle
|
||||
{
|
||||
get => float.IsNaN(TorsoAngleInRadians) ? float.NaN : MathHelper.ToDegrees(TorsoAngleInRadians);
|
||||
set
|
||||
{
|
||||
if (!float.IsNaN(value))
|
||||
{
|
||||
TorsoAngleInRadians = MathHelper.ToRadians(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
public float TorsoAngleInRadians { get; private set; } = float.NaN;
|
||||
|
||||
[Serialize(AnimationType.NotDefined, true), Editable]
|
||||
public virtual AnimationType AnimationType { get; protected set; }
|
||||
|
||||
public static string GetDefaultFileName(string speciesName, AnimationType animType) => $"{speciesName.CapitaliseFirstInvariant()}{animType.ToString()}";
|
||||
public static string GetDefaultFolder(string speciesName) => $"Content/Characters/{speciesName.CapitaliseFirstInvariant()}/Animations/";
|
||||
public static string GetDefaultFile(string speciesName, AnimationType animType) => $"{GetDefaultFolder(speciesName)}{GetDefaultFileName(speciesName, animType)}.xml";
|
||||
|
||||
protected static string GetFolder(string speciesName)
|
||||
{
|
||||
var folder = XMLExtensions.TryLoadXml(Character.GetConfigFile(speciesName))?.Root?.Element("animations")?.GetAttributeString("folder", string.Empty);
|
||||
if (string.IsNullOrEmpty(folder) || folder.ToLowerInvariant() == "default")
|
||||
{
|
||||
folder = GetDefaultFolder(speciesName);
|
||||
}
|
||||
return folder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects a random filepath from multiple paths, matching the specified animation type.
|
||||
/// </summary>
|
||||
public static string GetRandomFilePath(IEnumerable<string> filePaths, AnimationType type)
|
||||
{
|
||||
return filePaths.GetRandom(f => AnimationPredicate(f, type), Rand.RandSync.Server);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects all file paths that match the specified animation type.
|
||||
/// </summary>
|
||||
public static IEnumerable<string> FilterFilesByType(IEnumerable<string> filePaths, AnimationType type)
|
||||
{
|
||||
return filePaths.Where(f => AnimationPredicate(f, type));
|
||||
}
|
||||
|
||||
private static bool AnimationPredicate(string filePath, AnimationType type)
|
||||
{
|
||||
var doc = XMLExtensions.TryLoadXml(filePath);
|
||||
if (doc == null) { return false; }
|
||||
var typeString = doc.Root.GetAttributeString("animationtype", null);
|
||||
if (string.IsNullOrWhiteSpace(typeString))
|
||||
{
|
||||
typeString = doc.Root.GetAttributeString("AnimationType", "NotDefined");
|
||||
}
|
||||
return Enum.TryParse(typeString, out AnimationType fileType) && fileType == type;
|
||||
}
|
||||
|
||||
public static T GetDefaultAnimParams<T>(string speciesName, AnimationType animType) where T : AnimationParams, new() => GetAnimParams<T>(speciesName, animType, GetDefaultFileName(speciesName, animType));
|
||||
|
||||
/// <summary>
|
||||
/// If the file name is left null, default file is selected. If fails, will select the default file. Note: Use the filename without the extensions, don't use the full path!
|
||||
/// If a custom folder is used, it's defined in the character info file.
|
||||
/// </summary>
|
||||
public static T GetAnimParams<T>(string speciesName, AnimationType animType, string fileName = null) where T : AnimationParams, new()
|
||||
{
|
||||
if (!allAnimations.TryGetValue(speciesName, out Dictionary<string, AnimationParams> anims))
|
||||
{
|
||||
anims = new Dictionary<string, AnimationParams>();
|
||||
allAnimations.Add(speciesName, anims);
|
||||
}
|
||||
if (fileName == null || !anims.TryGetValue(fileName, out AnimationParams anim))
|
||||
{
|
||||
string selectedFile = null;
|
||||
string folder = GetFolder(speciesName);
|
||||
if (Directory.Exists(folder))
|
||||
{
|
||||
var files = Directory.GetFiles(folder);
|
||||
if (files.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"[AnimationParams] Could not find any animation files from the folder: {folder}. Using the default animation.");
|
||||
selectedFile = GetDefaultFile(speciesName, animType);
|
||||
}
|
||||
var filteredFiles = FilterFilesByType(files, animType);
|
||||
if (filteredFiles.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"[AnimationParams] Could not find any animation files that match the animation type {animType} from the folder: {folder}. Using the default animation.");
|
||||
selectedFile = GetDefaultFile(speciesName, animType);
|
||||
}
|
||||
else if (string.IsNullOrEmpty(fileName))
|
||||
{
|
||||
// Files found, but none specified.
|
||||
selectedFile = GetDefaultFile(speciesName, animType);
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedFile = filteredFiles.FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).ToLowerInvariant() == fileName.ToLowerInvariant());
|
||||
if (selectedFile == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"[AnimationParams] Could not find an animation file that matches the name {fileName} and the animation type {animType}. Using the default animations.");
|
||||
selectedFile = GetDefaultFile(speciesName, animType);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"[Animationparams] Invalid directory: {folder}. Using the default animation.");
|
||||
selectedFile = GetDefaultFile(speciesName, animType);
|
||||
}
|
||||
if (selectedFile == null)
|
||||
{
|
||||
throw new Exception("[AnimationParams] Selected file null!");
|
||||
}
|
||||
DebugConsole.Log($"[AnimationParams] Loading animations from {selectedFile}.");
|
||||
T a = new T();
|
||||
if (a.Load(selectedFile, speciesName))
|
||||
{
|
||||
if (!anims.ContainsKey(a.Name))
|
||||
{
|
||||
anims.Add(a.Name, a);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"[AnimationParams] Failed to load an animation {a} at {selectedFile} of type {animType} for the character {speciesName}");
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return (T)anim;
|
||||
}
|
||||
|
||||
public static AnimationParams Create(string fullPath, string speciesName, AnimationType animationType, Type type)
|
||||
{
|
||||
if (type == typeof(HumanWalkParams))
|
||||
{
|
||||
return Create<HumanWalkParams>(fullPath, speciesName, animationType);
|
||||
}
|
||||
if (type == typeof(HumanRunParams))
|
||||
{
|
||||
return Create<HumanRunParams>(fullPath, speciesName, animationType);
|
||||
}
|
||||
if (type == typeof(HumanSwimSlowParams))
|
||||
{
|
||||
return Create<HumanSwimSlowParams>(fullPath, speciesName, animationType);
|
||||
}
|
||||
if (type == typeof(HumanSwimFastParams))
|
||||
{
|
||||
return Create<HumanSwimFastParams>(fullPath, speciesName, animationType);
|
||||
}
|
||||
if (type == typeof(FishWalkParams))
|
||||
{
|
||||
return Create<FishWalkParams>(fullPath, speciesName, animationType);
|
||||
}
|
||||
if (type == typeof(FishRunParams))
|
||||
{
|
||||
return Create<FishRunParams>(fullPath, speciesName, animationType);
|
||||
}
|
||||
if (type == typeof(FishSwimSlowParams))
|
||||
{
|
||||
return Create<FishSwimSlowParams>(fullPath, speciesName, animationType);
|
||||
}
|
||||
if (type == typeof(FishSwimFastParams))
|
||||
{
|
||||
return Create<FishSwimFastParams>(fullPath, speciesName, animationType);
|
||||
}
|
||||
throw new NotImplementedException(type.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note: Overrides old animations, if found!
|
||||
/// </summary>
|
||||
public static T Create<T>(string fullPath, string speciesName, AnimationType animationType) where T : AnimationParams, new()
|
||||
{
|
||||
if (animationType == AnimationType.NotDefined)
|
||||
{
|
||||
throw new Exception("Cannot create an animation file of type " + animationType.ToString());
|
||||
}
|
||||
if (!allAnimations.TryGetValue(speciesName, out Dictionary<string, AnimationParams> anims))
|
||||
{
|
||||
anims = new Dictionary<string, AnimationParams>();
|
||||
allAnimations.Add(speciesName, anims);
|
||||
}
|
||||
var fileName = Path.GetFileNameWithoutExtension(fullPath);
|
||||
if (anims.ContainsKey(fileName))
|
||||
{
|
||||
DebugConsole.NewMessage($"[AnimationParams] Removing the old animation of type {animationType}.", Color.Red);
|
||||
anims.Remove(fileName);
|
||||
}
|
||||
var instance = new T();
|
||||
XElement animationElement = new XElement(GetDefaultFileName(speciesName, animationType), new XAttribute("animationtype", animationType.ToString()));
|
||||
instance.doc = new XDocument(animationElement);
|
||||
instance.UpdatePath(fullPath);
|
||||
instance.IsLoaded = instance.Deserialize(animationElement);
|
||||
instance.Save();
|
||||
instance.Load(fullPath, speciesName);
|
||||
anims.Add(instance.Name, instance);
|
||||
DebugConsole.NewMessage($"[AnimationParams] New animation file of type {animationType} created.", Color.GhostWhite);
|
||||
return instance as T;
|
||||
}
|
||||
|
||||
protected bool Load(string file, string speciesName)
|
||||
{
|
||||
if (Load(file))
|
||||
{
|
||||
SpeciesName = speciesName;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void UpdatePath(string newPath)
|
||||
{
|
||||
if (SpeciesName == null)
|
||||
{
|
||||
base.UpdatePath(newPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update the key by removing and re-adding the animation.
|
||||
if (allAnimations.TryGetValue(SpeciesName, out Dictionary<string, AnimationParams> animations))
|
||||
{
|
||||
animations.Remove(Name);
|
||||
}
|
||||
base.UpdatePath(newPath);
|
||||
if (animations != null)
|
||||
{
|
||||
if (!animations.ContainsKey(Name))
|
||||
{
|
||||
animations.Add(Name, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static string ParseFootAngles(Dictionary<int, float> footAngles)
|
||||
{
|
||||
//convert to the format "id1:angle,id2:angle,id3:angle"
|
||||
return string.Join(",", footAngles.Select(kv => kv.Key + ": " + kv.Value.ToString("G", CultureInfo.InvariantCulture)).ToArray());
|
||||
}
|
||||
|
||||
protected static void SetFootAngles(Dictionary<int, float> footAngles, string value)
|
||||
{
|
||||
footAngles.Clear();
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string[] keyValuePairs = value.Split(',');
|
||||
foreach (string joinedKvp in keyValuePairs)
|
||||
{
|
||||
string[] keyValuePair = joinedKvp.Split(':');
|
||||
if (keyValuePair.Length != 2 ||
|
||||
!int.TryParse(keyValuePair[0].Trim(), out int limbIndex) ||
|
||||
!float.TryParse(keyValuePair[1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out float angle))
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to parse foot angles (" + value + ")");
|
||||
continue;
|
||||
}
|
||||
footAngles[limbIndex] = angle;
|
||||
}
|
||||
}
|
||||
|
||||
public static Type GetParamTypeFromAnimType(AnimationType type, bool isHumanoid)
|
||||
{
|
||||
if (isHumanoid)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case AnimationType.Walk:
|
||||
return typeof(HumanWalkParams);
|
||||
case AnimationType.Run:
|
||||
return typeof(HumanRunParams);
|
||||
case AnimationType.SwimSlow:
|
||||
return typeof(HumanSwimSlowParams);
|
||||
case AnimationType.SwimFast:
|
||||
return typeof(HumanSwimFastParams);
|
||||
default:
|
||||
throw new NotImplementedException(type.ToString());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case AnimationType.Walk:
|
||||
return typeof(FishWalkParams);
|
||||
case AnimationType.Run:
|
||||
return typeof(FishRunParams);
|
||||
case AnimationType.SwimSlow:
|
||||
return typeof(FishSwimSlowParams);
|
||||
case AnimationType.SwimFast:
|
||||
return typeof(FishSwimFastParams);
|
||||
default:
|
||||
throw new NotImplementedException(type.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Memento
|
||||
protected void CreateSnapshot<T>() where T : AnimationParams, new()
|
||||
{
|
||||
Serialize();
|
||||
var copy = new T
|
||||
{
|
||||
IsLoaded = true,
|
||||
doc = new XDocument(doc)
|
||||
};
|
||||
copy.Deserialize();
|
||||
copy.Serialize();
|
||||
memento.Store(copy);
|
||||
}
|
||||
public override void Undo() => Deserialize(memento.Undo().MainElement);
|
||||
public override void Redo() => Deserialize(memento.Redo().MainElement);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class FishWalkParams : FishGroundedParams
|
||||
{
|
||||
public static FishWalkParams GetDefaultAnimParams(Character character)
|
||||
{
|
||||
return Check(character) ? GetDefaultAnimParams<FishWalkParams>(character.SpeciesName, AnimationType.Walk) : Empty;
|
||||
}
|
||||
public static FishWalkParams GetAnimParams(Character character, string fileName = null)
|
||||
{
|
||||
return Check(character) ? GetAnimParams<FishWalkParams>(character.SpeciesName, AnimationType.Walk, fileName) : Empty;
|
||||
}
|
||||
|
||||
protected static FishWalkParams Empty = new FishWalkParams();
|
||||
|
||||
public override void CreateSnapshot() => CreateSnapshot<FishWalkParams>();
|
||||
}
|
||||
|
||||
class FishRunParams : FishGroundedParams
|
||||
{
|
||||
public static FishRunParams GetDefaultAnimParams(Character character)
|
||||
{
|
||||
return Check(character) ? GetDefaultAnimParams<FishRunParams>(character.SpeciesName, AnimationType.Run) : Empty;
|
||||
}
|
||||
public static FishRunParams GetAnimParams(Character character, string fileName = null)
|
||||
{
|
||||
return Check(character) ? GetAnimParams<FishRunParams>(character.SpeciesName, AnimationType.Run, fileName) : Empty;
|
||||
}
|
||||
|
||||
protected static FishRunParams Empty = new FishRunParams();
|
||||
|
||||
public override void CreateSnapshot() => CreateSnapshot<FishRunParams>();
|
||||
}
|
||||
|
||||
class FishSwimFastParams : FishSwimParams
|
||||
{
|
||||
public static FishSwimFastParams GetDefaultAnimParams(Character character) => GetDefaultAnimParams<FishSwimFastParams>(character.SpeciesName, AnimationType.SwimFast);
|
||||
public static FishSwimFastParams GetAnimParams(Character character, string fileName = null)
|
||||
{
|
||||
return GetAnimParams<FishSwimFastParams>(character.SpeciesName, AnimationType.SwimFast, fileName);
|
||||
}
|
||||
|
||||
public override void CreateSnapshot() => CreateSnapshot<FishSwimFastParams>();
|
||||
}
|
||||
|
||||
class FishSwimSlowParams : FishSwimParams
|
||||
{
|
||||
public static FishSwimSlowParams GetDefaultAnimParams(Character character) => GetDefaultAnimParams<FishSwimSlowParams>(character.SpeciesName, AnimationType.SwimSlow);
|
||||
public static FishSwimSlowParams GetAnimParams(Character character, string fileName = null)
|
||||
{
|
||||
return GetAnimParams<FishSwimSlowParams>(character.SpeciesName, AnimationType.SwimSlow, fileName);
|
||||
}
|
||||
|
||||
public override void CreateSnapshot() => CreateSnapshot<FishSwimSlowParams>();
|
||||
}
|
||||
|
||||
abstract class FishGroundedParams : GroundedMovementParams, IFishAnimation
|
||||
{
|
||||
protected static bool Check(Character character)
|
||||
{
|
||||
if (!character.AnimController.CanWalk)
|
||||
{
|
||||
DebugConsole.ThrowError($"{character.SpeciesName} cannot use run animations!");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[Serialize(true, true), Editable(ToolTip = "Should the character be flipped depending on which direction it faces. Should usually be enabled on all characters that have distinctive upper and lower sides.")]
|
||||
public bool Flip { get; set; }
|
||||
|
||||
[Serialize(10.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 100, ToolTip = "How much force is used to move the head to the correct position.")]
|
||||
public float HeadMoveForce { get; set; }
|
||||
|
||||
[Serialize(10.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 100, ToolTip = "How much force is used to move the torso to the correct position.")]
|
||||
public float TorsoMoveForce { get; set; }
|
||||
|
||||
[Serialize(8.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 100, ToolTip = "How much force is used to move the feet to the correct position.")]
|
||||
public float FootMoveForce { get; set; }
|
||||
|
||||
[Serialize(50.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "How much torque is used to rotate the head to the correct orientation.")]
|
||||
public float HeadTorque { get; set; }
|
||||
|
||||
[Serialize(50.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "How much torque is used to rotate the torso to the correct orientation.")]
|
||||
public float TorsoTorque { get; set; }
|
||||
|
||||
[Serialize(50.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "How much torque is used to rotate the tail to the correct orientation.")]
|
||||
public float TailTorque { get; set; }
|
||||
|
||||
[Serialize(25.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "How much torque is used to rotate the feet to the correct orientation.")]
|
||||
public float FootTorque { get; set; }
|
||||
|
||||
[Serialize(0.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "Optional torque that's constantly applied to legs.")]
|
||||
public float LegTorque { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The angle of the collider when standing (i.e. out of water).
|
||||
/// In degrees.
|
||||
/// </summary>
|
||||
[Serialize(0f, true), Editable(MinValueFloat = -360, MaxValueFloat = 360, ToolTip = "The angle of the character's collider when standing.")]
|
||||
public float ColliderStandAngle
|
||||
{
|
||||
get => MathHelper.ToDegrees(ColliderStandAngleInRadians);
|
||||
set => ColliderStandAngleInRadians = MathHelper.ToRadians(value);
|
||||
}
|
||||
public float ColliderStandAngleInRadians { get; private set; }
|
||||
|
||||
[Serialize(null, true), Editable]
|
||||
public string FootAngles
|
||||
{
|
||||
get => ParseFootAngles(FootAnglesInRadians);
|
||||
set => SetFootAngles(FootAnglesInRadians, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Key = limb id, value = angle in radians
|
||||
/// </summary>
|
||||
public Dictionary<int, float> FootAnglesInRadians { get; set; } = new Dictionary<int, float>();
|
||||
|
||||
/// <summary>
|
||||
/// In degrees.
|
||||
/// </summary>
|
||||
[Serialize(float.NaN, true), Editable(-360f, 360f)]
|
||||
public float TailAngle
|
||||
{
|
||||
get => float.IsNaN(TailAngleInRadians) ? float.NaN : MathHelper.ToDegrees(TailAngleInRadians);
|
||||
set
|
||||
{
|
||||
if (!float.IsNaN(value))
|
||||
{
|
||||
TailAngleInRadians = MathHelper.ToRadians(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
public float TailAngleInRadians { get; private set; } = float.NaN;
|
||||
}
|
||||
|
||||
abstract class FishSwimParams : SwimParams, IFishAnimation
|
||||
{
|
||||
[Serialize(false, true), Editable(ToolTip = "TODO")]
|
||||
public bool UseSineMovement { get; set; }
|
||||
|
||||
[Serialize(true, true), Editable(ToolTip = "Should the character be flipped depending on which direction it faces. Should usually be enabled on all characters that have distinctive upper and lower sides.")]
|
||||
public bool Flip { get; set; }
|
||||
|
||||
[Serialize(true, true), Editable(ToolTip = "If enabled, the character will simply be mirrored horizontally when it wants to turn around. If disabled, it will rotate itself to face the other direction.")]
|
||||
public bool Mirror { get; set; }
|
||||
|
||||
[Serialize(1f, true), Editable]
|
||||
public float WaveAmplitude { get; set; }
|
||||
|
||||
[Serialize(10.0f, true), Editable]
|
||||
public float WaveLength { get; set; }
|
||||
|
||||
[Serialize(true, true), Editable(ToolTip = "Should the character face towards the direction it's heading.")]
|
||||
public bool RotateTowardsMovement { get; set; }
|
||||
|
||||
[Serialize(25.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "How much torque is used to rotate the torso to the correct orientation.")]
|
||||
public float TorsoTorque { get; set; }
|
||||
|
||||
[Serialize(25.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "How much torque is used to rotate the head to the correct orientation.")]
|
||||
public float HeadTorque { get; set; }
|
||||
|
||||
[Serialize(50.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "How much torque is used to rotate the tail to the correct orientation.")]
|
||||
public float TailTorque { get; set; }
|
||||
|
||||
[Serialize(25.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500, ToolTip = "How much torque is used to rotate the feet to the correct orientation.")]
|
||||
public float FootTorque { get; set; }
|
||||
|
||||
[Serialize(null, true), Editable]
|
||||
public string FootAngles
|
||||
{
|
||||
get => ParseFootAngles(FootAnglesInRadians);
|
||||
set => SetFootAngles(FootAnglesInRadians, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Key = limb id, value = angle in radians
|
||||
/// </summary>
|
||||
public Dictionary<int, float> FootAnglesInRadians { get; set; } = new Dictionary<int, float>();
|
||||
|
||||
/// <summary>
|
||||
/// In degrees.
|
||||
/// </summary>
|
||||
[Serialize(float.NaN, true), Editable(-360f, 360f)]
|
||||
public float TailAngle
|
||||
{
|
||||
get => float.IsNaN(TailAngleInRadians) ? float.NaN : MathHelper.ToDegrees(TailAngleInRadians);
|
||||
set
|
||||
{
|
||||
if (!float.IsNaN(value))
|
||||
{
|
||||
TailAngleInRadians = MathHelper.ToRadians(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
public float TailAngleInRadians { get; private set; } = float.NaN;
|
||||
}
|
||||
|
||||
interface IFishAnimation
|
||||
{
|
||||
bool Flip { get; set; }
|
||||
string FootAngles { get; set; }
|
||||
Dictionary<int, float> FootAnglesInRadians { get; set; }
|
||||
float TailAngle { get; set; }
|
||||
float TailAngleInRadians { get; }
|
||||
float HeadTorque { get; set; }
|
||||
float TorsoTorque { get; set; }
|
||||
float TailTorque { get; set; }
|
||||
float FootTorque { get; set; }
|
||||
}
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class HumanWalkParams : HumanGroundedParams
|
||||
{
|
||||
public static HumanWalkParams GetDefaultAnimParams(Character character) => GetDefaultAnimParams<HumanWalkParams>(character.SpeciesName, AnimationType.Walk);
|
||||
public static HumanWalkParams GetAnimParams(Character character, string fileName = null)
|
||||
{
|
||||
return GetAnimParams<HumanWalkParams>(character.SpeciesName, AnimationType.Walk, fileName);
|
||||
}
|
||||
|
||||
public override void CreateSnapshot() => CreateSnapshot<HumanWalkParams>();
|
||||
}
|
||||
|
||||
class HumanRunParams : HumanGroundedParams
|
||||
{
|
||||
public static HumanRunParams GetDefaultAnimParams(Character character) => GetDefaultAnimParams<HumanRunParams>(character.SpeciesName, AnimationType.Run);
|
||||
public static HumanRunParams GetAnimParams(Character character, string fileName = null)
|
||||
{
|
||||
return GetAnimParams<HumanRunParams>(character.SpeciesName, AnimationType.Run, fileName);
|
||||
}
|
||||
|
||||
public override void CreateSnapshot() => CreateSnapshot<HumanRunParams>();
|
||||
}
|
||||
|
||||
class HumanSwimFastParams: HumanSwimParams
|
||||
{
|
||||
public static HumanSwimFastParams GetDefaultAnimParams(Character character) => GetDefaultAnimParams<HumanSwimFastParams>(character.SpeciesName, AnimationType.SwimFast);
|
||||
public static HumanSwimFastParams GetAnimParams(Character character, string fileName = null)
|
||||
{
|
||||
return GetAnimParams<HumanSwimFastParams>(character.SpeciesName, AnimationType.SwimFast, fileName);
|
||||
}
|
||||
|
||||
|
||||
public override void CreateSnapshot() => CreateSnapshot<HumanSwimFastParams>();
|
||||
}
|
||||
|
||||
class HumanSwimSlowParams : HumanSwimParams
|
||||
{
|
||||
public static HumanSwimSlowParams GetDefaultAnimParams(Character character) => GetDefaultAnimParams<HumanSwimSlowParams>(character.SpeciesName, AnimationType.SwimSlow);
|
||||
public static HumanSwimSlowParams GetAnimParams(Character character, string fileName = null)
|
||||
{
|
||||
return GetAnimParams<HumanSwimSlowParams>(character.SpeciesName, AnimationType.SwimSlow, fileName);
|
||||
}
|
||||
|
||||
public override void CreateSnapshot() => CreateSnapshot<HumanSwimSlowParams>();
|
||||
}
|
||||
|
||||
abstract class HumanSwimParams : SwimParams, IHumanAnimation
|
||||
{
|
||||
[Serialize(0.5f, true), Editable(DecimalCount = 2)]
|
||||
public float LegMoveAmount { get; set; }
|
||||
|
||||
[Serialize(5.0f, true), Editable]
|
||||
public float LegCycleLength { get; set; }
|
||||
|
||||
[Serialize("0.5, 0.1", true), Editable(DecimalCount = 2)]
|
||||
public Vector2 HandMoveAmount { get; set; }
|
||||
|
||||
[Serialize(0.5f, true), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
|
||||
public float HandMoveStrength { get; set; }
|
||||
|
||||
[Serialize(5.0f, true), Editable]
|
||||
public float HandCycleSpeed { get; set; }
|
||||
|
||||
[Serialize("0.0, 0.0", true), Editable(DecimalCount = 2)]
|
||||
public Vector2 HandMoveOffset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// In degrees.
|
||||
/// </summary>
|
||||
[Serialize(0.0f, true), Editable(-360f, 360f)]
|
||||
public float FootAngle
|
||||
{
|
||||
get => MathHelper.ToDegrees(FootAngleInRadians);
|
||||
set
|
||||
{
|
||||
FootAngleInRadians = MathHelper.ToRadians(value);
|
||||
}
|
||||
}
|
||||
public float FootAngleInRadians { get; private set; }
|
||||
|
||||
[Serialize(25.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 100, ToolTip = "How much torque is used to rotate the feet to the correct orientation.")]
|
||||
public float FootRotateStrength { get; set; }
|
||||
}
|
||||
|
||||
abstract class HumanGroundedParams : GroundedMovementParams, IHumanAnimation
|
||||
{
|
||||
[Serialize(0.3f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1, DecimalCount = 2, ToolTip = "How much force is used to force the character upright.")]
|
||||
public float GetUpForce { get; set; }
|
||||
|
||||
// -- TODO: use a separate clip for crawling -> replace these when implemented.
|
||||
|
||||
[Serialize(0.65f, true), Editable(MinValueFloat = 0, MaxValueFloat = 5, DecimalCount = 2, ToolTip = "Height of the torso when crouching.")]
|
||||
public float CrouchingTorsoPos { get; set; }
|
||||
|
||||
[Serialize(0.65f, true), Editable(MinValueFloat = 0, MaxValueFloat = 5, DecimalCount = 2, ToolTip = "Height of the head when crouching.")]
|
||||
public float CrouchingHeadPos { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// In degrees
|
||||
/// </summary>
|
||||
[Serialize(-10f, true), Editable(MinValueFloat = -360, MaxValueFloat = 360, ToolTip = "Angle of the torso when crouching.")]
|
||||
public float CrouchingTorsoAngle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// In degrees
|
||||
/// </summary>
|
||||
[Serialize(-10f, true), Editable(MinValueFloat = -360, MaxValueFloat = 360, ToolTip = "Angle of the head when crouching.")]
|
||||
public float CrouchingHeadAngle { get; set; }
|
||||
|
||||
// --
|
||||
|
||||
[Serialize(0.25f, true), Editable(DecimalCount = 2, ToolTip = "How much the character's head leans forwards when moving.")]
|
||||
public float HeadLeanAmount { get; set; }
|
||||
|
||||
[Serialize(0.25f, true), Editable(DecimalCount = 2, ToolTip = "How much the character's torso leans forwards when moving.")]
|
||||
public float TorsoLeanAmount { get; set; }
|
||||
|
||||
[Serialize(15.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 100, ToolTip = "How much force is used to move the feet to the correct position.")]
|
||||
public float FootMoveStrength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// In degrees.
|
||||
/// </summary>
|
||||
[Serialize(0.0f, true), Editable(-360f, 360f)]
|
||||
public float FootAngle
|
||||
{
|
||||
get => MathHelper.ToDegrees(FootAngleInRadians);
|
||||
set
|
||||
{
|
||||
FootAngleInRadians = MathHelper.ToRadians(value);
|
||||
}
|
||||
}
|
||||
public float FootAngleInRadians { get; private set; }
|
||||
|
||||
[Serialize(20.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 100, ToolTip = "How much torque is used to rotate the feet to the correct orientation.")]
|
||||
public float FootRotateStrength { get; set; }
|
||||
|
||||
[Serialize("0.0, 0.0", true), Editable(DecimalCount = 2, ToolTip = "Added to the calculated foot positions, e.g. a value of {-1.0, 0.0f} would make the character \"drag\" their feet one unit behind them.")]
|
||||
public Vector2 FootMoveOffset { get; set; }
|
||||
|
||||
[Serialize("0.0, 0.0", true), Editable(DecimalCount = 2, ToolTip = "Added to the calculated foot positions, e.g. a value of {-1.0, 0.0f} would make the character \"drag\" their feet one unit behind them.")]
|
||||
public Vector2 CrouchingFootMoveOffset { get; set; }
|
||||
|
||||
[Serialize(10.0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 100, ToolTip = "How much torque is used to bend the characters legs when taking a step.")]
|
||||
public float LegBendTorque { get; set; }
|
||||
|
||||
[Serialize("0.4, 0.15", true), Editable(DecimalCount = 2, ToolTip = "How much the hands move along each axis.")]
|
||||
public Vector2 HandMoveAmount { get; set; }
|
||||
|
||||
[Serialize("-0.15, 0.0", true), Editable(DecimalCount = 2, ToolTip = "Added to the calculated hand positions, e.g. a value of {-1.0, 0.0f} would make the character \"drag\" their hands one unit behind them.")]
|
||||
public Vector2 HandMoveOffset { get; set; }
|
||||
|
||||
[Serialize(0.7f, true), Editable(MinValueFloat = 0, MaxValueFloat = 2, DecimalCount = 2, ToolTip = "How much force is used to move the hands.")]
|
||||
public float HandMoveStrength { get; set; }
|
||||
|
||||
[Serialize(-1.0f, true), Editable(DecimalCount = 2, ToolTip = "The position of the hands is clamped below this (relative to the position of the character's torso).")]
|
||||
public float HandClampY { get; set; }
|
||||
}
|
||||
|
||||
public interface IHumanAnimation
|
||||
{
|
||||
float FootAngle { get; set; }
|
||||
float FootAngleInRadians { get; }
|
||||
float FootRotateStrength { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
abstract class EditableParams : ISerializableEntity
|
||||
{
|
||||
public bool IsLoaded { get; protected set; }
|
||||
public string Name { get; private set; }
|
||||
public string FileName { get; private set; }
|
||||
public string Folder { get; private set; }
|
||||
public string FullPath { get; private set; }
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; protected set; }
|
||||
|
||||
protected XDocument doc;
|
||||
public XDocument Doc
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsLoaded)
|
||||
{
|
||||
DebugConsole.ThrowError("[Params] Not loaded!");
|
||||
return new XDocument();
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
protected set
|
||||
{
|
||||
doc = value;
|
||||
}
|
||||
}
|
||||
|
||||
public XElement MainElement => doc.Root;
|
||||
public XElement OriginalElement { get; protected set; }
|
||||
|
||||
protected virtual bool Deserialize(XElement element = null)
|
||||
{
|
||||
element = element ?? MainElement;
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
return SerializableProperties != null;
|
||||
}
|
||||
|
||||
protected virtual bool Serialize(XElement element = null)
|
||||
{
|
||||
element = element ?? MainElement;
|
||||
SerializableProperty.SerializeProperties(this, element, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual bool Load(string file)
|
||||
{
|
||||
UpdatePath(file);
|
||||
doc = XMLExtensions.TryLoadXml(FullPath);
|
||||
if (doc == null) { return false; }
|
||||
IsLoaded = Deserialize(MainElement);
|
||||
OriginalElement = new XElement(MainElement);
|
||||
return IsLoaded;
|
||||
}
|
||||
|
||||
protected virtual void UpdatePath(string fullPath)
|
||||
{
|
||||
FullPath = fullPath;
|
||||
Name = Path.GetFileNameWithoutExtension(FullPath);
|
||||
FileName = Path.GetFileName(FullPath);
|
||||
Folder = Path.GetDirectoryName(FullPath);
|
||||
}
|
||||
|
||||
public virtual bool Save(string fileNameWithoutExtension = null, XmlWriterSettings settings = null)
|
||||
{
|
||||
if (!Directory.Exists(Folder))
|
||||
{
|
||||
Directory.CreateDirectory(Folder);
|
||||
}
|
||||
OriginalElement = MainElement;
|
||||
Serialize();
|
||||
if (settings == null)
|
||||
{
|
||||
settings = new XmlWriterSettings
|
||||
{
|
||||
Indent = true,
|
||||
OmitXmlDeclaration = true,
|
||||
NewLineOnAttributes = true
|
||||
};
|
||||
}
|
||||
if (fileNameWithoutExtension != null)
|
||||
{
|
||||
UpdatePath(Path.Combine(Folder, $"{fileNameWithoutExtension}.xml"));
|
||||
}
|
||||
using (var writer = XmlWriter.Create(FullPath, settings))
|
||||
{
|
||||
Doc.WriteTo(writer);
|
||||
writer.Flush();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool Reset(bool forceReload = false)
|
||||
{
|
||||
if (forceReload)
|
||||
{
|
||||
return Load(FullPath);
|
||||
}
|
||||
return Deserialize(OriginalElement);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
public SerializableEntityEditor SerializableEntityEditor { get; protected set; }
|
||||
public virtual void AddToEditor(ParamsEditor editor)
|
||||
{
|
||||
if (!IsLoaded)
|
||||
{
|
||||
DebugConsole.ThrowError("[Params] Not loaded!");
|
||||
return;
|
||||
}
|
||||
SerializableEntityEditor = new SerializableEntityEditor(editor.EditorBox.Content.RectTransform, this, false, true);
|
||||
}
|
||||
#endif
|
||||
|
||||
#region Memento
|
||||
public readonly Memento<EditableParams> memento = new Memento<EditableParams>();
|
||||
public abstract void CreateSnapshot();
|
||||
public abstract void Undo();
|
||||
public abstract void Redo();
|
||||
public void ClearHistory() => memento.Clear();
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+649
@@ -0,0 +1,649 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Xml;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class HumanRagdollParams : RagdollParams
|
||||
{
|
||||
public static HumanRagdollParams GetRagdollParams(string speciesName, string fileName = null) => GetRagdollParams<HumanRagdollParams>(speciesName, fileName);
|
||||
public static HumanRagdollParams GetDefaultRagdollParams(string speciesName) => GetDefaultRagdollParams<HumanRagdollParams>(speciesName);
|
||||
}
|
||||
|
||||
class FishRagdollParams : RagdollParams
|
||||
{
|
||||
public static FishRagdollParams GetDefaultRagdollParams(string speciesName) => GetDefaultRagdollParams<FishRagdollParams>(speciesName);
|
||||
}
|
||||
|
||||
class RagdollParams : EditableParams
|
||||
{
|
||||
public const float MIN_SCALE = 0.1f;
|
||||
public const float MAX_SCALE = 2;
|
||||
|
||||
public string SpeciesName { get; private set; }
|
||||
|
||||
[Serialize(0f, true), Editable(-360, 360, ToolTip = "Rotation offset (in degrees) used for animations and widgets. If the sprites in the sheet are in different orientations, use the orientation of the torso for the final version of your character (while editing the character in the editor, you can change the orientation freely).")]
|
||||
public float SpritesheetOrientation { get; set; }
|
||||
|
||||
[Serialize(1.0f, true), Editable(MIN_SCALE, MAX_SCALE, DecimalCount = 3)]
|
||||
public float LimbScale { get; set; }
|
||||
|
||||
[Serialize(1.0f, true), Editable(MIN_SCALE, MAX_SCALE, DecimalCount = 3)]
|
||||
public float JointScale { get; set; }
|
||||
|
||||
[Serialize(1f, true), Editable(DecimalCount = 2)]
|
||||
public float TextureScale { get; set; }
|
||||
|
||||
[Serialize(45f, true), Editable(0f, 1000f)]
|
||||
public float ColliderHeightFromFloor { get; set; }
|
||||
|
||||
[Serialize(50f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
|
||||
public float ImpactTolerance { get; set; }
|
||||
|
||||
[Serialize(true, true), Editable]
|
||||
public bool CanEnterSubmarine { get; set; }
|
||||
|
||||
[Serialize(true, true), Editable]
|
||||
public bool Draggable { get; set; }
|
||||
|
||||
private static Dictionary<string, Dictionary<string, RagdollParams>> allRagdolls = new Dictionary<string, Dictionary<string, RagdollParams>>();
|
||||
|
||||
public List<ColliderParams> ColliderParams { get; private set; } = new List<ColliderParams>();
|
||||
public List<LimbParams> Limbs { get; private set; } = new List<LimbParams>();
|
||||
public List<JointParams> Joints { get; private set; } = new List<JointParams>();
|
||||
|
||||
protected IEnumerable<RagdollSubParams> GetAllSubParams() =>
|
||||
ColliderParams.Select(c => c as RagdollSubParams)
|
||||
.Concat(Limbs.Select(j => j as RagdollSubParams)
|
||||
.Concat(Joints.Select(j => j as RagdollSubParams)));
|
||||
|
||||
public static string GetDefaultFileName(string speciesName) => $"{speciesName.CapitaliseFirstInvariant()}DefaultRagdoll";
|
||||
public static string GetDefaultFolder(string speciesName) => $"Content/Characters/{speciesName.CapitaliseFirstInvariant()}/Ragdolls/";
|
||||
public static string GetDefaultFile(string speciesName) => $"{GetDefaultFolder(speciesName)}{GetDefaultFileName(speciesName)}.xml";
|
||||
|
||||
private static readonly object[] dummyParams = new object[]
|
||||
{
|
||||
new XAttribute("type", "Dummy"),
|
||||
new XElement("collider", new XAttribute("radius", 1)),
|
||||
new XElement("limb",
|
||||
new XAttribute("id", 0),
|
||||
new XAttribute("type", LimbType.Head.ToString()),
|
||||
new XAttribute("width", 1),
|
||||
new XAttribute("height", 1),
|
||||
new XElement("sprite",
|
||||
new XAttribute("sourcerect", $"0, 0, 1, 1")))
|
||||
};
|
||||
|
||||
protected static string GetFolder(string speciesName)
|
||||
{
|
||||
var folder = XMLExtensions.TryLoadXml(Character.GetConfigFile(speciesName))?.Root?.Element("ragdolls")?.GetAttributeString("folder", string.Empty);
|
||||
if (string.IsNullOrEmpty(folder) || folder.ToLowerInvariant() == "default")
|
||||
{
|
||||
folder = GetDefaultFolder(speciesName);
|
||||
}
|
||||
return folder;
|
||||
}
|
||||
|
||||
public static T GetDefaultRagdollParams<T>(string speciesName) where T : RagdollParams, new() => GetRagdollParams<T>(speciesName, GetDefaultFileName(speciesName));
|
||||
|
||||
/// <summary>
|
||||
/// If the file name is left null, default file is selected. If fails, will select the default file. Note: Use the filename without the extensions, don't use the full path!
|
||||
/// If a custom folder is used, it's defined in the character info file.
|
||||
/// </summary>
|
||||
public static T GetRagdollParams<T>(string speciesName, string fileName = null) where T : RagdollParams, new()
|
||||
{
|
||||
if (!allRagdolls.TryGetValue(speciesName, out Dictionary<string, RagdollParams> ragdolls))
|
||||
{
|
||||
ragdolls = new Dictionary<string, RagdollParams>();
|
||||
allRagdolls.Add(speciesName, ragdolls);
|
||||
}
|
||||
if (string.IsNullOrEmpty(fileName) || !ragdolls.TryGetValue(fileName, out RagdollParams ragdoll))
|
||||
{
|
||||
string selectedFile = null;
|
||||
string folder = GetFolder(speciesName);
|
||||
if (Directory.Exists(folder))
|
||||
{
|
||||
var files = Directory.GetFiles(folder);
|
||||
if (files.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"[RagdollParams] Could not find any ragdoll files from the folder: {folder}. Using the default ragdoll.");
|
||||
selectedFile = GetDefaultFile(speciesName);
|
||||
}
|
||||
else if (string.IsNullOrEmpty(fileName))
|
||||
{
|
||||
// Files found, but none specified
|
||||
selectedFile = GetDefaultFile(speciesName);
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedFile = files.FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).ToLowerInvariant() == fileName.ToLowerInvariant());
|
||||
if (selectedFile == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"[RagdollParams] Could not find a ragdoll file that matches the name {fileName}. Using the default ragdoll.");
|
||||
selectedFile = GetDefaultFile(speciesName);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"[RagdollParams] Invalid directory: {folder}. Using the default ragdoll.");
|
||||
selectedFile = GetDefaultFile(speciesName);
|
||||
}
|
||||
if (selectedFile == null)
|
||||
{
|
||||
throw new Exception("[RagdollParams] Selected file null!");
|
||||
}
|
||||
DebugConsole.Log($"[RagdollParams] Loading ragdoll from {selectedFile}.");
|
||||
T r = new T();
|
||||
if (r.Load(selectedFile, speciesName))
|
||||
{
|
||||
if (!ragdolls.ContainsKey(r.Name))
|
||||
{
|
||||
ragdolls.Add(r.Name, r);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"[RagdollParams] Failed to load ragdoll {r} at {selectedFile} for the character {speciesName}. Creating a dummy file.");
|
||||
var defaultFile = GetDefaultFile(speciesName);
|
||||
if (File.Exists(defaultFile))
|
||||
{
|
||||
DebugConsole.ThrowError($"[RagdollParams] Renaming the invalid file as {selectedFile}.invalid");
|
||||
// Rename the old file so that it's not lost.
|
||||
File.Move(defaultFile, defaultFile + ".invalid");
|
||||
}
|
||||
return CreateDefault<T>(defaultFile, speciesName, dummyParams);
|
||||
}
|
||||
}
|
||||
return (T)ragdoll;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a default ragdoll for the species using a predefined configuration.
|
||||
/// Note: Use only to create ragdolls for new characters, because this overrides the old ragdoll!
|
||||
/// </summary>
|
||||
public static T CreateDefault<T>(string fullPath, string speciesName, params object[] ragdollConfig) where T : RagdollParams, new()
|
||||
{
|
||||
// Remove the old ragdolls, if found.
|
||||
if (allRagdolls.ContainsKey(speciesName))
|
||||
{
|
||||
DebugConsole.NewMessage($"[RagdollParams] Removing the old ragdolls from {speciesName}.", Color.Red);
|
||||
allRagdolls.Remove(speciesName);
|
||||
}
|
||||
var ragdolls = new Dictionary<string, RagdollParams>();
|
||||
allRagdolls.Add(speciesName, ragdolls);
|
||||
var instance = new T();
|
||||
XElement ragdollElement = new XElement("Ragdoll", ragdollConfig);
|
||||
instance.doc = new XDocument(ragdollElement);
|
||||
instance.UpdatePath(fullPath);
|
||||
instance.IsLoaded = instance.Deserialize(ragdollElement);
|
||||
instance.Save();
|
||||
instance.Load(fullPath, speciesName);
|
||||
ragdolls.Add(instance.Name, instance);
|
||||
DebugConsole.NewMessage("[RagdollParams] New default ragdoll params successfully created at " + fullPath, Color.NavajoWhite);
|
||||
return instance as T;
|
||||
}
|
||||
|
||||
protected override void UpdatePath(string fullPath)
|
||||
{
|
||||
if (SpeciesName == null)
|
||||
{
|
||||
base.UpdatePath(fullPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update the key by removing and re-adding the ragdoll.
|
||||
if (allRagdolls.TryGetValue(SpeciesName, out Dictionary<string, RagdollParams> ragdolls))
|
||||
{
|
||||
ragdolls.Remove(Name);
|
||||
}
|
||||
base.UpdatePath(fullPath);
|
||||
if (ragdolls != null)
|
||||
{
|
||||
if (!ragdolls.ContainsKey(Name))
|
||||
{
|
||||
ragdolls.Add(Name, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Save(string fileNameWithoutExtension = null)
|
||||
{
|
||||
OriginalElement = MainElement;
|
||||
GetAllSubParams().ForEach(p => p.SetCurrentElementAsOriginalElement());
|
||||
Serialize();
|
||||
return base.Save(fileNameWithoutExtension, new XmlWriterSettings
|
||||
{
|
||||
Indent = true,
|
||||
OmitXmlDeclaration = true,
|
||||
NewLineOnAttributes = false
|
||||
});
|
||||
}
|
||||
|
||||
protected bool Load(string file, string speciesName)
|
||||
{
|
||||
if (Load(file))
|
||||
{
|
||||
SpeciesName = speciesName;
|
||||
CreateColliders();
|
||||
CreateLimbs();
|
||||
CreateJoints();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool Reset(bool forceReload = false)
|
||||
{
|
||||
if (forceReload)
|
||||
{
|
||||
return Load(FullPath, SpeciesName);
|
||||
}
|
||||
Deserialize(OriginalElement, recursive: true);
|
||||
GetAllSubParams().ForEach(sp => sp.Reset());
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void CreateColliders()
|
||||
{
|
||||
ColliderParams.Clear();
|
||||
for (int i = 0; i < MainElement.Elements("collider").Count(); i++)
|
||||
{
|
||||
var element = MainElement.Elements("collider").ElementAt(i);
|
||||
string name = i > 0 ? "Secondary Collider" : "Main Collider";
|
||||
ColliderParams.Add(new ColliderParams(element, this, name));
|
||||
}
|
||||
}
|
||||
|
||||
protected void CreateLimbs()
|
||||
{
|
||||
Limbs.Clear();
|
||||
foreach (var element in MainElement.Elements("limb"))
|
||||
{
|
||||
Limbs.Add(new LimbParams(element, this));
|
||||
}
|
||||
Limbs = Limbs.OrderBy(l => l.ID).ToList();
|
||||
}
|
||||
|
||||
protected void CreateJoints()
|
||||
{
|
||||
Joints.Clear();
|
||||
foreach (var element in MainElement.Elements("joint"))
|
||||
{
|
||||
Joints.Add(new JointParams(element, this));
|
||||
}
|
||||
}
|
||||
|
||||
protected bool Deserialize(XElement element = null, bool recursive = true)
|
||||
{
|
||||
if (base.Deserialize(element))
|
||||
{
|
||||
if (recursive)
|
||||
{
|
||||
GetAllSubParams().ForEach(p => p.Deserialize());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected bool Serialize(XElement element = null, bool recursive = true)
|
||||
{
|
||||
if (base.Serialize(element))
|
||||
{
|
||||
if (recursive)
|
||||
{
|
||||
GetAllSubParams().ForEach(p => p.Serialize());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#region Memento
|
||||
public override void CreateSnapshot()
|
||||
{
|
||||
Serialize();
|
||||
var copy = new RagdollParams
|
||||
{
|
||||
IsLoaded = true,
|
||||
doc = new XDocument(doc)
|
||||
};
|
||||
copy.CreateColliders();
|
||||
copy.CreateLimbs();
|
||||
copy.CreateJoints();
|
||||
copy.Deserialize();
|
||||
copy.Serialize();
|
||||
memento.Store(copy);
|
||||
}
|
||||
public override void Undo() => RevertTo(memento.Undo() as RagdollParams);
|
||||
public override void Redo() => RevertTo(memento.Redo() as RagdollParams);
|
||||
|
||||
private void RevertTo(RagdollParams source)
|
||||
{
|
||||
Deserialize(source.MainElement, recursive: false);
|
||||
var sourceSubParams = source.GetAllSubParams().ToList();
|
||||
var subParams = GetAllSubParams().ToList();
|
||||
for (int i = 0; i < subParams.Count; i++)
|
||||
{
|
||||
subParams[i].Deserialize(sourceSubParams[i].Element, recursive: false);
|
||||
var subSubParams = subParams[i].SubParams;
|
||||
for (int j = 0; j < subSubParams.Count; j++)
|
||||
{
|
||||
subSubParams[j].Deserialize(sourceSubParams[i].SubParams[j].Element, recursive: false);
|
||||
// Since we cannot use recursion here, we have to go deeper manually, if necessary.
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#if CLIENT
|
||||
public override void AddToEditor(ParamsEditor editor)
|
||||
{
|
||||
base.AddToEditor(editor);
|
||||
var subParams = GetAllSubParams();
|
||||
foreach (var subParam in subParams)
|
||||
{
|
||||
subParam.AddToEditor(editor);
|
||||
//TODO: divider sprite
|
||||
new GUIFrame(new RectTransform(new Point(editor.EditorBox.Rect.Width, 10), editor.EditorBox.Content.RectTransform),
|
||||
style: "ConnectionPanelWire");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
class JointParams : RagdollSubParams
|
||||
{
|
||||
public JointParams(XElement element, RagdollParams ragdoll) : base(element, ragdoll) { }
|
||||
|
||||
private string name;
|
||||
[Serialize("", true), Editable]
|
||||
public override string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
name = GenerateName();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
set
|
||||
{
|
||||
name = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override string GenerateName() => $"Joint {Limb1} - {Limb2}";
|
||||
|
||||
[Serialize(-1, true), Editable]
|
||||
public int Limb1 { get; set; }
|
||||
|
||||
[Serialize(-1, true), Editable]
|
||||
public int Limb2 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should be converted to sim units.
|
||||
/// </summary>
|
||||
[Serialize("1.0, 1.0", true), Editable]
|
||||
public Vector2 Limb1Anchor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should be converted to sim units.
|
||||
/// </summary>
|
||||
[Serialize("1.0, 1.0", true), Editable]
|
||||
public Vector2 Limb2Anchor { get; set; }
|
||||
|
||||
[Serialize(true, true), Editable]
|
||||
public bool CanBeSevered { get; set; }
|
||||
|
||||
[Serialize(true, true), Editable]
|
||||
public bool LimitEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// In degrees.
|
||||
/// </summary>
|
||||
[Serialize(0f, true), Editable]
|
||||
public float UpperLimit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// In degrees.
|
||||
/// </summary>
|
||||
[Serialize(0f, true), Editable]
|
||||
public float LowerLimit { get; set; }
|
||||
|
||||
[Serialize(0.25f, true), Editable]
|
||||
public float Stiffness { get; set; }
|
||||
}
|
||||
|
||||
class LimbParams : RagdollSubParams
|
||||
{
|
||||
public LimbParams(XElement element, RagdollParams ragdoll) : base(element, ragdoll)
|
||||
{
|
||||
var spriteElement = element.Element("sprite");
|
||||
if (spriteElement != null)
|
||||
{
|
||||
normalSpriteParams = new SpriteParams(spriteElement, ragdoll);
|
||||
SubParams.Add(normalSpriteParams);
|
||||
}
|
||||
var damagedElement = element.Element("damagedsprite");
|
||||
if (damagedElement != null)
|
||||
{
|
||||
damagedSpriteParams = new SpriteParams(damagedElement, ragdoll);
|
||||
// Hide the damaged sprite params in the editor for now.
|
||||
//SubParams.Add(damagedSpriteParams);
|
||||
}
|
||||
var deformElement = element.Element("deformablesprite");
|
||||
if (deformElement != null)
|
||||
{
|
||||
deformSpriteParams = new SpriteParams(deformElement, ragdoll);
|
||||
SubParams.Add(deformSpriteParams);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly SpriteParams normalSpriteParams;
|
||||
public readonly SpriteParams damagedSpriteParams;
|
||||
public readonly SpriteParams deformSpriteParams;
|
||||
|
||||
private string name;
|
||||
[Serialize("", true), Editable]
|
||||
public override string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
name = GenerateName();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
set
|
||||
{
|
||||
name = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override string GenerateName() => $"Limb {ID}";
|
||||
|
||||
/// <summary>
|
||||
/// Note that editing this in-game doesn't currently have any effect (unless the ragdoll is recreated). It should be visible, but readonly in the editor.
|
||||
/// </summary>
|
||||
[Serialize(-1, true), Editable]
|
||||
public int ID { get; set; }
|
||||
|
||||
[Serialize(LimbType.None, true), Editable]
|
||||
public LimbType Type { get; set; }
|
||||
|
||||
[Serialize(true, true), Editable]
|
||||
public bool Flip { get; set; }
|
||||
|
||||
[Serialize(0, true), Editable]
|
||||
public int HealthIndex { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable(ToolTip = "Higher values make AI characters prefer attacking this limb.")]
|
||||
public float AttackPriority { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
|
||||
public float SteerForce { get; set; }
|
||||
|
||||
[Serialize("0, 0", true), Editable(ToolTip = "Only applicable if this limb is a foot. Determines the \"neutral position\" of the foot relative to a joint determined by the \"RefJoint\" parameter. For example, a value of {-100, 0} would mean that the foot is positioned on the floor, 100 units behind the reference joint.")]
|
||||
public Vector2 StepOffset { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
|
||||
public float Radius { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
|
||||
public float Height { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
|
||||
public float Width { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 10000)]
|
||||
public float Mass { get; set; }
|
||||
|
||||
[Serialize(10f, true), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
|
||||
public float Density { get; set; }
|
||||
|
||||
[Serialize("0, 0", true), Editable(ToolTip = "The position which is used to lead the IK chain to the IK goal. Only applicable if the limb is hand or foot.")]
|
||||
public Vector2 PullPos { get; set; }
|
||||
|
||||
[Serialize(-1, true), Editable(ToolTip = "Only applicable if this limb is a foot. Determines which joint is used as the \"neutral x-position\" for the foot movement. For example in the case of a humanoid-shaped characters this would usually be the waist. The position can be offset using the StepOffset parameter.")]
|
||||
public int RefJoint { get; set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool IgnoreCollisions { get; set; }
|
||||
|
||||
[Serialize("", true), Editable]
|
||||
public string Notes { get; set; }
|
||||
|
||||
// Non-editable ->
|
||||
[Serialize(0.3f, true)]
|
||||
public float Friction { get; set; }
|
||||
|
||||
[Serialize(0.05f, true)]
|
||||
public float Restitution { get; set; }
|
||||
}
|
||||
|
||||
class SpriteParams : RagdollSubParams
|
||||
{
|
||||
public SpriteParams(XElement element, RagdollParams ragdoll) : base(element, ragdoll) { }
|
||||
|
||||
[Serialize("0, 0, 0, 0", true), Editable]
|
||||
public Rectangle SourceRect { get; set; }
|
||||
|
||||
[Serialize("0.5, 0.5", true), Editable(DecimalCount = 2, ToolTip = "Relative to the collider.")]
|
||||
public Vector2 Origin { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable(DecimalCount = 3)]
|
||||
public float Depth { get; set; }
|
||||
|
||||
[Serialize("", true)]
|
||||
public string Texture { get; set; }
|
||||
}
|
||||
|
||||
class ColliderParams : RagdollSubParams
|
||||
{
|
||||
public ColliderParams(XElement element, RagdollParams ragdoll, string name = null) : base(element, ragdoll)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
private string name;
|
||||
[Serialize("", true), Editable]
|
||||
public override string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
name = GenerateName();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
set
|
||||
{
|
||||
name = value;
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
|
||||
public float Radius { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
|
||||
public float Height { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
|
||||
public float Width { get; set; }
|
||||
}
|
||||
|
||||
abstract class RagdollSubParams : ISerializableEntity
|
||||
{
|
||||
public virtual string Name { get; set; }
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; private set; }
|
||||
public XElement Element { get; set; }
|
||||
public XElement OriginalElement { get; protected set; }
|
||||
public List<RagdollSubParams> SubParams { get; set; } = new List<RagdollSubParams>();
|
||||
public RagdollParams Ragdoll { get; private set; }
|
||||
|
||||
public virtual string GenerateName() => Element.Name.ToString();
|
||||
|
||||
public RagdollSubParams(XElement element, RagdollParams ragdoll)
|
||||
{
|
||||
Element = element;
|
||||
OriginalElement = new XElement(element);
|
||||
Ragdoll = ragdoll;
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
}
|
||||
|
||||
public virtual bool Deserialize(XElement element = null, bool recursive = true)
|
||||
{
|
||||
element = element ?? Element;
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
if (recursive)
|
||||
{
|
||||
SubParams.ForEach(sp => sp.Deserialize());
|
||||
}
|
||||
return SerializableProperties != null;
|
||||
}
|
||||
|
||||
public virtual bool Serialize(XElement element = null, bool recursive = true)
|
||||
{
|
||||
element = element ?? Element;
|
||||
SerializableProperty.SerializeProperties(this, element, true);
|
||||
if (recursive)
|
||||
{
|
||||
SubParams.ForEach(sp => sp.Serialize());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetCurrentElementAsOriginalElement()
|
||||
{
|
||||
OriginalElement = Element;
|
||||
SubParams.ForEach(sp => sp.SetCurrentElementAsOriginalElement());
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Deserialize(OriginalElement, false);
|
||||
SubParams.ForEach(sp => sp.Reset());
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
public SerializableEntityEditor SerializableEntityEditor { get; protected set; }
|
||||
public virtual void AddToEditor(ParamsEditor editor)
|
||||
{
|
||||
SerializableEntityEditor = new SerializableEntityEditor(editor.EditorBox.Content.RectTransform, this, false, true);
|
||||
SubParams.ForEach(sp => sp.AddToEditor(editor));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user