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;
}
@@ -56,9 +56,10 @@
OnAllyGainMissionExperience,
OnGainMissionExperience,
OnGainMissionMoney,
OnLocationDiscovered,
OnItemDeconstructed,
OnItemDeconstructedMaterial,
OnItemDeconstructedRetainProbability,
OnItemDeconstructedInventory,
OnStopTinkering,
OnItemPicked,
AfterSubmarineAttacked,
@@ -96,7 +97,6 @@
// Utility
RepairSpeed,
DeconstructorSpeedMultiplier,
TinkeringDuration,
RepairToolStructureRepairMultiplier,
RepairToolStructureDamageMultiplier,
RepairToolDeattachTimeMultiplier,
@@ -105,6 +105,10 @@
GeneticMaterialRefineBonus,
GeneticMaterialTaintedProbabilityReductionOnCombine,
SkillGainSpeed,
// Tinker
TinkeringDuration,
TinkeringStrength,
TinkeringDamage,
// Misc
ReputationGainMultiplier,
MissionMoneyGainMultiplier,
@@ -114,6 +118,7 @@
Coauthor,
WarriorPoetMissionRuns,
WarriorPoetEnemiesKilled,
QuickfixRepairCount,
}
public enum AbilityFlags
@@ -868,6 +868,7 @@ namespace Barotrauma
{
if (level == null) { return 0.0f; }
var refEntity = GetRefEntity();
if (refEntity == null) { return 0.0f; }
Vector2 target = ConvertUnits.ToSimUnits(level.EndPosition);
var steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(refEntity.WorldPosition), target);
if (steeringPath.Unreachable || float.IsPositiveInfinity(totalPathLength))
@@ -9,10 +9,10 @@ namespace Barotrauma
{
partial class MineralMission : Mission
{
private Dictionary<string, Pair<int, float>> ResourceClusters { get; } = new Dictionary<string, Pair<int, float>>();
private Dictionary<string, List<Item>> SpawnedResources { get; } = new Dictionary<string, List<Item>>();
private Dictionary<string, Item[]> RelevantLevelResources { get; } = new Dictionary<string, Item[]>();
private List<Tuple<string, Vector2>> MissionClusterPositions { get; } = new List<Tuple<string, Vector2>>();
private readonly Dictionary<string, (int amount, float rotation)> resourceClusters = new Dictionary<string, (int amount, float rotation)>();
private readonly Dictionary<string, List<Item>> spawnedResources = new Dictionary<string, List<Item>>();
private readonly Dictionary<string, Item[]> relevantLevelResources = new Dictionary<string, Item[]>();
private readonly List<Tuple<string, Vector2>> missionClusterPositions = new List<Tuple<string, Vector2>>();
private readonly HashSet<Level.Cave> caves = new HashSet<Level.Cave>();
@@ -20,8 +20,8 @@ namespace Barotrauma
{
get
{
return MissionClusterPositions
.Where(p => SpawnedResources.ContainsKey(p.Item1) && AnyAreUncollected(SpawnedResources[p.Item1]))
return missionClusterPositions
.Where(p => spawnedResources.ContainsKey(p.Item1) && AnyAreUncollected(spawnedResources[p.Item1]))
.Select(p => p.Item2);
}
}
@@ -33,53 +33,53 @@ namespace Barotrauma
{
var identifier = c.GetAttributeString("identifier", null);
if (string.IsNullOrWhiteSpace(identifier)) { continue; }
if (ResourceClusters.ContainsKey(identifier))
if (resourceClusters.ContainsKey(identifier))
{
ResourceClusters[identifier].First++;
resourceClusters[identifier] = (resourceClusters[identifier].amount + 1, resourceClusters[identifier].rotation);
}
else
{
ResourceClusters.Add(identifier, new Pair<int, float>(1, 0.0f));
resourceClusters.Add(identifier, (1, 0.0f));
}
}
}
protected override void StartMissionSpecific(Level level)
{
if (SpawnedResources.Any())
if (spawnedResources.Any())
{
#if DEBUG
throw new Exception($"SpawnedResources.Count > 0 ({SpawnedResources.Count})");
throw new Exception($"SpawnedResources.Count > 0 ({spawnedResources.Count})");
#else
DebugConsole.AddWarning("Spawned resources list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds.");
SpawnedResources.Clear();
spawnedResources.Clear();
#endif
}
if (RelevantLevelResources.Any())
if (relevantLevelResources.Any())
{
#if DEBUG
throw new Exception($"RelevantLevelResources.Count > 0 ({RelevantLevelResources.Count})");
throw new Exception($"RelevantLevelResources.Count > 0 ({relevantLevelResources.Count})");
#else
DebugConsole.AddWarning("Relevant level resources list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds.");
RelevantLevelResources.Clear();
relevantLevelResources.Clear();
#endif
}
if (MissionClusterPositions.Any())
if (missionClusterPositions.Any())
{
#if DEBUG
throw new Exception($"MissionClusterPositions.Count > 0 ({MissionClusterPositions.Count})");
throw new Exception($"MissionClusterPositions.Count > 0 ({missionClusterPositions.Count})");
#else
DebugConsole.AddWarning("Mission cluster positions list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds.");
MissionClusterPositions.Clear();
missionClusterPositions.Clear();
#endif
}
caves.Clear();
if (IsClient) { return; }
foreach (var kvp in ResourceClusters)
foreach (var kvp in resourceClusters)
{
var prefab = ItemPrefab.Find(null, kvp.Key);
if (prefab == null)
@@ -88,15 +88,14 @@ namespace Barotrauma
"couldn't find an item prefab with the identifier " + kvp.Key);
continue;
}
var spawnedResources = level.GenerateMissionResources(prefab, kvp.Value.First, out float rotation);
if (spawnedResources.Count < kvp.Value.First)
var spawnedResources = level.GenerateMissionResources(prefab, kvp.Value.amount, out float rotation);
if (spawnedResources.Count < kvp.Value.amount)
{
DebugConsole.ThrowError("Error in MineralMission - " +
"spawned " + spawnedResources.Count + "/" + kvp.Value.First + " of " + prefab.Name);
"spawned " + spawnedResources.Count + "/" + kvp.Value.amount + " of " + prefab.Name);
}
if (spawnedResources.None()) { continue; }
SpawnedResources.Add(kvp.Key, spawnedResources);
kvp.Value.Second = rotation;
this.spawnedResources.Add(kvp.Key, spawnedResources);
foreach (Level.Cave cave in Level.Loaded.Caves)
{
@@ -142,7 +141,7 @@ namespace Barotrauma
GiveReward();
completed = true;
}
foreach (var kvp in SpawnedResources)
foreach (var kvp in spawnedResources)
{
foreach (var i in kvp.Value)
{
@@ -152,33 +151,33 @@ namespace Barotrauma
}
}
}
SpawnedResources.Clear();
RelevantLevelResources.Clear();
MissionClusterPositions.Clear();
spawnedResources.Clear();
relevantLevelResources.Clear();
missionClusterPositions.Clear();
failed = !completed && state > 0;
}
private void FindRelevantLevelResources()
{
RelevantLevelResources.Clear();
foreach (var identifier in ResourceClusters.Keys)
relevantLevelResources.Clear();
foreach (var identifier in resourceClusters.Keys)
{
var items = Item.ItemList.Where(i => i.Prefab.Identifier == identifier &&
i.Submarine == null && i.ParentInventory == null &&
(!(i.GetComponent<Holdable>() is Holdable h) || (h.Attachable && h.Attached)))
.ToArray();
RelevantLevelResources.Add(identifier, items);
relevantLevelResources.Add(identifier, items);
}
}
private bool EnoughHaveBeenCollected()
{
foreach (var kvp in ResourceClusters)
foreach (var kvp in resourceClusters)
{
if (RelevantLevelResources.TryGetValue(kvp.Key, out var availableResources))
if (relevantLevelResources.TryGetValue(kvp.Key, out var availableResources))
{
var collected = availableResources.Count(r => HasBeenCollected(r));
var needed = kvp.Value.First;
var needed = kvp.Value.amount;
if (collected < needed) { return false; }
}
else
@@ -210,8 +209,8 @@ namespace Barotrauma
private void CalculateMissionClusterPositions()
{
MissionClusterPositions.Clear();
foreach (var kvp in SpawnedResources)
missionClusterPositions.Clear();
foreach (var kvp in spawnedResources)
{
if (kvp.Value.None()) { continue; }
var pos = Vector2.Zero;
@@ -222,7 +221,7 @@ namespace Barotrauma
itemCount++;
}
pos /= itemCount;
MissionClusterPositions.Add(new Tuple<string, Vector2>(kvp.Key, pos));
missionClusterPositions.Add(new Tuple<string, Vector2>(kvp.Key, pos));
}
}
}
@@ -180,7 +180,11 @@ namespace Barotrauma
var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(patrolPos), ConvertUnits.ToSimUnits(preferredSpawnPos));
if (!path.Unreachable)
{
preferredSpawnPos = path.Nodes[Rand.Range(0, path.Nodes.Count - 1)].WorldPosition; // spawn the sub in a random point in the path if possible
var validNodes = path.Nodes.FindAll(n => !Level.Loaded.ExtraWalls.Any(w => w.Cells.Any(c => c.IsPointInside(n.WorldPosition))));
if (validNodes.Any())
{
preferredSpawnPos = validNodes.GetRandom().WorldPosition; // spawn the sub in a random point in the path if possible
}
}
int graceDistance = 500; // the sub still spawns awkwardly close to walls, so this helps. could also be given as a parameter instead
@@ -0,0 +1,24 @@
using Microsoft.Xna.Framework;
namespace Barotrauma.Extensions
{
public static class ColorExtensions
{
public static Color Multiply(this Color color, float value, bool onlyAlpha = false)
{
return onlyAlpha ?
new Color(color.R, color.G, color.B, (byte)(color.A * value)) :
new Color((byte)(color.R * value), (byte)(color.G * value), (byte)(color.B * value), (byte)(color.A * value));
}
public static Color Multiply(this Color thisColor, Color color)
{
return new Color((byte)(thisColor.R * color.R / 255f), (byte)(thisColor.G * color.G / 255f), (byte)(thisColor.B * color.B / 255f), (byte)(thisColor.A * color.A / 255f));
}
public static Color Opaque(this Color color)
{
return new Color(color.R, color.G, color.B, (byte)255);
}
}
}
@@ -659,16 +659,7 @@ namespace Barotrauma
}
foreach (Location location in Map.Locations)
{
if (location.Type != location.OriginalType)
{
location.ChangeType(location.OriginalType);
location.PendingLocationTypeChange = null;
}
location.CreateStore(force: true);
location.ClearMissions();
location.Discovered = false;
location.LevelData?.EventHistory?.Clear();
location.UnlockInitialMissions();
location.Reset();
}
Map.SetLocation(Map.Locations.IndexOf(Map.StartLocation));
Map.SelectLocation(-1);
@@ -452,9 +452,11 @@ namespace Barotrauma
StatusEffect.StopAll();
#if CLIENT
#if !DEBUG
GameMain.LightManager.LosEnabled = GameMain.Client == null || GameMain.Client.CharacterInfo != null;
#endif
if (GameMain.LightManager.LosEnabled) { GameMain.LightManager.LosAlpha = 1f; }
if (GameMain.Client == null) GameMain.LightManager.LosMode = GameMain.Config.LosMode;
if (GameMain.Client == null) { GameMain.LightManager.LosMode = GameMain.Config.LosMode; }
#endif
LevelData = level?.LevelData;
Level = level;
@@ -661,6 +663,7 @@ namespace Barotrauma
#if SERVER
return GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c?.Info != null);
#else
if (GameMain.GameSession == null) { return Enumerable.Empty<Character>(); }
return GameMain.GameSession.CrewManager.GetCharacters().Where(c => c?.Info != null);
#endif
}
@@ -143,14 +143,7 @@ namespace Barotrauma
return true;
}
public int CharacterHeadIndex { get; set; }
public int CharacterHairIndex { get; set; }
public int CharacterBeardIndex { get; set; }
public int CharacterMoustacheIndex { get; set; }
public int CharacterFaceAttachmentIndex { get; set; }
public Gender CharacterGender { get; set; }
public Race CharacterRace { get; set; }
internal CharacterInfo.HeadInfo PlayerCharacterCustomization { get; set; }
private float aimAssistAmount;
public float AimAssistAmount
@@ -855,171 +848,6 @@ namespace Barotrauma
UnsavedSettings = false;
}
private void SaveNewDefaultConfig()
{
XDocument doc = new XDocument();
if (doc.Root == null)
{
doc.Add(new XElement("config"));
}
doc.Root.Add(
new XAttribute("language", TextManager.Language),
new XAttribute("masterserverurl", MasterServerUrl),
new XAttribute("remotecontenturl", RemoteContentUrl),
new XAttribute("autocheckupdates", AutoCheckUpdates),
new XAttribute("musicvolume", musicVolume),
new XAttribute("soundvolume", soundVolume),
new XAttribute("microphonevolume", microphoneVolume),
new XAttribute("voicechatvolume", voiceChatVolume),
new XAttribute("voicechatcutoffprevention", VoiceChatCutoffPrevention),
new XAttribute("verboselogging", VerboseLogging),
new XAttribute("savedebugconsolelogs", SaveDebugConsoleLogs),
new XAttribute("submarineautosave", EnableSubmarineAutoSave),
new XAttribute("maxautosaves", MaximumAutoSaves),
new XAttribute("autosaveintervalseconds", AutoSaveIntervalSeconds),
new XAttribute("subeditorbackground", XMLExtensions.ColorToString(SubEditorBackgroundColor)),
new XAttribute("subeditorundobuffer", SubEditorMaxUndoBuffer),
new XAttribute("enablesplashscreen", EnableSplashScreen),
new XAttribute("usesteammatchmaking", UseSteamMatchmaking),
new XAttribute("quickstartsub", QuickStartSubmarineName),
new XAttribute("requiresteamauthentication", RequireSteamAuthentication),
new XAttribute("aimassistamount", aimAssistAmount),
new XAttribute("tutorialskipwarning", ShowTutorialSkipWarning));
if (!ShowUserStatisticsPrompt)
{
doc.Root.Add(new XAttribute("senduserstatistics", sendUserStatistics));
}
XElement gMode = doc.Root.Element("graphicsmode");
if (gMode == null)
{
gMode = new XElement("graphicsmode");
doc.Root.Add(gMode);
}
if (GraphicsWidth == 0 || GraphicsHeight == 0)
{
gMode.ReplaceAttributes(new XAttribute("displaymode", windowMode));
}
else
{
gMode.ReplaceAttributes(
new XAttribute("width", GraphicsWidth),
new XAttribute("height", GraphicsHeight),
new XAttribute("vsync", VSyncEnabled),
new XAttribute("framelimit", Timing.FrameLimit),
new XAttribute("displaymode", windowMode));
}
XElement gSettings = doc.Root.Element("graphicssettings");
if (gSettings == null)
{
gSettings = new XElement("graphicssettings");
doc.Root.Add(gSettings);
}
gSettings.ReplaceAttributes(
new XAttribute("particlelimit", ParticleLimit),
new XAttribute("lightmapscale", LightMapScale),
new XAttribute("chromaticaberration", ChromaticAberrationEnabled),
new XAttribute("losmode", LosMode),
new XAttribute("hudscale", HUDScale),
new XAttribute("inventoryscale", InventoryScale));
foreach (ContentPackage contentPackage in ContentPackage.CorePackages)
{
if (contentPackage.Path.Contains(VanillaContentPackagePath))
{
doc.Root.Add(new XElement("contentpackages", new XElement("core", new XAttribute("name", contentPackage.Name))));
break;
}
}
#if CLIENT
var keyMappingElement = new XElement("keymapping");
doc.Root.Add(keyMappingElement);
for (int i = 0; i < keyMapping.Length; i++)
{
KeyOrMouse bind = keyMapping[i];
if (bind.MouseButton == MouseButton.None)
{
keyMappingElement.Add(new XAttribute(((InputType)i).ToString(), bind.Key));
}
else
{
keyMappingElement.Add(new XAttribute(((InputType)i).ToString(), bind.MouseButton));
}
}
var inventoryKeyMappingElement = new XElement("inventorykeymapping");
doc.Root.Add(inventoryKeyMappingElement);
for (int i = 0; i < inventoryKeyMapping.Length; i++)
{
KeyOrMouse bind = inventoryKeyMapping[i];
if (bind.MouseButton == MouseButton.None)
{
inventoryKeyMappingElement.Add(new XAttribute($"slot{i}", bind.Key));
}
else
{
inventoryKeyMappingElement.Add(new XAttribute($"slot{i}", bind.MouseButton));
}
}
#endif
var gameplay = new XElement("gameplay");
var jobPreferences = new XElement("jobpreferences");
foreach (Pair<string, int> job in JobPreferences)
{
XElement jobElement = new XElement("job");
jobElement.Add(new XAttribute("identifier", job.First));
jobElement.Add(new XAttribute("variant", job.Second));
jobPreferences.Add(jobElement);
}
gameplay.Add(jobPreferences);
var teamPreference = new XElement("teampreference");
teamPreference.Add(new XAttribute("team", TeamPreference.ToString()));
gameplay.Add(teamPreference);
doc.Root.Add(gameplay);
var playerElement = new XElement("player",
new XAttribute("name", playerName ?? ""),
new XAttribute("headindex", CharacterHeadIndex),
new XAttribute("gender", CharacterGender),
new XAttribute("race", CharacterRace),
new XAttribute("hairindex", CharacterHairIndex),
new XAttribute("beardindex", CharacterBeardIndex),
new XAttribute("moustacheindex", CharacterMoustacheIndex),
new XAttribute("faceattachmentindex", CharacterFaceAttachmentIndex));
doc.Root.Add(playerElement);
System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings
{
Indent = true,
OmitXmlDeclaration = true,
NewLineOnAttributes = true
};
try
{
using (var writer = XmlWriter.Create(SavePath, settings))
{
doc.WriteTo(writer);
writer.Flush();
}
}
catch (Exception e)
{
DebugConsole.ThrowError("Saving game settings failed.", e);
GameAnalyticsManager.AddErrorEventOnce("GameSettings.Save:SaveFailed", GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Saving game settings failed.\n" + e.Message + "\n" + e.StackTrace.CleanupStackTrace());
}
}
#region Load PlayerConfig
public void LoadPlayerConfig()
{
@@ -1307,15 +1135,20 @@ namespace Barotrauma
gameplay.Add(jobPreferences);
doc.Root.Add(gameplay);
var playerElement = new XElement("player",
new XAttribute("name", playerName ?? ""),
new XAttribute("headindex", CharacterHeadIndex),
new XAttribute("gender", CharacterGender),
new XAttribute("race", CharacterRace),
new XAttribute("hairindex", CharacterHairIndex),
new XAttribute("beardindex", CharacterBeardIndex),
new XAttribute("moustacheindex", CharacterMoustacheIndex),
new XAttribute("faceattachmentindex", CharacterFaceAttachmentIndex));
var playerElement = new XElement("player", new XAttribute("name", playerName ?? ""));
if (PlayerCharacterCustomization != null)
{
playerElement.SetAttributeValue("headindex", PlayerCharacterCustomization.HeadSpriteId);
playerElement.SetAttributeValue("gender", PlayerCharacterCustomization.gender);
playerElement.SetAttributeValue("race", PlayerCharacterCustomization.race);
playerElement.SetAttributeValue("hairindex", PlayerCharacterCustomization.HairIndex);
playerElement.SetAttributeValue("beardindex", PlayerCharacterCustomization.BeardIndex);
playerElement.SetAttributeValue("moustacheindex", PlayerCharacterCustomization.MoustacheIndex);
playerElement.SetAttributeValue("faceattachmentindex", PlayerCharacterCustomization.FaceAttachmentIndex);
playerElement.SetAttributeValue("skincolor", XMLExtensions.ColorToString(PlayerCharacterCustomization.SkinColor));
playerElement.SetAttributeValue("haircolor", XMLExtensions.ColorToString(PlayerCharacterCustomization.HairColor));
playerElement.SetAttributeValue("facialhaircolor", XMLExtensions.ColorToString(PlayerCharacterCustomization.FacialHairColor));
}
doc.Root.Add(playerElement);
#if CLIENT
@@ -1434,23 +1267,22 @@ namespace Barotrauma
if (playerElement != null)
{
playerName = playerElement.GetAttributeString("name", playerName);
CharacterHeadIndex = playerElement.GetAttributeInt("headindex", CharacterHeadIndex);
if (Enum.TryParse(playerElement.GetAttributeString("gender", "none"), true, out Gender g))
int head = playerElement.GetAttributeInt("headindex", -1);
Enum.TryParse(playerElement.GetAttributeString("gender", "none"), true, out Gender gender);
Enum.TryParse(playerElement.GetAttributeString("race", "white"), true, out Race race);
int hair = playerElement.GetAttributeInt("hairindex", -1);
int beard = playerElement.GetAttributeInt("beardindex", -1);
int moustache = playerElement.GetAttributeInt("moustacheindex", -1);
int faceAttachment = playerElement.GetAttributeInt("faceattachmentindex", -1);
Color skinColor = playerElement.GetAttributeColor("skincolor", Color.Black);
Color hairColor = playerElement.GetAttributeColor("haircolor", Color.Black);
Color facialHairColor = playerElement.GetAttributeColor("facialhaircolor", Color.Black);
PlayerCharacterCustomization = new CharacterInfo.HeadInfo(head, gender, race, hair, beard, moustache, faceAttachment)
{
CharacterGender = g;
}
if (Enum.TryParse(playerElement.GetAttributeString("race", "white"), true, out Race r))
{
CharacterRace = r;
}
else
{
CharacterRace = Race.White;
}
CharacterHairIndex = playerElement.GetAttributeInt("hairindex", CharacterHairIndex);
CharacterBeardIndex = playerElement.GetAttributeInt("beardindex", CharacterBeardIndex);
CharacterMoustacheIndex = playerElement.GetAttributeInt("moustacheindex", CharacterMoustacheIndex);
CharacterFaceAttachmentIndex = playerElement.GetAttributeInt("faceattachmentindex", CharacterFaceAttachmentIndex);
SkinColor = skinColor,
HairColor = hairColor,
FacialHairColor = facialHairColor
};
}
}
@@ -1656,13 +1488,7 @@ namespace Barotrauma
UseSteamMatchmaking = true;
RequireSteamAuthentication = true;
QuickStartSubmarineName = string.Empty;
CharacterHeadIndex = 1;
CharacterHairIndex = -1;
CharacterBeardIndex = -1;
CharacterMoustacheIndex = -1;
CharacterFaceAttachmentIndex = -1;
CharacterGender = Gender.None;
CharacterRace = Race.White;
PlayerCharacterCustomization = null;
aimAssistAmount = 0.5f;
EnableMouseLook = true;
EnableRadialDistortion = true;
@@ -15,14 +15,14 @@ namespace Barotrauma.Items.Components
private Character targetCharacter;
private AfflictionPrefab selectedEffect, selectedTaintedEffect;
[Serialize("", false)]
[Serialize("", true)]
public string Effect
{
get;
set;
}
[Serialize("geneticmaterialdebuff", false)]
[Serialize("geneticmaterialdebuff", true)]
public string TaintedEffect
{
get;
@@ -30,7 +30,7 @@ namespace Barotrauma.Items.Components
}
private bool tainted;
[Serialize(false, false)]
[Serialize(false, true)]
public bool Tainted
{
get { return tainted; }
@@ -49,7 +49,7 @@ namespace Barotrauma.Items.Components
}
//only for saving the selected tainted effect
[Serialize("", false)]
[Serialize("", true)]
public string SelectedTaintedEffect
{
get { return selectedTaintedEffect?.Identifier ?? string.Empty; }
@@ -100,6 +100,11 @@ namespace Barotrauma.Items.Components
{
float selectedTaintedEffectStrength = item.ConditionPercentage / 100.0f * selectedTaintedEffect.MaxStrength;
character.CharacterHealth.ApplyAffliction(null, selectedTaintedEffect.Instantiate(selectedTaintedEffectStrength));
var existingAffliction = character.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedTaintedEffect);
if (existingAffliction != null)
{
existingAffliction.Strength = selectedTaintedEffectStrength;
}
targetCharacter = character;
#if SERVER
item.CreateServerEvent(this);
@@ -111,6 +116,11 @@ namespace Barotrauma.Items.Components
ApplyStatusEffects(ActionType.OnWearing, 1.0f);
float selectedEffectStrength = item.ConditionPercentage / 100.0f * selectedEffect.MaxStrength;
character.CharacterHealth.ApplyAffliction(null, selectedEffect.Instantiate(selectedEffectStrength));
var existingAffliction = character.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedEffect);
if (existingAffliction != null)
{
existingAffliction.Strength = selectedEffectStrength;
}
targetCharacter = character;
#if SERVER
item.CreateServerEvent(this);
@@ -197,5 +207,21 @@ namespace Barotrauma.Items.Components
item.CreateServerEvent(this);
#endif
}
public static string TryCreateName(ItemPrefab prefab, XElement element)
{
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().Equals(nameof(GeneticMaterial), StringComparison.OrdinalIgnoreCase))
{
string nameId = subElement.GetAttributeString("nameidentifier", "");
if (!string.IsNullOrEmpty(nameId))
{
return prefab.Name.Replace("[type]", TextManager.Get(nameId, returnNull: true) ?? nameId);
}
}
}
return prefab.Name;
}
}
}
@@ -603,6 +603,11 @@ namespace Barotrauma.Items.Components
return true;
}
public override bool SecondaryUse(float deltaTime, Character character = null)
{
return true;
}
private Vector2 GetAttachPosition(Character user, bool useWorldCoordinates = false)
{
if (user == null) { return useWorldCoordinates ? item.WorldPosition : item.Position; }
@@ -61,8 +61,10 @@ namespace Barotrauma.Items.Components
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("attack", StringComparison.OrdinalIgnoreCase)) { continue; }
Attack = new Attack(subElement, item.Name + ", MeleeWeapon", item);
Attack.DamageRange = item.body == null ? 10.0f : ConvertUnits.ToDisplayUnits(item.body.GetMaxExtent());
Attack = new Attack(subElement, item.Name + ", MeleeWeapon", item)
{
DamageRange = item.body == null ? 10.0f : ConvertUnits.ToDisplayUnits(item.body.GetMaxExtent())
};
}
item.IsShootable = true;
// TODO: should define this in xml if we have melee weapons that don't require aim to use
@@ -266,16 +268,10 @@ namespace Barotrauma.Items.Components
return false;
}
Character targetCharacter = null;
Limb targetLimb = null;
Structure targetStructure = null;
Item targetItem = null;
if (f2.Body.UserData is Limb)
if (f2.Body.UserData is Limb targetLimb)
{
targetLimb = (Limb)f2.Body.UserData;
if (targetLimb.IsSevered || targetLimb.character == null || targetLimb.character == User) { return false; }
targetCharacter = targetLimb.character;
var targetCharacter = targetLimb.character;
if (targetCharacter == picker) { return false; }
if (AllowHitMultiple)
{
@@ -287,9 +283,8 @@ namespace Barotrauma.Items.Components
}
hitTargets.Add(targetCharacter);
}
else if (f2.Body.UserData is Character)
else if (f2.Body.UserData is Character targetCharacter)
{
targetCharacter = (Character)f2.Body.UserData;
if (targetCharacter == picker || targetCharacter == User) { return false; }
targetLimb = targetCharacter.AnimController.GetLimb(LimbType.Torso); //Otherwise armor can be bypassed in strange ways
if (AllowHitMultiple)
@@ -302,9 +297,8 @@ namespace Barotrauma.Items.Components
}
hitTargets.Add(targetCharacter);
}
else if (f2.Body.UserData is Structure)
else if (f2.Body.UserData is Structure targetStructure)
{
targetStructure = (Structure)f2.Body.UserData;
if (AllowHitMultiple)
{
if (hitTargets.Contains(targetStructure)) { return true; }
@@ -315,9 +309,8 @@ namespace Barotrauma.Items.Components
}
hitTargets.Add(targetStructure);
}
else if (f2.Body.UserData is Item)
else if (f2.Body.UserData is Item targetItem)
{
targetItem = (Item)f2.Body.UserData;
if (AllowHitMultiple)
{
if (hitTargets.Contains(targetItem)) { return true; }
@@ -350,13 +343,11 @@ namespace Barotrauma.Items.Components
Limb targetLimb = target.UserData as Limb;
Character targetCharacter = targetLimb?.character ?? target.UserData as Character;
Structure targetStructure = target.UserData as Structure;
Item targetItem = target.UserData as Item;
if (Attack != null)
{
Attack.SetUser(User);
Attack.DamageMultiplier = 1 + User.GetStatValue(StatTypes.MeleeAttackMultiplier);
Attack.DamageMultiplier *= 1.0f + item.GetQualityModifier(Quality.StatType.AttackMultiplier);
if (targetLimb != null)
{
@@ -370,12 +361,12 @@ namespace Barotrauma.Items.Components
targetCharacter.LastDamageSource = item;
Attack.DoDamage(User, targetCharacter, item.WorldPosition, 1.0f);
}
else if (targetStructure != null)
else if (target.UserData is Structure targetStructure)
{
if (targetStructure.Removed) { return; }
Attack.DoDamage(User, targetStructure, item.WorldPosition, 1.0f);
}
else if (targetItem != null && targetItem.Prefab.DamagedByMeleeWeapons && targetItem.Condition > 0)
else if (target.UserData is Item targetItem && targetItem.Prefab.DamagedByMeleeWeapons && targetItem.Condition > 0)
{
if (targetItem.Removed) { return; }
Attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f);
@@ -196,7 +196,8 @@ namespace Barotrauma.Items.Components
Vector2 barrelPos = TransformedBarrelPos + item.body.SimPosition;
float rotation = (Item.body.Dir == 1.0f) ? Item.body.Rotation : Item.body.Rotation - MathHelper.Pi;
float spread = GetSpread(character) * Rand.Range(-0.5f, 0.5f);
projectile.Shoot(character, character.AnimController.AimSourceSimPos, barrelPos, rotation + spread, ignoredBodies: limbBodies.ToList(), createNetworkEvent: false);
float damageMultiplier = 1f + item.GetQualityModifier(Quality.StatType.AttackMultiplier);
projectile.Shoot(character, character.AnimController.AimSourceSimPos, barrelPos, rotation + spread, ignoredBodies: limbBodies.ToList(), createNetworkEvent: false, damageMultiplier);
projectile.Item.GetComponent<Rope>()?.Attach(Item, projectile.Item);
if (i == 0)
{
@@ -619,7 +619,7 @@ namespace Barotrauma.Items.Components
levelResource.requiredItems.Any() &&
levelResource.HasRequiredItems(user, addMessage: false))
{
float addedDetachTime = deltaTime * (1f + user.GetStatValue(StatTypes.RepairToolDeattachTimeMultiplier)) * item.GetQualityModifier(Quality.StatType.RepairToolDeattachTimeMultiplier);
float addedDetachTime = deltaTime * (1f + user.GetStatValue(StatTypes.RepairToolDeattachTimeMultiplier)) * (1f + item.GetQualityModifier(Quality.StatType.RepairToolDeattachTimeMultiplier));
levelResource.DeattachTimer += addedDetachTime;
#if CLIENT
Character.Controlled?.UpdateHUDProgressBar(
@@ -19,6 +19,8 @@ namespace Barotrauma.Items.Components
private float userDeconstructorSpeedMultiplier = 1.0f;
private const float TinkeringSpeedIncrease = 1.5f;
private ItemContainer inputContainer, outputContainer;
public ItemContainer InputContainer
@@ -89,12 +91,20 @@ namespace Barotrauma.Items.Components
if (powerConsumption <= 0.0f) { Voltage = 1.0f; }
progressTimer += deltaTime * Math.Min(Voltage, 1.0f);
float tinkeringStrength = 0f;
if (repairable.IsTinkering)
{
tinkeringStrength = repairable.TinkeringStrength;
}
// doesn't quite work properly, remaining time changes if tinkering stops
float deconstructionSpeedModifier = userDeconstructorSpeedMultiplier * (1f + tinkeringStrength * TinkeringSpeedIncrease);
if (DeconstructItemsSimultaneously)
{
float deconstructTime = 0.0f;
foreach (Item targetItem in inputContainer.Inventory.AllItems)
{
deconstructTime += targetItem.Prefab.DeconstructTime / (DeconstructionSpeed * userDeconstructorSpeedMultiplier);
deconstructTime += targetItem.Prefab.DeconstructTime / (DeconstructionSpeed * deconstructionSpeedModifier);
}
progressState = Math.Min(progressTimer / deconstructTime, 1.0f);
@@ -126,7 +136,7 @@ namespace Barotrauma.Items.Components
var validDeconstructItems = targetItem.Prefab.DeconstructItems.FindAll(it =>
it.RequiredDeconstructor.Length == 0 || it.RequiredDeconstructor.Any(r => item.HasTag(r) || item.Prefab.Identifier.Equals(r, StringComparison.OrdinalIgnoreCase)));
float deconstructTime = validDeconstructItems.Any() ? targetItem.Prefab.DeconstructTime / DeconstructionSpeed : 1.0f;
float deconstructTime = validDeconstructItems.Any() ? targetItem.Prefab.DeconstructTime / (DeconstructionSpeed * deconstructionSpeedModifier) : 1.0f;
progressState = Math.Min(progressTimer / deconstructTime, 1.0f);
if (progressTimer > deconstructTime)
@@ -234,9 +244,13 @@ namespace Barotrauma.Items.Components
if (user != null && !user.Removed)
{
var itemsCreated = new AbilityValueItem(1f, targetItem.Prefab);
var itemsCreated = new AbilityValueItem(amount, targetItem.Prefab);
user.CheckTalents(AbilityEffectType.OnItemDeconstructedMaterial, itemsCreated);
amount = (int)itemsCreated.Value;
// used to spawn items directly into the deconstructor
var itemContainer = new AbilityItemPrefabItem(item, targetItem.Prefab);
user.CheckTalents(AbilityEffectType.OnItemDeconstructedInventory, itemContainer);
}
for (int i = 0; i < amount; i++)
@@ -256,17 +270,6 @@ namespace Barotrauma.Items.Components
}
}
if (user != null && !user.Removed)
{
var deconstructItemRetainProbability = new AbilityValueItem(0f, targetItem.Prefab);
user.CheckTalents(AbilityEffectType.OnItemDeconstructedRetainProbability, deconstructItemRetainProbability);
if (deconstructItemRetainProbability.Value > Rand.Range(0f, 1f, Rand.RandSync.Unsynced))
{
allowRemove = false;
}
}
if (targetItem.AllowDeconstruct && allowRemove)
{
//drop all items that are inside the deconstructed item
@@ -73,6 +73,8 @@ namespace Barotrauma.Items.Components
}
}
private const float TinkeringForceIncrease = 1.5f;
public Engine(Item item, XElement element)
: base(item, element)
{
@@ -128,7 +130,7 @@ namespace Barotrauma.Items.Components
currForce *= maxForce * forceMultiplier;
if (item.GetComponent<Repairable>() is Repairable repairable && repairable.IsTinkering)
{
currForce *= 2.5f;
currForce *= 1f + repairable.TinkeringStrength * TinkeringForceIncrease;
}
//less effective when in a bad condition
@@ -32,6 +32,8 @@ namespace Barotrauma.Items.Components
[Serialize(1.0f, true)]
public float SkillRequirementMultiplier { get; set; }
private const float TinkeringSpeedIncrease = 1.5f;
private enum FabricatorState
{
Active = 1,
@@ -279,7 +281,14 @@ namespace Barotrauma.Items.Components
if (powerConsumption <= 0) { Voltage = 1.0f; }
timeUntilReady -= deltaTime * Math.Min(Voltage, 1.0f);
float tinkeringStrength = 0f;
if (repairable.IsTinkering)
{
tinkeringStrength = repairable.TinkeringStrength;
}
float fabricationSpeedIncrease = 1f + tinkeringStrength * TinkeringSpeedIncrease;
timeUntilReady -= deltaTime * fabricationSpeedIncrease * Math.Min(Voltage, 1.0f);
UpdateRequiredTimeProjSpecific();
@@ -329,12 +338,7 @@ namespace Barotrauma.Items.Components
}
user.CheckTalents(AbilityEffectType.OnItemFabricatedAmount, fabricationValueItem);
float floatQuality = 0.0f;
foreach (string tag in fabricatedItem.TargetItem.Tags)
{
floatQuality += user.Info.GetSavedStatValue(StatTypes.IncreaseFabricationQuality, tag);
}
quality = (int)floatQuality;
quality = GetFabricatedItemQuality(fabricatedItem, user);
}
var tempUser = user;
@@ -404,6 +408,25 @@ namespace Barotrauma.Items.Components
}
}
private int GetFabricatedItemQuality(FabricationRecipe fabricatedItem, Character user)
{
if (user == null) { return 0; }
if (fabricatedItem.TargetItem.ConfigElement.GetChildElement("Quality") == null) { return 0; }
int quality = 0;
float floatQuality = 0.0f;
foreach (string tag in fabricatedItem.TargetItem.Tags)
{
floatQuality += user.Info.GetSavedStatValue(StatTypes.IncreaseFabricationQuality, tag);
}
quality = (int)floatQuality;
const int MaxCraftingSkill = 100;
quality += fabricatedItem.RequiredSkills.All(s => user.GetSkillLevel(s.Identifier) >= MaxCraftingSkill) ? 1 : 0;
quality += FabricationDegreeOfSuccess(user, fabricatedItem.RequiredSkills) >= 0.5f ? 1 : 0;
return quality;
}
partial void UpdateRequiredTimeProjSpecific();
private bool CanBeFabricated(FabricationRecipe fabricableItem, Dictionary<string, List<Item>> availableIngredients, Character character)
@@ -70,6 +70,8 @@ namespace Barotrauma.Items.Components
public bool HasPower => IsActive && Voltage >= MinVoltage;
public bool IsAutoControlled => pumpSpeedLockTimer > 0.0f || isActiveLockTimer > 0.0f;
private const float TinkeringSpeedIncrease = 1.5f;
public Pump(Item item, XElement element)
: base(item, element)
{
@@ -108,7 +110,7 @@ namespace Barotrauma.Items.Components
if (item.GetComponent<Repairable>() is Repairable repairable && repairable.IsTinkering)
{
currFlow *= 2.5f;
currFlow *= 1f + repairable.TinkeringStrength * TinkeringSpeedIncrease;
}
//less effective when in a bad condition
@@ -370,7 +370,7 @@ namespace Barotrauma.Items.Components
item.SendSignal(new Signal((ConvertUnits.ToDisplayUnits(sub.Velocity.X * Physics.DisplayToRealWorldRatio) * 3.6f).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_velocity_x");
item.SendSignal(new Signal((ConvertUnits.ToDisplayUnits(sub.Velocity.Y * Physics.DisplayToRealWorldRatio) * -3.6f).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_velocity_y");
item.SendSignal(new Signal(sub.WorldPosition.X.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_x");
item.SendSignal(new Signal((sub.WorldPosition.X * Physics.DisplayToRealWorldRatio).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_x");
item.SendSignal(new Signal(sub.RealWorldDepth.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_y");
}
@@ -227,10 +227,11 @@ namespace Barotrauma.Items.Components
}
}
private void Launch(Character user, Vector2 simPosition, float rotation)
private void Launch(Character user, Vector2 simPosition, float rotation, float damageMultiplier = 1f)
{
Item.body.ResetDynamics();
Item.SetTransform(simPosition, rotation);
Attack.DamageMultiplier = damageMultiplier;
// Set user for hitscan projectiles to work properly.
User = user;
// Need to set null for non-characterusable items.
@@ -243,7 +244,7 @@ namespace Barotrauma.Items.Components
Item.SetTransform(simPosition, rotation + (Item.body.Dir * LaunchRotationRadians));
}
public void Shoot(Character user, Vector2 weaponPos, Vector2 spawnPos, float rotation, List<Body> ignoredBodies, bool createNetworkEvent)
public void Shoot(Character user, Vector2 weaponPos, Vector2 spawnPos, float rotation, List<Body> ignoredBodies, bool createNetworkEvent, float damageMultiplier = 1f)
{
//add the limbs of the shooter to the list of bodies to be ignored
//so that the player can't shoot himself
@@ -264,7 +265,7 @@ namespace Barotrauma.Items.Components
projectilePos = newPos;
}
}
Launch(user, projectilePos, rotation);
Launch(user, projectilePos, rotation, damageMultiplier);
if (createNetworkEvent && !Item.Removed && GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
#if SERVER
@@ -114,6 +114,9 @@ namespace Barotrauma.Items.Components
private Item currentRepairItem;
private float tinkeringDuration;
private float tinkeringStrength;
public float TinkeringStrength => tinkeringStrength;
public enum FixActions : int
{
@@ -240,6 +243,8 @@ namespace Barotrauma.Items.Components
CurrentFixerAction = action;
if (action == FixActions.Tinker)
{
tinkeringStrength = 1f + CurrentFixer.GetStatValue(StatTypes.TinkeringStrength);
if (character.HasAbilityFlag(AbilityFlags.CanTinkerFabricatorsAndDeconstructors) && item.GetComponent<Deconstructor>() != null || item.GetComponent<Fabricator>() != null)
{
// fabricators and deconstructors can be tinkered indefinitely (more or less)
@@ -370,6 +375,10 @@ namespace Barotrauma.Items.Components
{
tinkeringDuration -= deltaTime;
// not great to interject it here, should be less reliant on returning
float conditionDecrease = deltaTime * (CurrentFixer.GetStatValue(StatTypes.TinkeringDamage) / item.MaxCondition) * 100f;
item.Condition -= conditionDecrease;
if (!CanTinker(CurrentFixer) || tinkeringDuration <= 0f)
{
StopRepairing(CurrentFixer);
@@ -476,8 +485,8 @@ namespace Barotrauma.Items.Components
{
if (!character.HasAbilityFlag(AbilityFlags.CanTinker)) { return false; }
if (item.GetComponent<Engine>() != null) { return true; }
if (item.GetComponent<Turret>() != null) { return true; }
if (item.GetComponent<Pump>() != null) { return true; }
if (item.HasTag("turretammosource")) { return true; }
if (!character.HasAbilityFlag(AbilityFlags.CanTinkerFabricatorsAndDeconstructors)) { return false; }
if (item.GetComponent<Fabricator>() != null) { return true; }
if (item.GetComponent<Deconstructor>() != null) { return true; }
@@ -80,6 +80,7 @@ namespace Barotrauma.Items.Components
public RegExFindComponent(Item item, XElement element)
: base(item, element)
{
nonContinuousOutputSent = true;
IsActive = true;
}
@@ -64,7 +64,9 @@ namespace Barotrauma.Items.Components
private Character currentTarget;
const float aiFindTargetInterval = 5.0f;
private const float TinkeringPowerCostReduction = 1.25f;
private const float TinkeringPowerCostReduction = 0.2f;
private const float TinkeringDamageIncrease = 0.2f;
private const float TinkeringReloadDecrease = 0.2f;
public float Rotation
{
@@ -560,7 +562,8 @@ namespace Barotrauma.Items.Components
Projectile launchedProjectile = null;
bool loaderBroken = false;
bool isTinkering = false;
float tinkeringStrength = 0f;
for (int i = 0; i < ProjectileCount; i++)
{
var projectiles = GetLoadedProjectiles();
@@ -624,9 +627,9 @@ namespace Barotrauma.Items.Components
{
if (!(e is Item linkedItem)) { continue; }
if (!item.prefab.IsLinkAllowed(e.prefab)) { continue; }
if (linkedItem.GetComponent<Repairable>() is Repairable repairable && linkedItem.HasTag("turretammosource"))
if (linkedItem.GetComponent<Repairable>() is Repairable repairable && repairable.IsTinkering && linkedItem.HasTag("turretammosource"))
{
isTinkering = repairable.IsTinkering;
tinkeringStrength = repairable.TinkeringStrength;
}
}
@@ -636,10 +639,8 @@ namespace Barotrauma.Items.Components
float neededPower = GetPowerRequiredToShoot();
// tinkering is currently not factored into the common method as it is checked only when shooting
// but this is a minor issue that causes mostly cosmetic woes. might still be worth refactoring later
if (isTinkering)
{
neededPower /= TinkeringPowerCostReduction;
}
neededPower /= 1f + (tinkeringStrength * TinkeringPowerCostReduction);
while (neededPower > 0.0001f && batteries.Count > 0)
{
batteries.RemoveAll(b => b.Charge <= 0.0001f || b.MaxOutPut <= 0.0001f);
@@ -673,12 +674,12 @@ namespace Barotrauma.Items.Components
{
foreach (Projectile projectile in projectiles)
{
Launch(projectile.Item, character, isTinkering: isTinkering);
Launch(projectile.Item, character, tinkeringStrength: tinkeringStrength);
}
}
else
{
Launch(null, character, isTinkering: isTinkering);
Launch(null, character, tinkeringStrength: tinkeringStrength);
}
if (item.AiTarget != null)
{
@@ -712,13 +713,10 @@ namespace Barotrauma.Items.Components
return true;
}
private void Launch(Item projectile, Character user = null, float? launchRotation = null, bool isTinkering = false)
private void Launch(Item projectile, Character user = null, float? launchRotation = null, float tinkeringStrength = 0f)
{
reload = reloadTime;
if (isTinkering)
{
reload /= 1.25f;
}
reload /= 1f + (tinkeringStrength * TinkeringReloadDecrease);
if (user != null)
{
@@ -747,10 +745,8 @@ namespace Barotrauma.Items.Components
if (projectileComponent != null)
{
projectileComponent.Attacker = projectileComponent.User = user;
if (isTinkering)
{
projectileComponent.Attack.DamageMultiplier = 1.25f;
}
projectileComponent.Attack.DamageMultiplier = 1f + (TinkeringDamageIncrease * tinkeringStrength);
projectileComponent.Use();
projectile.GetComponent<Rope>()?.Attach(item, projectile);
projectileComponent.User = user;
@@ -5,7 +5,6 @@ using Barotrauma.IO;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Items.Components;
using Barotrauma.Extensions;
using Barotrauma.Networking;
using Barotrauma.Abilities;
@@ -49,7 +48,13 @@ namespace Barotrauma
public bool HideOtherWearables { get; private set; }
public List<WearableType> HideWearablesOfType { get; private set; }
public bool InheritLimbDepth { get; private set; }
public bool InheritTextureScale { get; private set; }
/// <summary>
/// Does the wearable inherit all the scalings of the wearer? Also the wearable's own scale is used!
/// </summary>
public bool InheritScale { get; private set; }
public bool IgnoreRagdollScale { get; private set; }
public bool IgnoreLimbScale { get; private set; }
public bool IgnoreTextureScale { get; private set; }
public bool InheritOrigin { get; private set; }
public bool InheritSourceRect { get; private set; }
@@ -113,10 +118,9 @@ namespace Barotrauma
case WearableType.Husk:
case WearableType.Herpes:
Limb = LimbType.Head;
HideLimb = type == WearableType.Husk || type == WearableType.Herpes;
HideOtherWearables = false;
InheritLimbDepth = true;
InheritTextureScale = true;
InheritScale = true;
InheritOrigin = true;
InheritSourceRect = true;
break;
@@ -173,7 +177,19 @@ namespace Barotrauma
HideLimb = SourceElement.GetAttributeBool("hidelimb", false);
HideOtherWearables = SourceElement.GetAttributeBool("hideotherwearables", false);
InheritLimbDepth = SourceElement.GetAttributeBool("inheritlimbdepth", true);
InheritTextureScale = SourceElement.GetAttributeBool("inherittexturescale", false);
var scale = SourceElement.GetAttribute("inheritscale");
if (scale != null)
{
InheritScale = scale.GetAttributeBool(false);
}
else
{
InheritScale = SourceElement.GetAttributeBool("inherittexturescale", false);
}
IgnoreLimbScale = SourceElement.GetAttributeBool("ignorelimbscale", false);
IgnoreTextureScale = SourceElement.GetAttributeBool("ignoretexturescale", false);
IgnoreRagdollScale = SourceElement.GetAttributeBool("ignoreragdollscale", false);
SourceElement.GetAttributeBool("inherittexturescale", false);
InheritOrigin = SourceElement.GetAttributeBool("inheritorigin", false);
InheritSourceRect = SourceElement.GetAttributeBool("inheritsourcerect", false);
DepthLimb = (LimbType)Enum.Parse(typeof(LimbType), SourceElement.GetAttributeString("depthlimb", "None"), true);
@@ -842,6 +842,8 @@ namespace Barotrauma
}
}
name = GeneticMaterial.TryCreateName(this, element);
if (string.IsNullOrEmpty(name))
{
DebugConsole.ThrowError($"Unnamed item ({identifier}) in {filePath}!");
@@ -167,7 +167,8 @@ namespace Barotrauma
new XAttribute("type", type.ToString()),
new XAttribute("optional", IsOptional),
new XAttribute("ignoreineditor", IgnoreInEditor),
new XAttribute("excludebroken", ExcludeBroken));
new XAttribute("excludebroken", ExcludeBroken),
new XAttribute("targetslot", TargetSlot));
if (excludedIdentifiers.Length > 0)
{
@@ -340,7 +340,7 @@ namespace Barotrauma
//ensures that the attack hits the correct limb and that the direction of the hit can be determined correctly in the AddDamage methods
Vector2 dir = worldPosition - limb.WorldPosition;
Vector2 hitPos = limb.WorldPosition + (dir.LengthSquared() <= 0.001f ? Rand.Vector(1.0f) : Vector2.Normalize(dir)) * 0.01f;
AttackResult attackResult = c.AddDamage(hitPos, modifiedAfflictions, attack.Stun * distFactor, false, attacker: attacker);
AttackResult attackResult = c.AddDamage(hitPos, modifiedAfflictions, attack.Stun * distFactor, false, attacker: attacker, damageMultiplier: attack.DamageMultiplier);
damages.Add(limb, attackResult.Damage);
if (attack.StatusEffects != null && attack.StatusEffects.Any())
@@ -2563,10 +2563,18 @@ namespace Barotrauma
if (PositionsOfInterest.Any(p => p.PositionType == PositionType.Cave))
{
positionType = PositionType.Cave;
if (allValidLocations.Any(l => l.Edge.NextToCave))
{
allValidLocations.RemoveAll(l => !l.Edge.NextToCave);
}
}
else if (PositionsOfInterest.Any(p => p.PositionType == PositionType.SidePath))
{
positionType = PositionType.SidePath;
if (allValidLocations.Any(l => l.Edge.NextToSidePath))
{
allValidLocations.RemoveAll(l => !l.Edge.NextToSidePath);
}
}
var poi = PositionsOfInterest.GetRandom(p => p.PositionType == positionType, randSync: Rand.RandSync.Server);
@@ -60,7 +60,7 @@ namespace Barotrauma
private LocationType addInitialMissionsForType;
public bool Discovered;
public bool Discovered { get; private set; }
public readonly Dictionary<LocationTypeChange.Requirement, int> ProximityTimer = new Dictionary<LocationTypeChange.Requirement, int>();
public (LocationTypeChange typeChange, int delay, MissionPrefab parentMission)? PendingLocationTypeChange;
@@ -868,6 +868,8 @@ namespace Barotrauma
// Adjust by random price modifier
price = ((100 + StorePriceModifier) / 100.0f) * price;
price *= priceInfo.BuyingPriceMultiplier;
// Adjust by daily special status
if (considerDailySpecials && DailySpecials.Contains(item))
{
@@ -1111,6 +1113,30 @@ namespace Barotrauma
return nextStatus;
}
public void Discover(bool checkTalents = true)
{
if (Discovered) { return; }
Discovered = true;
if (checkTalents)
{
GameSession.GetSessionCrewCharacters().ForEach(c => c.CheckTalents(AbilityEffectType.OnLocationDiscovered, new Abilities.AbilityLocation(this)));
}
}
public void Reset()
{
if (Type != OriginalType)
{
ChangeType(OriginalType);
PendingLocationTypeChange = null;
}
CreateStore(force: true);
ClearMissions();
LevelData?.EventHistory?.Clear();
UnlockInitialMissions();
Discovered = false;
}
public XElement Save(Map map, XElement parentElement)
{
var locationElement = new XElement("location",
@@ -231,7 +231,7 @@ namespace Barotrauma
}
System.Diagnostics.Debug.Assert(StartLocation != null, "Start location not assigned after level generation.");
CurrentLocation.Discovered = true;
CurrentLocation.Discover(true);
CurrentLocation.CreateStore();
InitProjectSpecific();
@@ -671,7 +671,7 @@ namespace Barotrauma
SelectedConnection.Passed = true;
CurrentLocation = SelectedLocation;
CurrentLocation.Discovered = true;
CurrentLocation.Discover();
SelectedLocation = null;
CurrentLocation.CreateStore();
@@ -702,7 +702,7 @@ namespace Barotrauma
Location prevLocation = CurrentLocation;
CurrentLocation = Locations[index];
CurrentLocation.Discovered = true;
CurrentLocation.Discover();
if (prevLocation != CurrentLocation)
{
@@ -1055,7 +1055,10 @@ namespace Barotrauma
}
}
location.LoadLocationTypeChange(subElement);
location.Discovered = subElement.GetAttributeBool("discovered", false);
if (subElement.GetAttributeBool("discovered", false))
{
location.Discover(checkTalents: false);
}
if (location.Discovered)
{
#if CLIENT
@@ -24,6 +24,11 @@ namespace Barotrauma
/// The item isn't available in stores unless the level's difficulty is above this value
/// </summary>
public readonly int MinLevelDifficulty;
/// <summary>
/// The cost of item when sold by the store. Higher modifier means the item costs more to buy from the store.
/// </summary>
public readonly float BuyingPriceMultiplier = 1f;
/// <summary>
/// Support for the old style of determining item prices
@@ -34,6 +39,7 @@ namespace Barotrauma
{
Price = element.GetAttributeInt("buyprice", 0);
MinLevelDifficulty = element.GetAttributeInt("minleveldifficulty", 0);
BuyingPriceMultiplier = element.GetAttributeFloat("buyingpricemultiplier", 1f);
CanBeBought = true;
var minAmount = GetMinAmount(element);
MinAvailableAmount = Math.Min(minAmount, CargoManager.MaxQuantity);
@@ -42,11 +48,12 @@ namespace Barotrauma
MaxAvailableAmount = Math.Max(maxAmount, MinAvailableAmount);
}
public PriceInfo(int price, bool canBeBought, int minAmount = 0, int maxAmount = 0, bool canBeSpecial = true, int minLevelDifficulty = 0)
public PriceInfo(int price, bool canBeBought, int minAmount = 0, int maxAmount = 0, bool canBeSpecial = true, int minLevelDifficulty = 0, float buyingPriceMultiplier = 1f)
{
Price = price;
CanBeBought = canBeBought;
MinAvailableAmount = Math.Min(minAmount, CargoManager.MaxQuantity);
BuyingPriceMultiplier = buyingPriceMultiplier;
maxAmount = Math.Min(maxAmount, CargoManager.MaxQuantity);
MaxAvailableAmount = Math.Max(maxAmount, minAmount);
MinLevelDifficulty = minLevelDifficulty;
@@ -62,6 +69,7 @@ namespace Barotrauma
var maxAmount = GetMaxAmount(element);
var minLevelDifficulty = element.GetAttributeInt("minleveldifficulty", 0);
var canBeSpecial = element.GetAttributeBool("canbespecial", true);
var buyingPriceMultiplier = element.GetAttributeFloat("buyingpricemultiplier", 1f);
var priceInfos = new List<Tuple<string, PriceInfo>>();
foreach (XElement childElement in element.GetChildElements("price"))
@@ -73,7 +81,7 @@ namespace Barotrauma
minAmount: sold ? GetMinAmount(childElement, minAmount) : 0,
maxAmount: sold ? GetMaxAmount(childElement, maxAmount) : 0,
canBeSpecial,
childElement.GetAttributeInt("minleveldifficulty", minLevelDifficulty))));
childElement.GetAttributeInt("minleveldifficulty", minLevelDifficulty), childElement.GetAttributeFloat("buyingpricemultiplier", buyingPriceMultiplier))));
}
var canBeBoughtAtOtherLocations = soldByDefault && element.GetAttributeBool("soldeverywhere", true);
@@ -81,7 +89,7 @@ namespace Barotrauma
minAmount: canBeBoughtAtOtherLocations ? minAmount : 0,
maxAmount: canBeBoughtAtOtherLocations ? maxAmount : 0,
canBeSpecial,
minLevelDifficulty);
minLevelDifficulty, buyingPriceMultiplier);
return priceInfos;
}
@@ -19,6 +19,8 @@ namespace Barotrauma.Networking
Double ReadDouble();
UInt32 ReadVariableUInt32();
String ReadString();
Microsoft.Xna.Framework.Color ReadColorR8G8B8();
Microsoft.Xna.Framework.Color ReadColorR8G8B8A8();
int ReadRangedInteger(int min, int max);
Single ReadRangedSingle(Single min, Single max, int bitCount);
byte[] ReadBytes(int numberOfBytes);
@@ -15,6 +15,8 @@ namespace Barotrauma.Networking
void Write(UInt64 val);
void Write(Single val);
void Write(Double val);
void WriteColorR8G8B8(Microsoft.Xna.Framework.Color val);
void WriteColorR8G8B8A8(Microsoft.Xna.Framework.Color val);
void WriteVariableUInt32(UInt32 val);
void Write(string val);
void WriteRangedInteger(int val, int min, int max);
@@ -5,6 +5,7 @@ using Barotrauma.IO;
using System.IO.Compression;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Xna.Framework;
namespace Barotrauma.Networking
{
@@ -138,6 +139,26 @@ namespace Barotrauma.Networking
byte[] bytes = BitConverter.GetBytes(val);
WriteBytes(ref buf, ref bitPos, bytes, 0, 8);
}
internal static void WriteColorR8G8B8(ref byte[] buf, ref int bitPos, Microsoft.Xna.Framework.Color val)
{
EnsureBufferSize(ref buf, bitPos + 24);
Write(ref buf, ref bitPos, val.R);
Write(ref buf, ref bitPos, val.G);
Write(ref buf, ref bitPos, val.B);
}
internal static void WriteColorR8G8B8A8(ref byte[] buf, ref int bitPos, Microsoft.Xna.Framework.Color val)
{
EnsureBufferSize(ref buf, bitPos + 32);
Write(ref buf, ref bitPos, val.R);
Write(ref buf, ref bitPos, val.G);
Write(ref buf, ref bitPos, val.B);
Write(ref buf, ref bitPos, val.A);
}
internal static void Write(ref byte[] buf, ref int bitPos, string val)
{
if (string.IsNullOrEmpty(val))
@@ -299,6 +320,23 @@ namespace Barotrauma.Networking
return BitConverter.ToDouble(bytes, 0);
}
internal static Microsoft.Xna.Framework.Color ReadColorR8G8B8(byte[] buf, ref int bitPos)
{
byte r = ReadByte(buf, ref bitPos);
byte g = ReadByte(buf, ref bitPos);
byte b = ReadByte(buf, ref bitPos);
return new Color(r, g, b, (byte)255);
}
internal static Microsoft.Xna.Framework.Color ReadColorR8G8B8A8(byte[] buf, ref int bitPos)
{
byte r = ReadByte(buf, ref bitPos);
byte g = ReadByte(buf, ref bitPos);
byte b = ReadByte(buf, ref bitPos);
byte a = ReadByte(buf, ref bitPos);
return new Color(r, g, b, a);
}
internal static UInt32 ReadVariableUInt32(byte[] buf, ref int bitPos)
{
int bitLength = buf.Length * 8;
@@ -482,6 +520,16 @@ namespace Barotrauma.Networking
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void WriteColorR8G8B8(Color val)
{
MsgWriter.WriteColorR8G8B8(ref buf, ref seekPos, val);
}
public void WriteColorR8G8B8A8(Color val)
{
MsgWriter.WriteColorR8G8B8A8(ref buf, ref seekPos, val);
}
public void WriteVariableUInt32(UInt32 val)
{
MsgWriter.WriteVariableUInt32(ref buf, ref seekPos, val);
@@ -702,6 +750,17 @@ namespace Barotrauma.Networking
return MsgReader.ReadString(buf, ref seekPos);
}
public Color ReadColorR8G8B8()
{
return MsgReader.ReadColorR8G8B8(buf, ref seekPos);
}
public Color ReadColorR8G8B8A8()
{
return MsgReader.ReadColorR8G8B8A8(buf, ref seekPos);
}
public int ReadRangedInteger(int min, int max)
{
return MsgReader.ReadRangedInteger(buf, ref seekPos, min, max);
@@ -845,6 +904,16 @@ namespace Barotrauma.Networking
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void WriteColorR8G8B8(Color val)
{
MsgWriter.WriteColorR8G8B8(ref buf, ref seekPos, val);
}
public void WriteColorR8G8B8A8(Color val)
{
MsgWriter.WriteColorR8G8B8A8(ref buf, ref seekPos, val);
}
public void WriteVariableUInt32(UInt32 val)
{
MsgWriter.WriteVariableUInt32(ref buf, ref seekPos, val);
@@ -936,6 +1005,16 @@ namespace Barotrauma.Networking
return MsgReader.ReadString(buf, ref seekPos);
}
public Color ReadColorR8G8B8()
{
return MsgReader.ReadColorR8G8B8(buf, ref seekPos);
}
public Color ReadColorR8G8B8A8()
{
return MsgReader.ReadColorR8G8B8A8(buf, ref seekPos);
}
public int ReadRangedInteger(int min, int max)
{
return MsgReader.ReadRangedInteger(buf, ref seekPos, min, max);
@@ -88,7 +88,7 @@ namespace Barotrauma
return GameMain.NetworkMember?.ServerSettings?.AllowLinkingWifiToChat ?? true;
case ConditionType.IsSwappableItem:
{
return entity is Item item && item.Prefab.SwappableItem != null;
return entity is Item item && item.Prefab.SwappableItem != null && Screen.Selected == GameMain.SubEditorScreen;
}
case ConditionType.AllowRotating:
{
@@ -236,7 +236,7 @@ namespace Barotrauma
{
lock (list)
{
list.RemoveAll(wRef => !wRef.TryGetTarget(out Sprite s) || s==this);
list.RemoveAll(wRef => !wRef.TryGetTarget(out Sprite s) || s == this);
}
DisposeTexture();
}
@@ -457,7 +457,14 @@ namespace Barotrauma
</Description>
*/
string extraDescriptionLine = Get(descriptionElement.GetAttributeString("tag", string.Empty));
if (descriptionElement.GetAttributeBool("linebreak", false))
{
Description += "\n";
return;
}
string descriptionTag = descriptionElement.GetAttributeString("tag", string.Empty);
string extraDescriptionLine = Get(descriptionTag);
if (string.IsNullOrEmpty(extraDescriptionLine)) { return; }
foreach (XElement replaceElement in descriptionElement.Elements())
{
@@ -468,12 +475,23 @@ namespace Barotrauma
string replacementValue = string.Empty;
for (int i = 0; i < replacementValues.Length; i++)
{
#if DEBUG
if (!int.TryParse(replacementValues[i], out int _) && !float.TryParse(replacementValues[i], System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out float __) && !ContainsTag(replacementValues[i]))
{
DebugConsole.AddWarning($"Couldn't find the tag \"{replacementValues[i]}\" in text files for description \"{descriptionTag}\". Is the tag correct?");
}
#endif
replacementValue += Get(replacementValues[i], returnNull: true) ?? replacementValues[i];
if (i < replacementValues.Length - 1)
{
replacementValue += ", ";
}
}
if (replaceElement.Attribute("color") != null)
{
string colorStr = replaceElement.GetAttributeString("color", "255,255,255,255");
replacementValue = $"‖color:{colorStr}‖{replacementValue}‖color:end‖";
}
extraDescriptionLine = extraDescriptionLine.Replace(tag, replacementValue);
}
if (!string.IsNullOrEmpty(Description)) { Description += "\n"; }
@@ -23,17 +23,19 @@ namespace Barotrauma.IO
{
path = System.IO.Path.GetFullPath(path).CleanUpPath();
string extension = System.IO.Path.GetExtension(path).Replace(" ", "");
if (unwritableExtensions.Any(e => e.Equals(extension, StringComparison.OrdinalIgnoreCase)))
if (!isDirectory)
{
return false;
}
if (!path.StartsWith(System.IO.Path.GetFullPath("Mods/").CleanUpPath(), StringComparison.OrdinalIgnoreCase)
&& (extension.Equals(".dll", StringComparison.OrdinalIgnoreCase)
|| extension.Equals(".exe", StringComparison.OrdinalIgnoreCase)))
{
return false;
string extension = System.IO.Path.GetExtension(path).Replace(" ", "");
if (unwritableExtensions.Any(e => e.Equals(extension, StringComparison.OrdinalIgnoreCase)))
{
return false;
}
if (!path.StartsWith(System.IO.Path.GetFullPath("Mods/").CleanUpPath(), StringComparison.OrdinalIgnoreCase)
&& (extension.Equals(".dll", StringComparison.OrdinalIgnoreCase)
|| extension.Equals(".exe", StringComparison.OrdinalIgnoreCase)))
{
return false;
}
}
foreach (string unwritableDir in unwritableDirs)
@@ -251,6 +253,7 @@ namespace Barotrauma.IO
if (!Validation.CanWrite(path, true))
{
DebugConsole.ThrowError($"Cannot create directory \"{path}\": modifying the contents of this folder/using this extension is not allowed.");
Validation.CanWrite(path, true);
return null;
}
return System.IO.Directory.CreateDirectory(path);