Unstable 0.1500.4.0 (Shrek edition)

This commit is contained in:
Markus Isberg
2021-09-23 21:29:31 +09:00
parent 5a6bbcc79e
commit 3043a9a7bc
124 changed files with 3571 additions and 1848 deletions
@@ -464,7 +464,7 @@ namespace Barotrauma
};
break;
case "return":
newObjective = new AIObjectiveReturn(character, this, priorityModifier: priorityModifier);
newObjective = new AIObjectiveReturn(character, orderGiver, this, priorityModifier: priorityModifier);
newObjective.Abandoned += () => DismissSelf(order, option);
newObjective.Completed += () => DismissSelf(order, option);
break;
@@ -408,7 +408,7 @@ namespace Barotrauma
}
bool isCompleted =
AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter) ||
targetCharacter.CharacterHealth.GetAllAfflictions().All(a => a.Strength < a.Prefab.TreatmentThreshold);
targetCharacter.CharacterHealth.GetAllAfflictions().All(a => a.Strength <= a.Prefab.TreatmentThreshold);
if (isCompleted && targetCharacter != character && character.IsOnPlayerTeam)
{
@@ -83,7 +83,7 @@ namespace Barotrauma
if (character.AIController is HumanAIController humanAI)
{
if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target) ||
target.CharacterHealth.GetAllAfflictions().All(a => a.Strength < a.Prefab.TreatmentThreshold))
target.CharacterHealth.GetAllAfflictions().All(a => a.Strength <= a.Prefab.TreatmentThreshold))
{
return false;
}
@@ -12,7 +12,7 @@ namespace Barotrauma
private bool usingEscapeBehavior;
public Submarine ReturnTarget { get; }
public AIObjectiveReturn(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1.0f) : base(character, objectiveManager, priorityModifier)
public AIObjectiveReturn(Character character, Character orderGiver, AIObjectiveManager objectiveManager, float priorityModifier = 1.0f) : base(character, objectiveManager, priorityModifier)
{
ReturnTarget = GetReturnTarget(Submarine.MainSubs) ?? GetReturnTarget(Submarine.Loaded);
if (ReturnTarget == null)
@@ -23,10 +23,12 @@ namespace Barotrauma
Submarine GetReturnTarget(IEnumerable<Submarine> subs)
{
var requiredTeamID = orderGiver?.TeamID ?? character?.TeamID;
Submarine returnTarget = null;
foreach (var sub in subs)
{
if (sub?.TeamID != character.TeamID) { continue; }
if (sub == null) { continue; }
if (sub.TeamID != requiredTeamID) { continue; }
returnTarget = sub;
break;
}
@@ -229,7 +231,7 @@ namespace Barotrauma
protected override void OnAbandon()
{
base.OnAbandon();
SteeringManager.Reset();
SteeringManager?.Reset();
if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder == objectiveManager.CurrentObjective)
{
string msg = TextManager.Get("dialogcannotreturn", returnNull: true);
@@ -24,8 +24,6 @@ namespace Barotrauma
public readonly Vector2 Position;
public readonly int WayPointID;
public bool blocked;
public override string ToString()
{
return $"PathNode {WayPointID}";
@@ -81,6 +79,31 @@ namespace Barotrauma
return nodeList;
}
private bool? blocked;
public bool IsBlocked()
{
if (blocked.HasValue) { return blocked.Value; }
blocked = false;
if (Waypoint.Submarine != null) { return blocked.Value; }
if (Waypoint.Tunnel?.Type != Level.TunnelType.Cave) { return blocked.Value; }
foreach (var w in Level.Loaded.ExtraWalls)
{
if (!(w is DestructibleLevelWall d)) { return blocked.Value; }
if (d.Destroyed) { return blocked.Value; }
if (!d.IsPointInside(Waypoint.Position)) { return blocked.Value; }
blocked = true;
break;
}
return blocked.Value;
}
public void ResetBlocked()
{
blocked = null;
}
}
class PathFinder
@@ -146,7 +169,10 @@ namespace Barotrauma
public SteeringPath FindPath(Vector2 start, Vector2 end, Submarine hostSub = null, string errorMsgStr = null, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisibility = true)
{
UpdateBlockedNodes();
foreach (PathNode node in nodes)
{
node.ResetBlocked();
}
//sort nodes roughly according to distance
sortedNodes.Clear();
@@ -202,11 +228,11 @@ namespace Barotrauma
{
if (startNode == null || node.TempDistance < startNode.TempDistance)
{
if (node.blocked) { continue; }
if (nodeFilter != null && !nodeFilter(node)) { continue; }
if (startNodeFilter != null && !startNodeFilter(node)) { continue; }
// Always check the visibility for the start node
if (!IsWaypointVisible(node, start)) { continue; }
if (node.IsBlocked()) { continue; }
startNode = node;
}
}
@@ -251,11 +277,11 @@ namespace Barotrauma
{
if (endNode == null || node.TempDistance < endNode.TempDistance)
{
if (node.blocked) { continue; }
if (nodeFilter != null && !nodeFilter(node)) { continue; }
if (endNodeFilter != null && !endNodeFilter(node)) { continue; }
// Only check the visibility for the end node when allowed (fix leaks)
if (!IsWaypointVisible(node, end, checkVisibility: checkVisibility)) { continue; }
if (node.IsBlocked()) { continue; }
endNode = node;
}
}
@@ -326,15 +352,13 @@ namespace Barotrauma
float dist = float.MaxValue;
foreach (PathNode node in nodes)
{
if (node.state != 1) { continue; }
if (node.state != 1 || node.F > dist) { continue; }
if (isCharacter && node.Waypoint.isObstructed) { continue; }
if (node.blocked) { continue; }
if (filter != null && !filter(node)) { continue; }
if (node.F < dist)
{
dist = node.F;
currNode = node;
}
if (node.IsBlocked()) { continue; }
dist = node.F;
currNode = node;
}
if (currNode == null || currNode == end) { break; }
@@ -436,25 +460,6 @@ namespace Barotrauma
return path;
}
private void UpdateBlockedNodes()
{
if (!isCharacter) { return; }
foreach (var n in nodes)
{
n.blocked = false;
if (n.Waypoint.Submarine != null) { continue; }
if (n.Waypoint.Tunnel?.Type != Level.TunnelType.Cave) { continue; }
foreach (var w in Level.Loaded.ExtraWalls)
{
if (!(w is DestructibleLevelWall d)) { continue; }
if (d.Destroyed) { continue; }
if (!d.IsPointInside(n.Waypoint.Position)) { continue; }
n.blocked = true;
break;
}
}
}
}
}
@@ -42,6 +42,10 @@ namespace Barotrauma
}
else
{
if (this is HumanoidAnimController humanAnimController && humanAnimController.Crouching)
{
return humanAnimController.HumanCrouchParams;
}
return IsMovingFast ? RunParams : WalkParams;
}
}
@@ -96,7 +100,12 @@ namespace Barotrauma
{
if (CanWalk)
{
return new List<AnimationParams> { WalkParams, RunParams, SwimSlowParams, SwimFastParams };
var anims = new List<AnimationParams> { WalkParams, RunParams, SwimSlowParams, SwimFastParams };
if (this is HumanoidAnimController humanAnimController)
{
anims.Add(humanAnimController.HumanCrouchParams);
}
return anims;
}
else
{
@@ -154,7 +163,7 @@ namespace Barotrauma
public virtual void UpdateUseItem(bool allowMovement, Vector2 handWorldPos) { }
public float GetSpeed(AnimationType type)
public virtual float GetSpeed(AnimationType type)
{
GroundedMovementParams movementParams;
switch (type)
@@ -207,7 +216,14 @@ namespace Barotrauma
}
else
{
animType = AnimationType.Walk;
if (this is HumanoidAnimController humanAnimController && humanAnimController.Crouching)
{
animType = AnimationType.Crouch;
}
else
{
animType = AnimationType.Walk;
}
}
}
return GetSpeed(animType);
@@ -221,6 +237,12 @@ namespace Barotrauma
return WalkParams;
case AnimationType.Run:
return RunParams;
case AnimationType.Crouch:
if (this is HumanoidAnimController humanAnimController)
{
return humanAnimController.HumanCrouchParams;
}
throw new NotImplementedException(type.ToString());
case AnimationType.SwimSlow:
return SwimSlowParams;
case AnimationType.SwimFast:
@@ -97,9 +97,9 @@ namespace Barotrauma
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 FootTorque => CurrentAnimationParams.FootTorque;
public float HeadTorque => CurrentAnimationParams.HeadTorque;
public float TorsoTorque => CurrentAnimationParams.TorsoTorque;
public float TailTorque => CurrentFishAnimation.TailTorque;
public float HeadMoveForce => CurrentGroundedParams.HeadMoveForce;
public float TorsoMoveForce => CurrentGroundedParams.TorsoMoveForce;
@@ -2,7 +2,6 @@
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
using Barotrauma.Networking;
@@ -73,6 +72,20 @@ namespace Barotrauma
set { _humanRunParams = value; }
}
private HumanCrouchParams _humanCrouchParams;
public HumanCrouchParams HumanCrouchParams
{
get
{
if (_humanCrouchParams == null)
{
_humanCrouchParams = HumanCrouchParams.GetDefaultAnimParams(character);
}
return _humanCrouchParams;
}
set { _humanCrouchParams = value; }
}
private HumanSwimSlowParams _humanSwimSlowParams;
public HumanSwimSlowParams HumanSwimSlowParams
{
@@ -102,8 +115,11 @@ namespace Barotrauma
}
public new HumanGroundedParams CurrentGroundedParams => base.CurrentGroundedParams as HumanGroundedParams;
public new HumanSwimParams CurrentSwimParams => base.CurrentSwimParams as HumanSwimParams;
public IHumanAnimation CurrentHumanAnimParams => CurrentAnimationParams as IHumanAnimation;
public override GroundedMovementParams WalkParams
{
get { return HumanWalkParams; }
@@ -169,42 +185,9 @@ namespace Barotrauma
private float swimmingStateLockTimer;
private float useItemTimer;
public override float? TorsoPosition
{
get
{
return Crouching && !swimming ? CurrentGroundedParams.CrouchingTorsoPos * RagdollParams.JointScale : base.TorsoPosition;
}
}
public override float? HeadPosition
{
get
{
return Crouching && !swimming ? CurrentGroundedParams.CrouchingHeadPos * RagdollParams.JointScale : base.HeadPosition;
}
}
public override float? TorsoAngle
{
get
{
return Crouching && !swimming ? MathHelper.ToRadians(CurrentGroundedParams.CrouchingTorsoAngle) : base.TorsoAngle;
}
}
public override float? HeadAngle
{
get
{
return Crouching && !swimming ? MathHelper.ToRadians(CurrentGroundedParams.CrouchingHeadAngle) : base.HeadAngle;
}
}
public float HeadLeanAmount => CurrentGroundedParams.HeadLeanAmount;
public float TorsoLeanAmount => CurrentGroundedParams.TorsoLeanAmount;
public Vector2 FootMoveOffset => (Crouching ? CurrentGroundedParams.CrouchingFootMoveOffset : CurrentGroundedParams.FootMoveOffset) * RagdollParams.JointScale;
public Vector2 FootMoveOffset => CurrentGroundedParams.FootMoveOffset * RagdollParams.JointScale;
public float LegBendTorque => CurrentGroundedParams.LegBendTorque * RagdollParams.JointScale;
public Vector2 HandMoveOffset => CurrentGroundedParams.HandMoveOffset * RagdollParams.JointScale;
@@ -298,7 +281,7 @@ namespace Barotrauma
LimbType lowerLegType = LimbType.RightLeg;
LimbType footType = LimbType.RightFoot;
var waistJoint = GetJointBetweenLimbs(LimbType.Waist, upperLegType);
var waistJoint = GetJointBetweenLimbs(LimbType.Waist, upperLegType) ?? GetJointBetweenLimbs(LimbType.Torso, upperLegType);
Vector2 localAnchorWaist = Vector2.Zero;
Vector2 localAnchorKnee = Vector2.Zero;
if (waistJoint != null)
@@ -336,7 +319,7 @@ namespace Barotrauma
levitatingCollider = true;
ColliderIndex = Crouching && !swimming ? 1 : 0;
if (character.SelectedConstruction?.GetComponent<Controller>()?.ControlCharacterPose ?? false ||
(ForceSelectAnimationType != AnimationType.Walk && ForceSelectAnimationType != AnimationType.NotDefined))
(ForceSelectAnimationType != AnimationType.Crouch && ForceSelectAnimationType != AnimationType.NotDefined))
{
Crouching = false;
ColliderIndex = 0;
@@ -439,9 +422,8 @@ namespace Barotrauma
midPos += Vector2.Transform(new Vector2(-0.3f * Dir, -0.2f), torsoTransform);
if (rightHand.PullJointEnabled) midPos = (midPos + rightHand.PullJointWorldAnchorB) / 2.0f;
HandIK(rightHand, midPos);
HandIK(leftHand, midPos);
HandIK(rightHand, midPos, CurrentHumanAnimParams.ArmIKStrength, CurrentHumanAnimParams.HandIKStrength);
HandIK(leftHand, midPos, CurrentHumanAnimParams.ArmIKStrength, CurrentHumanAnimParams.HandIKStrength);
}
else if (character.AnimController.AnimationTestPose)
{
@@ -638,7 +620,7 @@ namespace Barotrauma
Collider.LinearVelocity.Y > 0.0f ? Collider.LinearVelocity.Y * 0.5f : Collider.LinearVelocity.Y);
}
getUpForce = getUpForce * Math.Max(head.SimPosition.Y - colliderPos.Y, 0.5f);
getUpForce *= Math.Max(head.SimPosition.Y - colliderPos.Y, 0.5f);
torso.PullJointEnabled = true;
head.PullJointEnabled = true;
@@ -710,9 +692,12 @@ namespace Barotrauma
float torsoAngle = TorsoAngle.Value;
float herpesStrength = character.CharacterHealth.GetAfflictionStrength("spaceherpes");
torsoAngle -= herpesStrength / 150.0f;
torso.body.SmoothRotate(torsoAngle * Dir, 50.0f);
torso.body.SmoothRotate(torsoAngle * Dir, CurrentGroundedParams.TorsoTorque);
}
if (HeadAngle.HasValue)
{
head.body.SmoothRotate(HeadAngle.Value * Dir, CurrentGroundedParams.HeadTorque);
}
if (HeadAngle.HasValue) head.body.SmoothRotate(HeadAngle.Value * Dir, 50.0f);
if (!onGround)
{
@@ -743,12 +728,23 @@ namespace Barotrauma
Vector2 footPos = stepSize * -i;
footPos += new Vector2(Math.Sign(movement.X) * FootMoveOffset.X, FootMoveOffset.Y);
if (footPos.Y < 0.0f) footPos.Y = -0.15f;
if (footPos.Y < 0.0f) { footPos.Y = -0.15f; }
//make the character limp if the feet are damaged
float footAfflictionStrength = character.CharacterHealth.GetAfflictionStrength("damage", foot, true);
footPos.X *= MathHelper.Lerp(1.0f, 0.75f, MathHelper.Clamp(footAfflictionStrength / 50.0f, 0.0f, 1.0f));
if (CurrentGroundedParams.FootLiftHorizontalFactor > 0)
{
// Calculate the foot y dynamically based on the foot position relative to the waist,
// so that the foot aims higher when it's behind the waist and lower when it's in the front.
float xDiff = (foot.SimPosition.X - waistPos.X + FootMoveOffset.X) * Dir;
float min = MathUtils.InverseLerp(1, 0, CurrentGroundedParams.FootLiftHorizontalFactor);
float max = 1 + MathUtils.InverseLerp(0, 1, CurrentGroundedParams.FootLiftHorizontalFactor);
float xFactor = MathHelper.Lerp(min, max, MathUtils.InverseLerp(RagdollParams.JointScale, -RagdollParams.JointScale, xDiff));
footPos.Y *= xFactor;
}
if (onSlope && Stairs == null)
{
footPos.Y *= 2.0f;
@@ -770,7 +766,7 @@ namespace Barotrauma
foot.DebugTargetPos = colliderPos + footPos;
MoveLimb(foot, colliderPos + footPos, CurrentGroundedParams.FootMoveStrength);
FootIK(foot, colliderPos + footPos,
CurrentGroundedParams.LegBendTorque, CurrentGroundedParams.FootRotateStrength, CurrentGroundedParams.FootAngleInRadians);
CurrentGroundedParams.LegBendTorque, CurrentGroundedParams.FootTorque, CurrentGroundedParams.FootAngleInRadians);
}
}
@@ -789,7 +785,7 @@ namespace Barotrauma
HandIK(rightHand, torso.SimPosition + posAddition +
new Vector2(
-handPos.X,
(Math.Sign(walkPosX) == Math.Sign(Dir)) ? handPos.Y : lowerY), CurrentGroundedParams.HandMoveStrength);
(Math.Sign(walkPosX) == Math.Sign(Dir)) ? handPos.Y : lowerY), CurrentGroundedParams.ArmMoveStrength, CurrentGroundedParams.HandMoveStrength);
}
if (leftHand != null && !leftHand.Disabled)
@@ -797,16 +793,14 @@ namespace Barotrauma
HandIK(leftHand, torso.SimPosition + posAddition +
new Vector2(
handPos.X,
(Math.Sign(walkPosX) == Math.Sign(-Dir)) ? handPos.Y : lowerY), CurrentGroundedParams.HandMoveStrength);
(Math.Sign(walkPosX) == Math.Sign(-Dir)) ? handPos.Y : lowerY), CurrentGroundedParams.ArmMoveStrength, CurrentGroundedParams.HandMoveStrength);
}
}
else
{
for (int i = -1; i < 2; i += 2)
{
Vector2 footPos = colliderPos;
if (Crouching)
{
footPos = new Vector2(
@@ -817,27 +811,24 @@ namespace Barotrauma
//lift the foot at the back up a bit
footPos.Y += 0.15f;
}
footPos.X += torso.SimPosition.X;
footPos.X += colliderPos.X;
}
else
{
footPos = new Vector2(colliderPos.X + stepSize.X * i * 0.2f, colliderPos.Y - 0.1f);
}
if (Stairs == null)
{
footPos.Y = Math.Max(Math.Min(FloorY, footPos.Y + 0.5f), footPos.Y);
}
var foot = i == -1 ? rightFoot : leftFoot;
if (foot != null && !foot.Disabled)
{
foot.DebugRefPos = colliderPos;
foot.DebugTargetPos = footPos;
MoveLimb(foot, footPos, CurrentGroundedParams.FootMoveStrength);
FootIK(foot, footPos,
CurrentGroundedParams.LegBendTorque, CurrentGroundedParams.FootRotateStrength, CurrentGroundedParams.FootAngleInRadians);
CurrentGroundedParams.LegBendTorque, CurrentGroundedParams.FootTorque, CurrentGroundedParams.FootAngleInRadians);
}
}
@@ -970,7 +961,7 @@ namespace Barotrauma
if (!aiming)
{
float newRotation = MathUtils.VectorToAngle(TargetMovement) - MathHelper.PiOver2;
Collider.SmoothRotate(newRotation, 5.0f * character.SpeedMultiplier);
Collider.SmoothRotate(newRotation, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
}
}
else
@@ -981,7 +972,7 @@ namespace Barotrauma
Vector2 diff = (mousePos - torso.SimPosition) * Dir;
TargetMovement = new Vector2(0.0f, -0.1f);
float newRotation = MathUtils.VectorToAngle(diff);
Collider.SmoothRotate(newRotation, 5.0f * character.SpeedMultiplier);
Collider.SmoothRotate(newRotation, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
}
}
@@ -991,19 +982,19 @@ namespace Barotrauma
if (TorsoAngle.HasValue)
{
torso.body.SmoothRotate(Collider.Rotation + TorsoAngle.Value * Dir, CurrentSwimParams.SteerTorque);
torso.body.SmoothRotate(Collider.Rotation + TorsoAngle.Value * Dir, CurrentSwimParams.TorsoTorque);
}
else
{
torso.body.SmoothRotate(Collider.Rotation, CurrentSwimParams.SteerTorque);
torso.body.SmoothRotate(Collider.Rotation, CurrentSwimParams.TorsoTorque);
}
if (HeadAngle.HasValue)
{
head.body.SmoothRotate(Collider.Rotation + HeadAngle.Value * Dir, CurrentSwimParams.SteerTorque);
head.body.SmoothRotate(Collider.Rotation + HeadAngle.Value * Dir, CurrentSwimParams.HeadTorque);
}
else
{
head.body.SmoothRotate(Collider.Rotation, CurrentSwimParams.SteerTorque);
head.body.SmoothRotate(Collider.Rotation, CurrentSwimParams.HeadTorque);
}
//dont try to move upwards if head is already out of water
@@ -1044,25 +1035,25 @@ namespace Barotrauma
float legMoveMultiplier = 1.0f;
if (movement.LengthSquared() < 0.001f)
{
//TODO: expose these?
// Swimming in place (TODO: expose?)
legMoveMultiplier = 0.3f;
legCyclePos += 0.4f;
handCyclePos += 0.1f;
}
var waist = GetLimb(LimbType.Waist);
var waist = GetLimb(LimbType.Waist) ?? GetLimb(LimbType.Torso);
footPos = waist == null ? Vector2.Zero : waist.SimPosition - new Vector2((float)Math.Sin(-Collider.Rotation), (float)Math.Cos(-Collider.Rotation)) * (upperLegLength + lowerLegLength);
Vector2 transformedFootPos = new Vector2((float)Math.Sin(legCyclePos / CurrentSwimParams.LegCycleLength) * CurrentSwimParams.LegMoveAmount * legMoveMultiplier, 0.0f);
transformedFootPos = Vector2.Transform(transformedFootPos, Matrix.CreateRotationZ(Collider.Rotation));
float torque = CurrentSwimParams.FootRotateStrength * character.SpeedMultiplier * (1.2f - character.GetLegPenalty());
float legTorque = CurrentSwimParams.LegTorque * character.SpeedMultiplier * (1.2f - character.GetLegPenalty());
if (rightFoot != null && !rightFoot.Disabled)
{
FootIK(rightFoot, footPos - transformedFootPos, torque, torque, CurrentSwimParams.FootAngleInRadians);
FootIK(rightFoot, footPos - transformedFootPos, legTorque, CurrentSwimParams.FootTorque, CurrentSwimParams.FootAngleInRadians);
}
if (leftFoot != null && !leftFoot.Disabled)
{
FootIK(leftFoot, footPos + transformedFootPos, torque, torque, CurrentSwimParams.FootAngleInRadians);
FootIK(leftFoot, footPos + transformedFootPos, legTorque, CurrentSwimParams.FootTorque, CurrentSwimParams.FootAngleInRadians);
}
handPos = (torso.SimPosition + head.SimPosition) / 2.0f;
@@ -1071,7 +1062,7 @@ namespace Barotrauma
// -> hands just float around
if ((!headInWater && TargetMovement.X == 0.0f && TargetMovement.Y > 0) || TargetMovement.LengthSquared() < 0.001f)
{
handPos += MathUtils.RotatePoint(Vector2.UnitX * Dir * 0.6f, torso.Rotation);
handPos += MathUtils.RotatePoint(Vector2.UnitX * Dir * 0.2f, torso.Rotation);
float wobbleAmount = 0.1f;
@@ -1079,14 +1070,14 @@ namespace Barotrauma
{
MoveLimb(rightHand, new Vector2(
handPos.X + (float)Math.Sin(handCyclePos / 1.5f) * wobbleAmount,
handPos.Y + (float)Math.Sin(handCyclePos / 3.5f) * wobbleAmount - 0.25f), 1.5f);
handPos.Y + (float)Math.Sin(handCyclePos / 3.5f) * wobbleAmount - 0.25f), CurrentSwimParams.ArmMoveStrength);
}
if (leftHand != null && !leftHand.Disabled)
{
MoveLimb(leftHand, new Vector2(
handPos.X + (float)Math.Sin(handCyclePos / 2.0f) * wobbleAmount,
handPos.Y + (float)Math.Sin(handCyclePos / 3.0f) * wobbleAmount - 0.25f), 1.5f);
handPos.Y + (float)Math.Sin(handCyclePos / 3.0f) * wobbleAmount - 0.25f), CurrentSwimParams.ArmMoveStrength);
}
return;
@@ -1107,8 +1098,8 @@ namespace Barotrauma
Vector2 rightHandPos = new Vector2(-handPosX, -handPosY) + handMoveOffset;
rightHandPos.X = (Dir == 1.0f) ? Math.Max(0.3f, rightHandPos.X) : Math.Min(-0.3f, rightHandPos.X);
rightHandPos = Vector2.Transform(rightHandPos, rotationMatrix);
HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.HandMoveStrength * character.SpeedMultiplier * (1 - Character.GetRightHandPenalty()));
float speedMultiplier = character.SpeedMultiplier * (1 - Character.GetRightHandPenalty());
HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier);
}
if (leftHand != null && !leftHand.Disabled)
@@ -1116,8 +1107,8 @@ namespace Barotrauma
Vector2 leftHandPos = new Vector2(handPosX, handPosY) + handMoveOffset;
leftHandPos.X = (Dir == 1.0f) ? Math.Max(0.3f, leftHandPos.X) : Math.Min(-0.3f, leftHandPos.X);
leftHandPos = Vector2.Transform(leftHandPos, rotationMatrix);
HandIK(leftHand, handPos + leftHandPos, CurrentSwimParams.HandMoveStrength * character.SpeedMultiplier * (1 - Character.GetLeftHandPenalty()));
float speedMultiplier = character.SpeedMultiplier * (1 - Character.GetLeftHandPenalty());
HandIK(leftHand, handPos + leftHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier);
}
}
@@ -1173,15 +1164,16 @@ namespace Barotrauma
}
float bottomPos = Collider.SimPosition.Y - ColliderHeightFromFloor - Collider.radius - Collider.height / 2.0f;
float headPos = HeadPosition ?? 0;
float torsoPos = TorsoPosition ?? 0;
MoveLimb(head, new Vector2(ladderSimPos.X - 0.2f * Dir, bottomPos + headPos), 10.5f);
MoveLimb(torso, new Vector2(ladderSimPos.X - 0.35f * Dir, bottomPos + torsoPos), 10.5f);
MoveLimb(head, new Vector2(ladderSimPos.X - 0.35f * Dir, bottomPos + WalkParams.HeadPosition), 10.5f);
MoveLimb(torso, new Vector2(ladderSimPos.X - 0.35f * Dir, bottomPos + WalkParams.TorsoPosition), 10.5f);
Collider.MoveToPos(new Vector2(ladderSimPos.X - 0.1f * Dir, Collider.SimPosition.Y), 10.5f);
Collider.MoveToPos(new Vector2(ladderSimPos.X - 0.1f * Dir, Collider.SimPosition.Y), 10.5f);
Vector2 handPos = new Vector2(
ladderSimPos.X,
bottomPos + WalkParams.TorsoPosition + movement.Y * 0.1f - ladderSimPos.Y);
bottomPos + torsoPos + movement.Y * 0.1f - ladderSimPos.Y);
//prevent the hands from going above the top of the ladders
handPos.Y = Math.Min(-0.5f, handPos.Y);
@@ -1258,7 +1250,8 @@ namespace Barotrauma
//apply forces to the collider to move the Character up/down
Collider.ApplyForce((climbForce * 20.0f + subSpeed * 50.0f) * Collider.Mass, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
head.body.SmoothRotate(0.0f);
float movementMultiplier = targetMovement.Y < 0 ? 0 : 1;
head.body.SmoothRotate(MathHelper.PiOver4 * movementMultiplier * Dir, WalkParams.HeadTorque);
if (!character.SelectedConstruction.Prefab.Triggers.Any())
{
@@ -1881,7 +1874,7 @@ namespace Barotrauma
for (int i = 0; i < 2; i++)
{
if (!character.Inventory.IsInLimbSlot(item, i == 0 ? InvSlotType.RightHand : InvSlotType.LeftHand)) { continue; }
HandIK(i == 0 ? rightHand : leftHand, transformedHoldPos + transformedHandlePos[i]);
HandIK(i == 0 ? rightHand : leftHand, transformedHoldPos + transformedHandlePos[i], CurrentHumanAnimParams.ArmIKStrength, CurrentHumanAnimParams.HandIKStrength);
}
}
}
@@ -1906,7 +1899,7 @@ namespace Barotrauma
return (lowFreqNoise * 1.0f + highFreqNoise * 0.1f) * wobbleStrength;
}
private void HandIK(Limb hand, Vector2 pos, float force = 1.0f)
private void HandIK(Limb hand, Vector2 pos, float armTorque = 1.0f, float handTorque = 1.0f)
{
Vector2 shoulderPos;
@@ -1948,9 +1941,11 @@ namespace Barotrauma
armAngle -= MathHelper.TwoPi;
}
arm?.body.SmoothRotate((armAngle - upperArmAngle), 20.0f * force * arm.Mass, wrapAngle: false);
forearm?.body.SmoothRotate((armAngle + lowerArmAngle), 20.0f * force * forearm.Mass, wrapAngle: false);
hand?.body.SmoothRotate((armAngle + lowerArmAngle), 100.0f * force * hand.Mass, wrapAngle: false);
arm?.body.SmoothRotate(armAngle - upperArmAngle, 100.0f * armTorque * arm.Mass, wrapAngle: false);
float forearmAngle = armAngle + lowerArmAngle;
forearm?.body.SmoothRotate(forearmAngle, 100.0f * handTorque * forearm.Mass, wrapAngle: false);
float handAngle = forearm != null ? armAngle : forearmAngle;
hand?.body.SmoothRotate(handAngle, 100.0f * handTorque * hand.Mass, wrapAngle: false);
}
private void FootIK(Limb foot, Vector2 pos, float legTorque, float footTorque, float footAngle)
@@ -1976,12 +1971,12 @@ namespace Barotrauma
upperLeg = GetLimb(LimbType.RightThigh);
lowerLeg = GetLimb(LimbType.RightLeg);
}
var torso = GetLimb(LimbType.Torso);
var waist = GetJointBetweenLimbs(LimbType.Waist, upperLeg.type);
Limb torso = GetLimb(LimbType.Torso);
LimbJoint waistJoint = GetJointBetweenLimbs(LimbType.Waist, upperLeg.type) ?? GetJointBetweenLimbs(LimbType.Torso, upperLeg.type);
Vector2 waistPos = Vector2.Zero;
if (waist != null)
if (waistJoint != null)
{
waistPos = waist.LimbA == upperLeg ? waist.WorldAnchorA : waist.WorldAnchorB;
waistPos = waistJoint.LimbA == upperLeg ? waistJoint.WorldAnchorA : waistJoint.WorldAnchorB;
}
//distance from waist joint to the target position
@@ -2137,5 +2132,18 @@ namespace Barotrauma
}
}
public override float GetSpeed(AnimationType type)
{
if (type == AnimationType.Crouch)
{
if (!CanWalk)
{
DebugConsole.ThrowError($"{character.SpeciesName} cannot crouch!");
return 0;
}
return IsMovingBackwards ? HumanCrouchParams.MovementSpeed * HumanCrouchParams.BackwardsMovementMultiplier : HumanCrouchParams.MovementSpeed;
}
return base.GetSpeed(type);
}
}
}
@@ -309,8 +309,6 @@ namespace Barotrauma
public string TraitorCurrentObjective = "";
public bool IsHuman => SpeciesName.Equals(CharacterPrefab.HumanSpeciesName, StringComparison.OrdinalIgnoreCase);
public bool IsMale => Info != null && Info.HasGenders && Info.Gender == Gender.Male;
public bool IsFemale => Info != null && Info.HasGenders && Info.Gender == Gender.Female;
private float attackCoolDown;
@@ -1665,9 +1663,9 @@ namespace Barotrauma
AnimController.IgnorePlatforms = AnimController.TargetMovement.Y < -0.1f;
}
if (AnimController is HumanoidAnimController)
if (AnimController is HumanoidAnimController humanAnimController)
{
((HumanoidAnimController)AnimController).Crouching = IsKeyDown(InputType.Crouch);
humanAnimController.Crouching = humanAnimController.ForceSelectAnimationType == AnimationType.Crouch || IsKeyDown(InputType.Crouch);
}
if (!aiControlled &&
@@ -2760,9 +2758,9 @@ namespace Barotrauma
//ragdoll button
if (IsRagdolled || !CanMove)
{
if (AnimController is HumanoidAnimController)
if (AnimController is HumanoidAnimController humanAnimController)
{
((HumanoidAnimController)AnimController).Crouching = false;
humanAnimController.Crouching = false;
}
AnimController.ResetPullJoints();
SelectedConstruction = null;
@@ -3505,9 +3503,9 @@ namespace Barotrauma
}
}
public AttackResult AddDamage(Vector2 worldPosition, IEnumerable<Affliction> afflictions, float stun, bool playSound, float attackImpulse = 0.0f, Character attacker = null)
public AttackResult AddDamage(Vector2 worldPosition, IEnumerable<Affliction> afflictions, float stun, bool playSound, float attackImpulse = 0.0f, Character attacker = null, float damageMultiplier = 1f)
{
return AddDamage(worldPosition, afflictions, stun, playSound, attackImpulse, out _, attacker);
return AddDamage(worldPosition, afflictions, stun, playSound, attackImpulse, out _, attacker, damageMultiplier: damageMultiplier);
}
public AttackResult AddDamage(Vector2 worldPosition, IEnumerable<Affliction> afflictions, float stun, bool playSound, float attackImpulse, out Limb hitLimb, Character attacker = null, float damageMultiplier = 1)
@@ -4351,7 +4349,7 @@ namespace Barotrauma
return GiveTalent(talentPrefab, addingFirstTime);
}
private bool GiveTalent(TalentPrefab talentPrefab, bool addingFirstTime = true)
public bool GiveTalent(TalentPrefab talentPrefab, bool addingFirstTime = true)
{
if (addingFirstTime)
{
@@ -1,9 +1,9 @@
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Barotrauma.IO;
using System.Linq;
using System.Xml.Linq;
@@ -14,7 +14,6 @@ namespace Barotrauma
public enum Gender { None, Male, Female };
public enum Race { None, White, Black, Brown, Asian };
// TODO: Generating the HeadInfo could be simplified.
partial class CharacterInfo
{
public class HeadInfo
@@ -25,15 +24,7 @@ namespace Barotrauma
get { return _headSpriteId; }
set
{
_headSpriteId = value;
if (_headSpriteId < (int)headSpriteRange.X)
{
_headSpriteId = (int)headSpriteRange.Y;
}
if (_headSpriteId > (int)headSpriteRange.Y)
{
_headSpriteId = (int)headSpriteRange.X;
}
_headSpriteId = Math.Max(Math.Clamp(value, (int)headSpriteRange.X, (int)headSpriteRange.Y), 1);
GetSpriteSheetIndex();
}
}
@@ -42,6 +33,10 @@ namespace Barotrauma
public Gender gender;
public Race race;
public Color HairColor;
public Color FacialHairColor;
public Color SkinColor;
public int HairIndex { get; set; } = -1;
public int BeardIndex { get; set; } = -1;
public int MoustacheIndex { get; set; } = -1;
@@ -74,11 +69,11 @@ namespace Barotrauma
FaceAttachmentIndex = -1;
}
private void GetSpriteSheetIndex()
public void GetSpriteSheetIndex()
{
if (heads != null && heads.Any())
{
var matchingHead = heads.Keys.FirstOrDefault(h => h.Gender == gender && h.Race == race && h.ID == _headSpriteId);
var matchingHead = heads.Keys.FirstOrDefault(h => h.ID == HeadSpriteId && IsMatchingGender(h.Gender, gender) && IsMatchingRace(h.Race, race));
if (matchingHead != null)
{
if (heads.TryGetValue(matchingHead, out Vector2 index))
@@ -99,14 +94,13 @@ namespace Barotrauma
if (head != value && value != null)
{
head = value;
if (head.race == Race.None)
if (!IsValidRace(head.race))
{
head.race = GetRandomRace(Rand.RandSync.Unsynced);
}
CalculateHeadSpriteRange();
Head.HeadSpriteId = value.HeadSpriteId;
HeadSprite = null;
AttachmentSprites = null;
RefreshHeadSprites();
}
}
}
@@ -157,7 +151,9 @@ namespace Barotrauma
public bool HasNickname => Name != OriginalName;
public string OriginalName { get; private set; }
public string Name;
public string DisplayName
{
get
@@ -387,29 +383,31 @@ namespace Barotrauma
set
{
Head.HeadSpriteId = value;
HeadSprite = null;
AttachmentSprites = null;
ResetHeadAttachments();
RefreshHeadSprites();
}
}
public readonly bool HasGenders;
public readonly bool HasRaces;
public Gender Gender
{
get { return Head.gender; }
set
{
if (Head.gender == value) return;
Gender previousValue = Head.gender;
Head.gender = value;
if (Head.gender == Gender.None)
if (!IsValidGender(Head.gender))
{
Head.gender = Gender.Male;
Head.gender = GetDefaultGender();
}
if (Head.gender != previousValue)
{
CalculateHeadSpriteRange();
ResetHeadAttachments();
RefreshHeadSprites();
}
CalculateHeadSpriteRange();
ResetHeadAttachments();
HeadSprite = null;
AttachmentSprites = null;
}
}
@@ -418,28 +416,82 @@ namespace Barotrauma
get { return Head.race; }
set
{
if (Head.race == value) { return; }
Race previousValue = Head.race;
Head.race = value;
if (Head.race == Race.None)
if (!IsValidRace(Head.race))
{
Head.race = Race.White;
Head.race = GetDefaultRace();
}
if (Head.race != previousValue)
{
CalculateHeadSpriteRange();
ResetHeadAttachments();
RefreshHeadSprites();
}
CalculateHeadSpriteRange();
ResetHeadAttachments();
HeadSprite = null;
AttachmentSprites = null;
}
}
public int HairIndex { get => Head.HairIndex; set => Head.HairIndex = value; }
public int BeardIndex { get => Head.BeardIndex; set => Head.BeardIndex = value; }
public int MoustacheIndex { get => Head.MoustacheIndex; set => Head.MoustacheIndex = value; }
public int FaceAttachmentIndex { get => Head.FaceAttachmentIndex; set => Head.FaceAttachmentIndex = value; }
private bool IsValidRace(Race race) => HasRaces ? race != Race.None : race == Race.None;
public XElement HairElement { get => Head.HairElement; set => Head.HairElement = value; }
public XElement BeardElement { get => Head.BeardElement; set => Head.BeardElement = value; }
public XElement MoustacheElement { get => Head.MoustacheElement; set => Head.MoustacheElement = value; }
public XElement FaceAttachment { get => Head.FaceAttachment; set => Head.FaceAttachment = value; }
private bool IsValidGender(Gender gender) => HasGenders ? gender != Gender.None : gender == Gender.None;
private Gender GetDefaultGender() => HasGenders ? Gender.Male : Gender.None;
private Race GetDefaultRace() => HasRaces ? Race.White : Race.None;
public int HairIndex
{
get => Head.HairIndex;
set => Head.HairIndex = value;
}
public int BeardIndex
{
get => Head.BeardIndex;
set => Head.BeardIndex = value;
}
public int MoustacheIndex
{
get => Head.MoustacheIndex;
set => Head.MoustacheIndex = value;
}
public int FaceAttachmentIndex
{
get => Head.FaceAttachmentIndex;
set => Head.FaceAttachmentIndex = value;
}
public readonly ImmutableArray<Color> HairColors;
public readonly ImmutableArray<Color> FacialHairColors;
public readonly ImmutableArray<Color> SkinColors;
public Color HairColor
{
get => Head.HairColor;
set => Head.HairColor = value;
}
public Color FacialHairColor
{
get => Head.FacialHairColor;
set => Head.FacialHairColor = value;
}
public Color SkinColor
{
get => Head.SkinColor;
set => Head.SkinColor = value;
}
public XElement HairElement => Head.HairElement;
public XElement BeardElement => Head.BeardElement;
public XElement MoustacheElement => Head.MoustacheElement;
public XElement FaceAttachment => Head.FaceAttachment;
private RagdollParams ragdoll;
public RagdollParams Ragdoll
@@ -480,16 +532,18 @@ namespace Barotrauma
if (doc == null) { return; }
CharacterConfigElement = doc.Root.IsOverride() ? doc.Root.FirstElement() : doc.Root;
// TODO: support for variants
head = new HeadInfo();
Head = new HeadInfo();
HasGenders = CharacterConfigElement.GetAttributeBool("genders", false);
if (HasGenders)
{
Head.gender = GetRandomGender(randSync);
}
Head.gender = GetRandomGender(randSync);
HasRaces = CharacterConfigElement.GetAttributeBool("races", false);
Head.race = GetRandomRace(randSync);
CalculateHeadSpriteRange();
Head.HeadSpriteId = GetRandomHeadID(randSync);
HeadSpriteId = GetRandomHeadID(randSync);
Job = (jobPrefab == null) ? Job.Random(Rand.RandSync.Unsynced) : new Job(jobPrefab, variant);
HairColors = CharacterConfigElement.GetAttributeColorArray("haircolors", new Color[] { Color.WhiteSmoke }).ToImmutableArray();
FacialHairColors = CharacterConfigElement.GetAttributeColorArray("facialhaircolors", new Color[] { Color.WhiteSmoke }).ToImmutableArray();
SkinColors = CharacterConfigElement.GetAttributeColorArray("skincolors", new Color[] { new Color(255, 215, 200, 255) }).ToImmutableArray();
SetColors();
if (!string.IsNullOrEmpty(name))
{
@@ -502,23 +556,7 @@ namespace Barotrauma
else
{
name = "";
if (CharacterConfigElement.Element("name") != null)
{
string firstNamePath = CharacterConfigElement.Element("name").GetAttributeString("firstname", "");
if (firstNamePath != "")
{
firstNamePath = firstNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male");
Name = ToolBox.GetRandomLine(firstNamePath, randSync);
}
string lastNamePath = CharacterConfigElement.Element("name").GetAttributeString("lastname", "");
if (lastNamePath != "")
{
lastNamePath = lastNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male");
if (Name != "") Name += " ";
Name += ToolBox.GetRandomLine(lastNamePath, randSync);
}
}
Name = GetRandomName(randSync);
}
OriginalName = !string.IsNullOrEmpty(originalName) ? originalName : Name;
personalityTrait = NPCPersonalityTrait.GetRandom(name + HeadSpriteId);
@@ -530,6 +568,53 @@ namespace Barotrauma
LoadHeadAttachments();
}
public string GetRandomName(Rand.RandSync randSync)
{
string name = "";
if (CharacterConfigElement.Element("name") != null)
{
string firstNamePath = CharacterConfigElement.Element("name").GetAttributeString("firstname", "");
if (firstNamePath != "")
{
firstNamePath = firstNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male");
name = ToolBox.GetRandomLine(firstNamePath, randSync);
}
string lastNamePath = CharacterConfigElement.Element("name").GetAttributeString("lastname", "");
if (lastNamePath != "")
{
lastNamePath = lastNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male");
if (name != "") { name += " "; }
name += ToolBox.GetRandomLine(lastNamePath, randSync);
}
}
return name;
}
private void SetColors()
{
HairColor = HairColors.GetRandom();
FacialHairColor = FacialHairColors.GetRandom();
SkinColor = SkinColors.GetRandom();
}
private void CheckColors()
{
if (HairColor == Color.Black)
{
HairColor = HairColors.GetRandom();
}
if (FacialHairColor == Color.Black)
{
FacialHairColor = FacialHairColors.GetRandom();
}
if (SkinColor == Color.Black)
{
SkinColor = SkinColors.GetRandom();
}
}
// Used for loading the data
public CharacterInfo(XElement infoElement)
{
@@ -542,7 +627,7 @@ namespace Barotrauma
ExperiencePoints = infoElement.GetAttributeInt("experiencepoints", 0);
UnlockedTalents = new HashSet<string>(infoElement.GetAttributeStringArray("unlockedtalents", new string[0], convertToLowerInvariant: true));
AdditionalTalentPoints = infoElement.GetAttributeInt("additionaltalentpoints", 0);
Enum.TryParse(infoElement.GetAttributeString("race", "White"), true, out Race race);
Enum.TryParse(infoElement.GetAttributeString("race", "None"), true, out Race race);
Enum.TryParse(infoElement.GetAttributeString("gender", "None"), true, out Gender gender);
_speciesName = infoElement.GetAttributeString("speciesname", null);
XDocument doc = null;
@@ -560,14 +645,19 @@ namespace Barotrauma
// TODO: support for variants
CharacterConfigElement = doc.Root.IsOverride() ? doc.Root.FirstElement() : doc.Root;
HasGenders = CharacterConfigElement.GetAttributeBool("genders", false);
if (HasGenders && gender == Gender.None)
HasRaces = CharacterConfigElement.GetAttributeBool("hasraces", false);
if (!IsValidGender(gender))
{
gender = GetRandomGender(Rand.RandSync.Unsynced);
}
else if (!HasGenders)
if (!IsValidRace(race))
{
gender = Gender.None;
race = GetRandomRace(Rand.RandSync.Unsynced);
}
HairColors = CharacterConfigElement.GetAttributeColorArray("haircolors", new Color[] { Color.WhiteSmoke }).ToImmutableArray();
FacialHairColors = CharacterConfigElement.GetAttributeColorArray("facialhaircolors", new Color[] { Color.WhiteSmoke }).ToImmutableArray();
SkinColors = CharacterConfigElement.GetAttributeColorArray("skincolors", new Color[] { new Color(255, 215, 200, 255) }).ToImmutableArray();
RecreateHead(
infoElement.GetAttributeInt("headspriteid", 1),
race,
@@ -577,6 +667,11 @@ namespace Barotrauma
infoElement.GetAttributeInt("moustacheindex", -1),
infoElement.GetAttributeInt("faceattachmentindex", -1));
SkinColor = infoElement.GetAttributeColor("skincolor", Color.White);
HairColor = infoElement.GetAttributeColor("haircolor", Color.White);
FacialHairColor = infoElement.GetAttributeColor("facialhaircolor", Color.White);
CheckColors();
if (string.IsNullOrEmpty(Name))
{
if (CharacterConfigElement.Element("name") != null)
@@ -652,8 +747,25 @@ namespace Barotrauma
LoadHeadAttachments();
}
public Gender GetRandomGender(Rand.RandSync randSync) => (Rand.Range(0.0f, 1.0f, randSync) < CharacterConfigElement.GetAttributeFloat("femaleratio", 0.5f)) ? Gender.Female : Gender.Male;
public Race GetRandomRace(Rand.RandSync randSync) => new Race[] { Race.White, Race.Black, Race.Asian }.GetRandom(randSync);
public Gender GetRandomGender(Rand.RandSync randSync)
{
if (HasGenders)
{
return (Rand.Range(0.0f, 1.0f, randSync) < CharacterConfigElement.GetAttributeFloat("femaleratio", 0.5f)) ? Gender.Female : Gender.Male;
}
return Gender.None;
}
public Race GetRandomRace(Rand.RandSync randSync)
{
if (HasRaces)
{
return new Race[] { Race.White, Race.Black, Race.Asian }.GetRandom(randSync);
}
return Race.None;
}
public int GetRandomHeadID(Rand.RandSync randSync) => Head.headSpriteRange != Vector2.Zero ? Rand.Range((int)Head.headSpriteRange.X, (int)Head.headSpriteRange.Y + 1, randSync) : 0;
private List<XElement> hairs;
@@ -720,10 +832,13 @@ namespace Barotrauma
{
if (elements == null) { return elements; }
return elements.Where(w =>
Enum.TryParse(w.GetAttributeString("gender", "None"), true, out Gender g) && g == gender &&
Enum.TryParse(w.GetAttributeString("race", "None"), true, out Race r) && r == race);
IsMatchingGender(Enum.Parse<Gender>(w.GetAttributeString("gender", "None"), ignoreCase: true), gender) &&
IsMatchingRace(Enum.Parse<Race>(w.GetAttributeString("race", "None"), ignoreCase: true), race));
}
public static bool IsMatchingGender(Gender gender, Gender myGender) => gender == Gender.None || gender == myGender;
public static bool IsMatchingRace(Race race, Race myRace) => race == Race.None || race == myRace;
private void LoadHeadPresets()
{
if (CharacterConfigElement == null) { return; }
@@ -752,9 +867,16 @@ namespace Barotrauma
// If there are any head presets defined, use them.
if (heads.Any())
{
var ids = heads.Keys.Where(h => h.Race == Race && h.Gender == Gender).Select(w => w.ID);
var ids = heads.Keys.Where(h => IsMatchingRace(Race, h.Race) && IsMatchingGender(Gender, h.Gender)).Select(w => w.ID);
ids = ids.OrderBy(id => id);
Head.headSpriteRange = new Vector2(ids.First(), ids.Last());
if (ids.Any())
{
Head.headSpriteRange = new Vector2(ids.First(), ids.Last());
}
else
{
DebugConsole.ThrowError($"[CharacterInfo] Couldn't find a head definition that matches {Race} and {Gender}!");
}
}
// Else we calculate the range from the wearables.
if (Head.headSpriteRange == Vector2.Zero)
@@ -787,26 +909,72 @@ namespace Barotrauma
}
}
public void RecreateHead(HeadInfo headInfo)
{
RecreateHead(
headInfo.HeadSpriteId,
headInfo.race,
headInfo.gender,
headInfo.HairIndex,
headInfo.BeardIndex,
headInfo.MoustacheIndex,
headInfo.FaceAttachmentIndex);
SkinColor = headInfo.SkinColor;
HairColor = headInfo.HairColor;
FacialHairColor = headInfo.FacialHairColor;
CheckColors();
}
/// <summary>
/// Recreates the head info and checks that everything is valid.
/// </summary>
public void RecreateHead(int headID, Race race, Gender gender, int hairIndex, int beardIndex, int moustacheIndex, int faceAttachmentIndex)
{
if (HasGenders && gender == Gender.None)
if (!IsValidGender(gender))
{
gender = GetRandomGender(Rand.RandSync.Unsynced);
}
else if (!HasGenders)
if (!IsValidRace(race))
{
gender = Gender.None;
race = GetRandomRace(Rand.RandSync.Unsynced);
}
if (heads == null)
{
LoadHeadPresets();
}
head = new HeadInfo(headID, gender, race, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
Color skin = Color.Black;
Color hair = Color.Black;
Color facialHair = Color.Black;
if (head != null)
{
skin = head.SkinColor;
hair = head.HairColor;
facialHair = head.FacialHairColor;
}
head = new HeadInfo(headID, gender, race, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex)
{
SkinColor = skin,
HairColor = hair,
FacialHairColor = facialHair
};
CalculateHeadSpriteRange();
ReloadHeadAttachments();
RefreshHead();
}
public void LoadHeadSprite()
/// <summary>
/// Reloads the head sprite and the attachment sprites.
/// </summary>
public void RefreshHead()
{
ReloadHeadAttachments();
RefreshHeadSprites();
}
partial void LoadHeadSpriteProjectSpecific(XElement limbElement);
private void LoadHeadSprite()
{
foreach (XElement limbElement in Ragdoll.MainElement.Elements())
{
@@ -816,6 +984,7 @@ namespace Barotrauma
if (spriteElement == null) { continue; }
string spritePath = spriteElement.Attribute("texture").Value;
if (string.IsNullOrEmpty(spritePath)) { continue; }
spritePath = spritePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male");
spritePath = spritePath.Replace("[RACE]", Head.race.ToString().ToLowerInvariant());
@@ -823,6 +992,8 @@ namespace Barotrauma
string fileName = Path.GetFileNameWithoutExtension(spritePath);
if (string.IsNullOrEmpty(fileName)) { continue; }
//go through the files in the directory to find a matching sprite
foreach (string file in Directory.GetFiles(Path.GetDirectoryName(spritePath)))
{
@@ -847,13 +1018,12 @@ namespace Barotrauma
break;
}
LoadHeadSpriteProjectSpecific(limbElement);
break;
}
}
/// <summary>
/// Loads only the elements according to the indices, not the sprites.
/// </summary>
public void LoadHeadAttachments()
{
if (Wearables != null)
@@ -1138,7 +1308,7 @@ namespace Barotrauma
new XAttribute("name", Name),
new XAttribute("originalname", OriginalName),
new XAttribute("speciesname", SpeciesName),
new XAttribute("gender", Head.gender == Gender.Male ? "male" : "female"),
new XAttribute("gender", Head.gender.ToString()),
new XAttribute("race", Head.race.ToString()),
new XAttribute("salary", Salary),
new XAttribute("experiencepoints", ExperiencePoints),
@@ -1149,6 +1319,9 @@ namespace Barotrauma
new XAttribute("beardindex", BeardIndex),
new XAttribute("moustacheindex", MoustacheIndex),
new XAttribute("faceattachmentindex", FaceAttachmentIndex),
new XAttribute("skincolor", XMLExtensions.ColorToString(SkinColor)),
new XAttribute("haircolor", XMLExtensions.ColorToString(HairColor)),
new XAttribute("facialhaircolor", XMLExtensions.ColorToString(FacialHairColor)),
new XAttribute("startitemsgiven", StartItemsGiven),
new XAttribute("ragdoll", ragdollFileName),
new XAttribute("personality", personalityTrait == null ? "" : personalityTrait.Name));
@@ -1462,13 +1635,19 @@ namespace Barotrauma
if (healthData != null) { character?.CharacterHealth.Load(healthData); }
}
public void ReloadHeadAttachments()
/// <summary>
/// Reloads the attachment xml elements according to the indices. Doesn't reload the sprites.
/// </summary>
private void ReloadHeadAttachments()
{
ResetLoadedAttachments();
LoadHeadAttachments();
}
public void ResetHeadAttachments()
/// <summary>
/// Loads only the elements according to the indices, not the sprites.
/// </summary>
private void ResetHeadAttachments()
{
ResetAttachmentIndices();
ResetLoadedAttachments();
@@ -1500,6 +1679,12 @@ namespace Barotrauma
AttachmentSprites = null;
}
private void RefreshHeadSprites()
{
HeadSprite = null;
AttachmentSprites = null;
}
// This could maybe be a LookUp instead?
private readonly Dictionary<StatTypes, List<SavedStatValue>> savedStatValues = new Dictionary<StatTypes, List<SavedStatValue>>();
@@ -1541,7 +1726,7 @@ namespace Barotrauma
}
}
public void ChangeSavedStatValue(StatTypes statType, float value, string statIdentifier, bool removeOnDeath, bool removeAfterRound = false, float maxValue = float.MaxValue)
public void ChangeSavedStatValue(StatTypes statType, float value, string statIdentifier, bool removeOnDeath, bool removeAfterRound = false, float maxValue = float.MaxValue, bool setValue = false)
{
if (!savedStatValues.ContainsKey(statType))
{
@@ -1550,7 +1735,7 @@ namespace Barotrauma
if (savedStatValues[statType].FirstOrDefault(s => s.StatIdentifier == statIdentifier) is SavedStatValue savedStat)
{
savedStat.StatValue = MathHelper.Min(savedStat.StatValue + value, maxValue);
savedStat.StatValue = setValue ? value : MathHelper.Min(savedStat.StatValue + value, maxValue);
}
else
{
@@ -628,6 +628,11 @@ namespace Barotrauma
Description = TextManager.Get("AfflictionDescription." + translationId, true) ?? element.GetAttributeString("description", "");
IsBuff = element.GetAttributeBool("isbuff", false);
if (element.Attribute("nameidentifier") != null)
{
Name = TextManager.Get(element.GetAttributeString("nameidentifier", string.Empty), returnNull: true) ?? Name;
}
LimbSpecific = element.GetAttributeBool("limbspecific", false);
if (!LimbSpecific)
{
@@ -669,6 +674,15 @@ namespace Barotrauma
case "afflictionoverlay":
AfflictionOverlay = new Sprite(subElement);
break;
case "statvalue":
DebugConsole.ThrowError($"Error in affliction \"{Identifier}\" - stat values should be configured inside the affliction's effects.");
break;
case "effect":
case "periodiceffect":
break;
default:
DebugConsole.AddWarning($"Unrecognized element in affliction \"{Identifier}\" ({subElement.Name})");
break;
}
}
@@ -979,7 +979,7 @@ namespace Barotrauma
float minSuitability = -10, maxSuitability = 10;
foreach (Affliction affliction in getAfflictions(limb))
{
if (affliction.Strength < affliction.Prefab.TreatmentThreshold) { continue; }
if (affliction.Strength <= affliction.Prefab.TreatmentThreshold) { continue; }
if (ignoreHiddenAfflictions && affliction.Strength < affliction.Prefab.ShowIconThreshold) { continue; }
foreach (KeyValuePair<string, float> treatment in affliction.Prefab.TreatmentSuitability)
{
@@ -1088,7 +1088,7 @@ namespace Barotrauma
/// Automatically filters out buffs.
/// </summary>
public static IEnumerable<Affliction> SortAfflictionsBySeverity(IEnumerable<Affliction> afflictions, bool excludeBuffs = true) =>
afflictions.Where(a => !excludeBuffs || !a.Prefab.IsBuff).OrderByDescending(a => a.DamagePerSecond).ThenByDescending(a => a.Strength);
afflictions.Where(a => !excludeBuffs || !a.Prefab.IsBuff).OrderByDescending(a => a.DamagePerSecond).ThenByDescending(a => a.Strength / a.Prefab.MaxStrength);
public void Save(XElement healthElement)
{
@@ -275,7 +275,8 @@ namespace Barotrauma
Skills.Sort((x,y) => y.LevelRange.X.CompareTo(x.LevelRange.X));
ClothingElement = element.GetChildElement("PortraitClothing");
// Disabled on purpose, TODO: remove all references?
//ClothingElement = element.GetChildElement("PortraitClothing");
}
@@ -744,7 +744,7 @@ namespace Barotrauma
}
if (attacker != null)
{
var abilityAffliction = new AbilityAffliction(newAffliction);
var abilityAffliction = new AbilityAfflictionCharacter(newAffliction, character);
attacker.CheckTalents(AbilityEffectType.OnAddDamageAffliction, abilityAffliction);
}
if (applyAffliction)
@@ -14,6 +14,7 @@ namespace Barotrauma
NotDefined,
Walk,
Run,
Crouch,
SwimSlow,
SwimFast
}
@@ -56,12 +57,15 @@ namespace Barotrauma
{
[Serialize(25.0f, true, description: "Turning speed (or rather a force applied on the main collider to make it turn). Note that you can set a limb-specific steering forces too (additional)."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)]
public float SteerTorque { get; set; }
[Serialize(25.0f, true, description: "How much torque is used to move the legs."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)]
public float LegTorque { get; set; }
}
abstract class AnimationParams : EditableParams, IMemorizable<AnimationParams>
{
public string SpeciesName { get; private set; }
public bool IsGroundedAnimation => AnimationType == AnimationType.Walk || AnimationType == AnimationType.Run;
public bool IsGroundedAnimation => AnimationType == AnimationType.Walk || AnimationType == AnimationType.Run || AnimationType == AnimationType.Crouch;
public bool IsSwimAnimation => AnimationType == AnimationType.SwimSlow || AnimationType == AnimationType.SwimFast;
protected static Dictionary<string, Dictionary<string, AnimationParams>> allAnimations = new Dictionary<string, Dictionary<string, AnimationParams>>();
@@ -110,8 +114,18 @@ namespace Barotrauma
}
}
}
public float TorsoAngleInRadians { get; private set; } = float.NaN;
[Serialize(50.0f, true, description: "How much torque is used to rotate the head to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)]
public float HeadTorque { get; set; }
[Serialize(50.0f, true, description: "How much torque is used to rotate the torso to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)]
public float TorsoTorque { get; set; }
[Serialize(25.0f, true, description: "How much torque is used to rotate the feet to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)]
public float FootTorque { get; set; }
[Serialize(AnimationType.NotDefined, true), Editable]
public virtual AnimationType AnimationType { get; protected set; }
@@ -402,6 +416,8 @@ namespace Barotrauma
return typeof(HumanWalkParams);
case AnimationType.Run:
return typeof(HumanRunParams);
case AnimationType.Crouch:
return typeof(HumanCrouchParams);
case AnimationType.SwimSlow:
return typeof(HumanSwimSlowParams);
case AnimationType.SwimFast:
@@ -87,18 +87,9 @@ namespace Barotrauma
[Serialize(8.0f, true, description: "How much force is used to move the feet to the correct position."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
public float FootMoveForce { get; set; }
[Serialize(50.0f, true, description: "How much torque is used to rotate the head to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)]
public float HeadTorque { get; set; }
[Serialize(50.0f, true, description: "How much torque is used to rotate the torso to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)]
public float TorsoTorque { get; set; }
[Serialize(50.0f, true, description: "How much torque is used to rotate the tail to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)]
public float TailTorque { get; set; }
[Serialize(25.0f, true, description: "How much torque is used to rotate the feet to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)]
public float FootTorque { get; set; }
[Serialize(0.0f, true, description: "Optional torque that's constantly applied to legs."), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
public float LegTorque { get; set; }
@@ -173,20 +164,12 @@ namespace Barotrauma
[Editable, Serialize(true, true, description: "Should the character face towards the direction it's heading.")]
public bool RotateTowardsMovement { get; set; }
[Serialize(25.0f, true, description: "How much torque is used to rotate the torso to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 2000, ValueStep = 1)]
public float TorsoTorque { get; set; }
[Serialize(25.0f, true, description: "How much torque is used to rotate the head to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 2000, ValueStep = 1)]
public float HeadTorque { get; set; }
[Serialize(50.0f, true, description: "How much torque is used to rotate the tail to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 2000, ValueStep = 1)]
public float TailTorque { get; set; }
[Serialize(1f, true, description: "Multiplier applied based on the angle difference between the tail and the main limb. Increasing the value prevents snake-like characters from getting tangled on themselves. Default = 1 (no boost)"), Editable(MinValueFloat = 1, MaxValueFloat = 100)]
public float TailTorqueMultiplier { get; set; }
[Serialize(25.0f, true, description: "How much torque is used to rotate the feet to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)]
public float FootTorque { get; set; }
[Serialize(null, true), Editable]
public string FootAngles
@@ -224,10 +207,7 @@ namespace Barotrauma
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; }
bool Flip { get; set; }
float FlipCooldown { get; set; }
float FlipDelay { get; set; }
@@ -24,6 +24,17 @@ namespace Barotrauma
public override void StoreSnapshot() => StoreSnapshot<HumanRunParams>();
}
class HumanCrouchParams : HumanGroundedParams
{
public static HumanCrouchParams GetDefaultAnimParams(Character character) => GetDefaultAnimParams<HumanCrouchParams>(character, AnimationType.Crouch);
public static HumanCrouchParams GetAnimParams(Character character, string fileName = null)
{
return GetAnimParams<HumanCrouchParams>(character.SpeciesName, AnimationType.Crouch, fileName);
}
public override void StoreSnapshot() => StoreSnapshot<HumanCrouchParams>();
}
class HumanSwimFastParams: HumanSwimParams
{
public static HumanSwimFastParams GetDefaultAnimParams(Character character) => GetDefaultAnimParams<HumanSwimFastParams>(character, AnimationType.SwimFast);
@@ -58,9 +69,6 @@ namespace Barotrauma
[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; }
@@ -81,36 +89,23 @@ namespace Barotrauma
}
public float FootAngleInRadians { get; private set; }
[Serialize(25.0f, true, description: "How much torque is used to rotate the feet to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
public float FootRotateStrength { get; set; }
[Serialize(1f, true, description: "How much force is used to move the arms."), Editable(MinValueFloat = 0, MaxValueFloat = 20, DecimalCount = 2)]
public float ArmMoveStrength { get; set; }
[Serialize(1f, true, description: "How much force is used to move the hands."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
public float HandMoveStrength { get; set; }
[Serialize(1f, true, description: "How much force is used to rotate the arms to the IK position."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
public float ArmIKStrength { get; set; }
[Serialize(1f, true, description: "How much force is used to rotate the hands to the IK position."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
public float HandIKStrength { get; set; }
}
abstract class HumanGroundedParams : GroundedMovementParams, IHumanAnimation
{
[Serialize(0.3f, true, description: "How much force is used to force the character upright."), Editable(MinValueFloat = 0, MaxValueFloat = 1, DecimalCount = 2)]
public float GetUpForce { get; set; }
// -- TODO: use a separate clip for crawling -> replace these when implemented.
[Serialize(0.65f, true, description: "Height of the torso when crouching."), Editable(MinValueFloat = 0, MaxValueFloat = 5, DecimalCount = 2)]
public float CrouchingTorsoPos { get; set; }
[Serialize(0.65f, true, description: "Height of the head when crouching."), Editable(MinValueFloat = 0, MaxValueFloat = 5, DecimalCount = 2)]
public float CrouchingHeadPos { get; set; }
/// <summary>
/// In degrees
/// </summary>
[Serialize(-10f, true, description: "Angle of the torso when crouching."), Editable(MinValueFloat = -360, MaxValueFloat = 360)]
public float CrouchingTorsoAngle { get; set; }
/// <summary>
/// In degrees
/// </summary>
[Serialize(-10f, true, description: "Angle of the head when crouching."), Editable(MinValueFloat = -360, MaxValueFloat = 360)]
public float CrouchingHeadAngle { get; set; }
// --
[Serialize(0.25f, true, description: "How much the character's head leans forwards when moving."), Editable(DecimalCount = 2)]
public float HeadLeanAmount { get; set; }
@@ -121,6 +116,9 @@ namespace Barotrauma
[Serialize(15.0f, true, description: "How much force is used to move the feet to the correct position."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
public float FootMoveStrength { get; set; }
[Serialize(0f, true, description: "How much the horizontal difference of waist and the foot positions has an effect to lifting the foot."), Editable(DecimalCount = 2, ValueStep = 0.1f, MinValueFloat = 0f, MaxValueFloat = 1f)]
public float FootLiftHorizontalFactor { get; set; }
/// <summary>
/// In degrees.
/// </summary>
@@ -135,15 +133,9 @@ namespace Barotrauma
}
public float FootAngleInRadians { get; private set; }
[Serialize(20.0f, true, description: "How much torque is used to rotate the feet to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
public float FootRotateStrength { get; set; }
[Serialize("0.0, 0.0", true, description: "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."), Editable(DecimalCount = 2)]
public Vector2 FootMoveOffset { get; set; }
[Serialize("0.0, 0.0", true, description: "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."), Editable(DecimalCount = 2)]
public Vector2 CrouchingFootMoveOffset { get; set; }
[Serialize(10.0f, true, description: "How much torque is used to bend the characters legs when taking a step."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
public float LegBendTorque { get; set; }
@@ -153,17 +145,33 @@ namespace Barotrauma
[Serialize("-0.15, 0.0", true, description: "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."), Editable(DecimalCount = 2)]
public Vector2 HandMoveOffset { get; set; }
[Serialize(0.7f, true, description: "How much force is used to move the hands."), Editable(MinValueFloat = 0, MaxValueFloat = 2, DecimalCount = 2)]
public float HandMoveStrength { get; set; }
[Serialize(-1.0f, true, description: "The position of the hands is clamped below this (relative to the position of the character's torso)."), Editable(DecimalCount = 2)]
public float HandClampY { get; set; }
[Serialize(1f, true, description: "How much force is used to move the arms."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
public float ArmMoveStrength { get; set; }
[Serialize(1f, true, description: "How much force is used to move the hands."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
public float HandMoveStrength { get; set; }
[Serialize(1f, true, description: "How much force is used to rotate the arms to the IK position."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
public float ArmIKStrength { get; set; }
[Serialize(1f, true, description: "How much force is used to rotate the hands to the IK position."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
public float HandIKStrength { get; set; }
}
public interface IHumanAnimation
{
float FootAngle { get; set; }
float FootAngleInRadians { get; }
float FootRotateStrength { get; set; }
float ArmMoveStrength { get; set; }
float HandMoveStrength { get; set; }
float ArmIKStrength { get; set; }
float HandIKStrength { get; set; }
}
}
@@ -34,6 +34,9 @@ namespace Barotrauma
[Serialize("", true, description: "Default path for the limb sprite textures. Used only if the limb specific path for the limb is not defined"), Editable]
public string Texture { get; set; }
[Serialize("1.0,1.0,1.0,1.0", true), Editable()]
public Color Color { get; set; }
[Serialize(0.0f, true, description: "The orientation of the sprites as drawn on the sprite sheet. Can be overridden by setting a value for Limb's 'Sprite Orientation'. Used mainly for animations and widgets."), Editable(-360, 360)]
public float SpritesheetOrientation { get; set; }
@@ -556,7 +559,7 @@ namespace Barotrauma
}
}
public override string GenerateName() => $"Limb {ID}";
public override string GenerateName() => Type != LimbType.None ? $"{Type} ({ID})" : $"Limb {ID}";
public SpriteParams GetSprite() => deformSpriteParams ?? normalSpriteParams;
@@ -574,7 +577,7 @@ namespace Barotrauma
[Serialize("", true), Editable]
public string Notes { get; set; }
[Serialize(1f, true), Editable]
[Serialize(1f, true), Editable(DecimalCount = 2)]
public float Scale { get; set; }
[Serialize(true, true, description: "Does the limb flip when the character flips?"), Editable()]
@@ -889,6 +892,9 @@ namespace Barotrauma
[Serialize("", true), Editable()]
public string Texture { get; set; }
[Serialize(false, true), Editable()]
public bool IgnoreTint { get; set; }
[Serialize("1.0,1.0,1.0,1.0", true), Editable()]
public Color Color { get; set; }
@@ -33,6 +33,7 @@ namespace Barotrauma.Abilities
NotSelf = 3,
Alive = 4,
Monster = 5,
InFriendlySubmarine = 6,
};
protected List<TargetType> ParseTargetTypes(string[] targetTypeStrings)
@@ -80,6 +81,8 @@ namespace Barotrauma.Abilities
return !targetCharacter.IsDead;
case TargetType.Monster:
return !targetCharacter.IsHuman;
case TargetType.InFriendlySubmarine:
return targetCharacter.Submarine != null && targetCharacter.Submarine.TeamID == character.TeamID;
default:
return true;
}
@@ -0,0 +1,29 @@
using Barotrauma.Items.Components;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionAffliction : AbilityConditionData
{
private readonly string[] afflictions;
public AbilityConditionAffliction(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
afflictions = conditionElement.GetAttributeStringArray("afflictions", new string[0], convertToLowerInvariant: true);
}
protected override bool MatchesConditionSpecific(AbilityObject abilityObject)
{
if ((abilityObject as IAbilityAffliction)?.Affliction is Affliction affliction)
{
return afflictions.Any(a => a == affliction.Identifier);
}
else
{
LogAbilityConditionError(abilityObject, typeof(IAbilityAttackResult));
return false;
}
}
}
}
@@ -0,0 +1,41 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionLocation : AbilityConditionData
{
private readonly bool? hasOutpost;
private readonly string[] locationIdentifiers;
public AbilityConditionLocation(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
if (conditionElement.Attribute("hasoutpost") != null)
{
hasOutpost = conditionElement.GetAttributeBool("hasoutpost", false);
}
locationIdentifiers = conditionElement.GetAttributeStringArray("locationtype", new string[0]);
}
protected override bool MatchesConditionSpecific(AbilityObject abilityObject)
{
if (abilityObject is IAbilityLocation abilityLocation)
{
if (locationIdentifiers.Any())
{
if (!locationIdentifiers.Contains(abilityLocation.Location.Type.Identifier)) { return false; }
}
if (hasOutpost.HasValue)
{
if (hasOutpost.Value != abilityLocation.Location.HasOutpost()) { return false; }
}
return true;
}
else
{
LogAbilityConditionError(abilityObject, typeof(IAbilityItemPrefab));
return false;
}
}
}
}
@@ -20,6 +20,11 @@
public Mission Mission { get; set; }
}
interface IAbilityLocation
{
public Location Location { get; set; }
}
interface IAbilityCharacter
{
public Character Character { get; set; }
@@ -43,6 +43,17 @@ namespace Barotrauma.Abilities
public Affliction Affliction { get; set; }
}
class AbilityAfflictionCharacter : AbilityObject, IAbilityAffliction, IAbilityCharacter
{
public AbilityAfflictionCharacter(Affliction affliction, Character character)
{
Affliction = affliction;
Character = character;
}
public Character Character { get; set; }
public Affliction Affliction { get; set; }
}
class AbilityValueItem : AbilityObject, IAbilityValue, IAbilityItemPrefab
{
public AbilityValueItem(float value, ItemPrefab itemPrefab)
@@ -54,6 +65,17 @@ namespace Barotrauma.Abilities
public ItemPrefab ItemPrefab { get; set; }
}
class AbilityItemPrefabItem : AbilityObject, IAbilityItem, IAbilityItemPrefab
{
public AbilityItemPrefabItem(Item item, ItemPrefab itemPrefab)
{
Item = item;
ItemPrefab = itemPrefab;
}
public Item Item { get; set; }
public ItemPrefab ItemPrefab { get; set; }
}
class AbilityValueString : AbilityObject, IAbilityValue, IAbilityString
{
public AbilityValueString(float value, string abilityString)
@@ -65,7 +87,7 @@ namespace Barotrauma.Abilities
public string String { get; set; }
}
class AbilityValueStringCharacter : AbilityObject, IAbilityValue, IAbilityString
class AbilityValueStringCharacter : AbilityObject, IAbilityValue, IAbilityString, IAbilityCharacter
{
public AbilityValueStringCharacter(float value, string abilityString, Character character)
{
@@ -111,6 +133,16 @@ namespace Barotrauma.Abilities
public Mission Mission { get; set; }
}
class AbilityLocation : AbilityObject, IAbilityLocation
{
public AbilityLocation(Location location)
{
Location = location;
}
public Location Location { get; set; }
}
// this is an exception class that should only be passed in this form, so classes that use it should cast into it directly
class AbilityAttackData : AbilityObject, IAbilityCharacter
{
@@ -1,5 +1,4 @@
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
@@ -9,10 +8,12 @@ namespace Barotrauma.Abilities
class CharacterAbilityApplyStatusEffectsToAllies : CharacterAbilityApplyStatusEffects
{
private readonly bool allowSelf;
private readonly float maxDistance = float.MaxValue;
public CharacterAbilityApplyStatusEffectsToAllies(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
allowSelf = abilityElement.GetAttributeBool("allowself", true);
maxDistance = abilityElement.GetAttributeFloat("maxdistance", float.MaxValue);
}
@@ -22,6 +23,10 @@ namespace Barotrauma.Abilities
foreach (Character character in chosenCharacters)
{
if (maxDistance < float.MaxValue)
{
if (Vector2.DistanceSquared(character.WorldPosition, Character.WorldPosition) > maxDistance * maxDistance) { continue; }
}
ApplyEffectSpecific(character);
}
}
@@ -1,5 +1,4 @@
using Microsoft.Xna.Framework;
using System.Xml.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
@@ -8,7 +7,7 @@ namespace Barotrauma.Abilities
public override bool AppliesEffectOnIntervalUpdate => true;
private readonly int amount;
private StatTypes scalingStatType;
private readonly StatTypes scalingStatType;
public CharacterAbilityGiveMoney(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
@@ -13,6 +13,7 @@ namespace Barotrauma.Abilities
private readonly bool removeOnDeath;
private readonly bool removeAfterRound;
private readonly bool giveOnAddingFirstTime;
private readonly bool setValue;
//private readonly float maximumValue;
@@ -28,6 +29,7 @@ namespace Barotrauma.Abilities
removeOnDeath = abilityElement.GetAttributeBool("removeondeath", true);
removeAfterRound = abilityElement.GetAttributeBool("removeafterround", false);
giveOnAddingFirstTime = abilityElement.GetAttributeBool("giveonaddingfirsttime", characterAbilityGroup.AbilityEffectType == AbilityEffectType.None);
setValue = abilityElement.GetAttributeBool("setvalue", false);
}
public override void InitializeAbility(bool addingFirstTime)
@@ -52,11 +54,11 @@ namespace Barotrauma.Abilities
{
if (targetAllies)
{
Character.GetFriendlyCrew(Character).ForEach(c => c?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, removeAfterRound, maxValue));
Character.GetFriendlyCrew(Character).ForEach(c => c?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, removeAfterRound, maxValue, setValue));
}
else
{
Character?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, removeAfterRound, maxValue);
Character?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, removeAfterRound, maxValue, setValue);
}
}
}
@@ -9,7 +9,7 @@ namespace Barotrauma.Abilities
public CharacterAbilityGiveResistance(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
resistanceId = abilityElement.GetAttributeString("resistanceid", "");
resistanceId = abilityElement.GetAttributeString("resistanceid", abilityElement.GetAttributeString("resistance", string.Empty));
multiplier = abilityElement.GetAttributeFloat("multiplier", 1f);
if (string.IsNullOrEmpty(resistanceId))
@@ -0,0 +1,35 @@
using Microsoft.Xna.Framework;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityModifyStatToLevel : CharacterAbility
{
private readonly StatTypes statType;
private readonly float statPerLevel;
private readonly int maxLevel;
private float lastValue = 0f;
public CharacterAbilityModifyStatToLevel(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
statPerLevel = abilityElement.GetAttributeFloat("statperlevel", 0f);
maxLevel = abilityElement.GetAttributeInt("maxlevel", int.MaxValue);
}
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
{
Character.ChangeStat(statType, -lastValue);
if (conditionsMatched)
{
int level = MathHelper.Min(Character?.Info.GetCurrentLevel() ?? 0, maxLevel);
lastValue = statPerLevel * level;
Character.ChangeStat(statType, lastValue);
}
else
{
lastValue = 0f;
}
}
}
}
@@ -10,19 +10,24 @@ namespace Barotrauma.Abilities
private readonly List<StatusEffect> statusEffects;
private readonly List<Item> openedContainers = new List<Item>();
private readonly float randomChance;
private readonly bool oncePerContainer;
public CharacterAbilitySpawnItemsToContainer(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
randomChance = abilityElement.GetAttributeFloat("randomchance", 1f);
oncePerContainer = abilityElement.GetAttributeBool("oncepercontainer", false);
}
protected override void ApplyEffect(AbilityObject abilityObject)
{
if ((abilityObject as IAbilityItem)?.Item is Item item)
{
if (openedContainers.Contains(item)) { return; }
openedContainers.Add(item);
if (oncePerContainer)
{
if (openedContainers.Contains(item)) { return; }
openedContainers.Add(item);
}
if (randomChance < Rand.Range(0f, 1f, Rand.RandSync.Unsynced)) { return; }
foreach (var statusEffect in statusEffects)
@@ -0,0 +1,30 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityUnlockTree : CharacterAbility
{
public CharacterAbilityUnlockTree(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
}
public override void InitializeAbility(bool addingFirstTime)
{
if (!addingFirstTime) { return; }
if (!TalentTree.JobTalentTrees.TryGetValue(Character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; }
var subTree = talentTree.TalentSubTrees.Find(t => t.TalentOptionStages.Any(ts => ts.Talents.Contains(CharacterTalent.Prefab)));
if (subTree != null)
{
foreach (var talentOption in subTree.TalentOptionStages)
{
foreach (var talent in talentOption.Talents)
{
Character.GiveTalent(talent);
}
}
}
}
}
}
@@ -4,14 +4,14 @@ using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityEnigmaMachine : CharacterAbility
class CharacterAbilityAtmosMachine : CharacterAbility
{
private readonly float addedValue;
private readonly float multiplyValue;
private readonly string[] tags;
private readonly int maxMultiplyCount;
public CharacterAbilityEnigmaMachine(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
public CharacterAbilityAtmosMachine(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f);
multiplyValue = abilityElement.GetAttributeFloat("multiplyvalue", 1f);
@@ -1,42 +0,0 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityStonewall : CharacterAbility
{
private readonly List<StatusEffect> statusEffects;
private readonly List<StatusEffect> statusEffectsReset;
private readonly int maxEnemyCount;
private readonly float squaredDistance;
public CharacterAbilityStonewall(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
statusEffectsReset = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffectsreset"));
maxEnemyCount = abilityElement.GetAttributeInt("maxenemycount", 0);
squaredDistance = MathF.Pow(abilityElement.GetAttributeFloat("distance", 0), 2);
}
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
{
int numberOfEnemiesInRange = Character.CharacterList.Count(c => !HumanAIController.IsFriendly(Character, c) && !c.IsDead && Vector2.DistanceSquared(Character.WorldPosition, c.WorldPosition) < squaredDistance);
foreach (var statusEffect in statusEffectsReset)
{
statusEffect.Apply(ActionType.OnAbility, 1f, Character, Character);
}
if (conditionsMatched && numberOfEnemiesInRange > 0)
{
foreach (var statusEffect in statusEffects)
{
statusEffect.Apply(ActionType.OnAbility, Math.Min(numberOfEnemiesInRange, maxEnemyCount), Character, Character);
}
}
}
}
}
@@ -23,6 +23,7 @@ namespace Barotrauma.Abilities
characterAbility.ApplyAbilityEffect(abilityObject);
}
}
timesTriggered++;
}
}
@@ -39,6 +39,10 @@ namespace Barotrauma.Abilities
characterAbility.UpdateCharacterAbility(conditionsMatched, TimeSinceLastUpdate);
}
}
if (conditionsMatched)
{
timesTriggered++;
}
TimeSinceLastUpdate = 0;
}
}
@@ -34,7 +34,7 @@ namespace Barotrauma
if (string.IsNullOrEmpty(jobIdentifier))
{
DebugConsole.ThrowError("No job defined for talent tree!");
DebugConsole.ThrowError($"No job defined for talent tree in \"{filePath}\"!");
return;
}