Unstable 0.1500.5.0 (almost forgor edition 💀)

This commit is contained in:
Markus Isberg
2021-10-01 23:56:14 +09:00
parent 3043a9a7bc
commit 08bdfc6cea
150 changed files with 5669 additions and 4403 deletions
@@ -104,6 +104,8 @@ namespace Barotrauma
(!requireNonDirty || !pathSteering.IsPathDirty);
protected readonly float colliderWidth;
protected readonly float minGapSize;
protected readonly float minHullSize;
protected readonly float colliderLength;
protected readonly float avoidLookAheadDistance;
@@ -116,6 +118,8 @@ namespace Barotrauma
colliderWidth = size.X;
colliderLength = size.Y;
avoidLookAheadDistance = Math.Max(Math.Max(colliderWidth, colliderLength) * 3, 1.5f);
minGapSize = ConvertUnits.ToDisplayUnits(Math.Min(colliderWidth, colliderLength));
minHullSize = ConvertUnits.ToDisplayUnits(Math.Max(colliderLength, colliderWidth) * 1.1f);
}
public virtual void OnAttacked(Character attacker, AttackResult attackResult) { }
@@ -390,8 +394,7 @@ namespace Barotrauma
else
{
if (gap.Open < 1) { continue; }
bool canGetThrough = ConvertUnits.ToDisplayUnits(colliderWidth) < gap.Size;
if (!canGetThrough) { continue; }
if (gap.Size < minGapSize) { continue; }
}
if (gap.FlowTargetHull == Character.CurrentHull)
{
@@ -92,7 +92,16 @@ namespace Barotrauma
public string SonarLabel;
public string SonarIconIdentifier;
public bool Enabled => SoundRange > 0 || SightRange > 0;
private bool inDetectable;
/// <summary>
/// Should be reset to false each frame and kept indetectable by e.g. a status effect.
/// </summary>
public bool InDetectable
{
get => inDetectable || (SoundRange <= 0 && SightRange <= 0);
set => inDetectable = value;
}
public float MinSoundRange, MinSightRange;
public float MaxSoundRange = 100000, MaxSightRange = 100000;
@@ -181,14 +190,15 @@ namespace Barotrauma
public void Update(float deltaTime)
{
if (Enabled && !Static && FadeOutTime > 0)
InDetectable = false;
if (!Static && FadeOutTime > 0)
{
// The aitarget goes silent/invisible if the components don't keep it active
if (!StaticSight)
if (!StaticSight && SightRange > 0)
{
DecreaseSightRange(deltaTime);
}
if (!StaticSound)
if (!StaticSound && SoundRange > 0)
{
DecreaseSoundRange(deltaTime);
}
File diff suppressed because it is too large Load Diff
@@ -1036,16 +1036,19 @@ namespace Barotrauma
}
if (previousAttackResults.ContainsKey(attacker))
{
foreach (Affliction newAffliction in attackResult.Afflictions)
if (attackResult.Afflictions != null)
{
var matchingAffliction = previousAttackResults[attacker].Afflictions.Find(a => a.Prefab == newAffliction.Prefab && a.Source == newAffliction.Source);
if (matchingAffliction == null)
foreach (Affliction newAffliction in attackResult.Afflictions)
{
previousAttackResults[attacker].Afflictions.Add(newAffliction);
}
else
{
matchingAffliction.Strength += newAffliction.Strength;
var matchingAffliction = previousAttackResults[attacker].Afflictions.Find(a => a.Prefab == newAffliction.Prefab && a.Source == newAffliction.Source);
if (matchingAffliction == null)
{
previousAttackResults[attacker].Afflictions.Add(newAffliction);
}
else
{
matchingAffliction.Strength += newAffliction.Strength;
}
}
}
previousAttackResults[attacker] = new AttackResult(previousAttackResults[attacker].Afflictions, previousAttackResults[attacker].HitLimb);
@@ -1062,9 +1065,12 @@ namespace Barotrauma
float realDamage = attackResult.Damage;
// including poisons etc
float totalDamage = realDamage;
foreach (Affliction affliction in attackResult.Afflictions)
if (attackResult.Afflictions != null)
{
totalDamage -= affliction.Prefab.KarmaChangeOnApplied * affliction.Strength;
foreach (Affliction affliction in attackResult.Afflictions)
{
totalDamage -= affliction.Prefab.KarmaChangeOnApplied * affliction.Strength;
}
}
if (totalDamage <= 0.01f) { return; }
if (Character.IsBot)
@@ -1255,7 +1261,7 @@ namespace Barotrauma
// Already targeting the attacker -> treat as a more serious threat.
cumulativeDamage *= 2;
}
if (attackResult.Afflictions.Any(a => a is AfflictionHusk))
if (attackResult.Afflictions != null && attackResult.Afflictions.Any(a => a is AfflictionHusk))
{
cumulativeDamage = 100;
}
@@ -2,7 +2,6 @@
using Microsoft.Xna.Framework;
using System;
using System.Linq;
using Barotrauma.Extensions;
using FarseerPhysics;
namespace Barotrauma
@@ -15,6 +14,11 @@ namespace Barotrauma
private bool canOpenDoors;
public bool CanBreakDoors { get; set; }
private bool ShouldBreakDoor(Door door) =>
CanBreakDoors &&
!door.Item.Indestructible && !door.Item.InvulnerableToDamage &&
(door.Item.Submarine == null || door.Item.Submarine.TeamID != character.TeamID);
private Character character;
private Vector2 currentTarget;
@@ -23,7 +27,7 @@ namespace Barotrauma
private float buttonPressCooldown;
const float ButtonPressInterval = 0.5f;
const float ButtonPressInterval = 0.25f;
public SteeringPath CurrentPath
{
@@ -111,9 +115,9 @@ namespace Barotrauma
IsPathDirty = true;
}
public void SteeringSeek(Vector2 target, float weight, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisiblity = true)
public void SteeringSeek(Vector2 target, float weight, float minGapWidth = 0, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisiblity = true)
{
steering += CalculateSteeringSeek(target, weight, startNodeFilter, endNodeFilter, nodeFilter, checkVisiblity);
steering += CalculateSteeringSeek(target, weight, minGapWidth, startNodeFilter, endNodeFilter, nodeFilter, checkVisiblity);
}
/// <summary>
@@ -158,7 +162,7 @@ namespace Barotrauma
}
}
private Vector2 CalculateSteeringSeek(Vector2 target, float weight, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisibility = true)
private Vector2 CalculateSteeringSeek(Vector2 target, float weight, float minGapSize = 0, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisibility = true)
{
bool needsNewPath = currentPath == null || currentPath.Unreachable;
if (!needsNewPath && character.Submarine != null && character.Params.PathFinderPriority > 0.5f)
@@ -200,7 +204,7 @@ namespace Barotrauma
}
pathFinder.InsideSubmarine = character.Submarine != null;
pathFinder.ApplyPenaltyToOutsideNodes = character.PressureProtection <= 0;
var newPath = pathFinder.FindPath(currentPos, target, character.Submarine, "(Character: " + character.Name + ")", startNodeFilter, endNodeFilter, nodeFilter, checkVisibility: checkVisibility);
var newPath = pathFinder.FindPath(currentPos, target, character.Submarine, "(Character: " + character.Name + ")", minGapSize, startNodeFilter, endNodeFilter, nodeFilter, checkVisibility: checkVisibility);
bool useNewPath = needsNewPath || currentPath == null || currentPath.CurrentNode == null || character.Submarine != null && findPathTimer < -1 && Math.Abs(character.AnimController.TargetMovement.X) <= 0;
if (!useNewPath && currentPath != null && currentPath.CurrentNode != null && newPath.Nodes.Any() && !newPath.Unreachable)
{
@@ -241,6 +245,10 @@ namespace Barotrauma
}
if (useNewPath)
{
if (currentPath != null)
{
CheckDoorsInPath();
}
currentPath = newPath;
}
float priority = MathHelper.Lerp(3, 1, character.Params.PathFinderPriority);
@@ -306,10 +314,12 @@ namespace Barotrauma
pos2 -= CurrentPath.Nodes.Last().Submarine.SimPosition;
}
return currentTarget - pos2;
}
if (canOpenDoors && !character.LockHands && buttonPressCooldown <= 0.0f)
}
bool doorsChecked = false;
if (!character.LockHands && buttonPressCooldown <= 0.0f)
{
CheckDoorsInPath();
doorsChecked = true;
}
Vector2 pos = host.SimPosition;
if (character != null && CurrentPath.CurrentNode?.Submarine != null)
@@ -394,7 +404,7 @@ namespace Barotrauma
}
if (isAboveFloor || nextLadderSameAsCurrent)
{
currentPath.SkipToNextNode();
NextNode(!doorsChecked);
}
}
else if (nextLadder != null)
@@ -404,7 +414,7 @@ namespace Barotrauma
//e.g. no point in going down to reach the starting point of a path when we could go directly to the one above
if (Math.Sign(currentPath.CurrentNode.WorldPosition.Y - character.WorldPosition.Y) != Math.Sign(currentPath.NextNode.WorldPosition.Y - character.WorldPosition.Y))
{
currentPath.SkipToNextNode();
NextNode(!doorsChecked);
}
}
return diff;
@@ -426,7 +436,7 @@ namespace Barotrauma
float distance = horizontalDistance + verticalDistance;
if (ConvertUnits.ToSimUnits(distance) < targetDistance)
{
currentPath.SkipToNextNode();
NextNode(!doorsChecked);
}
}
}
@@ -451,7 +461,7 @@ namespace Barotrauma
float targetDistance = Math.Max(colliderSize.X / 2 * margin, minWidth / 2);
if (horizontalDistance < targetDistance && isAboveFeet && isNotTooHigh && (door == null || door.CanBeTraversed))
{
currentPath.SkipToNextNode();
NextNode(!doorsChecked);
}
}
if (currentPath.CurrentNode == null)
@@ -461,28 +471,51 @@ namespace Barotrauma
return currentPath.CurrentNode.SimPosition - pos;
}
private void NextNode(bool checkDoors)
{
if (checkDoors)
{
CheckDoorsInPath();
}
currentPath.SkipToNextNode();
}
private bool CanAccessDoor(Door door, Func<Controller, bool> buttonFilter = null)
{
if (door.IsOpen || door.IsBroken) { return true; }
if (!door.Item.IsInteractable(character)) { return false; }
if (!CanBreakDoors)
if (door.IsBroken) { return true; }
if (!door.IsOpen)
{
if (door.IsStuck || door.IsJammed) { return false; }
if (!canOpenDoors || character.LockHands) { return false; }
if (!door.Item.IsInteractable(character)) { return false; }
if (!ShouldBreakDoor(door))
{
if (door.IsStuck || door.IsJammed) { return false; }
if (!canOpenDoors || character.LockHands) { return false; }
}
}
if (door.HasIntegratedButtons)
{
return door.HasAccess(character) || CanBreakDoors;
return door.IsOpen || door.HasAccess(character) || ShouldBreakDoor(door);
}
else
{
return door.Item.GetConnectedComponents<Controller>(true).Any(b => b.HasAccess(character) && (buttonFilter == null || buttonFilter(b))) || CanBreakDoors;
// We'll want this to run each time, because the delegate is used to find a valid button component.
bool canAccessButtons = door.Item.GetConnectedComponents<Controller>(true).Any(b => b.HasAccess(character) && (buttonFilter == null || buttonFilter(b)));
return canAccessButtons || door.IsOpen || ShouldBreakDoor(door);
}
}
private Vector2 GetColliderSize() => ConvertUnits.ToDisplayUnits(character.AnimController.Collider.GetSize());
private float GetColliderLength()
{
Vector2 colliderSize = character.AnimController.Collider.GetSize();
return ConvertUnits.ToDisplayUnits(Math.Max(colliderSize.X, colliderSize.Y));
}
private void CheckDoorsInPath()
{
for (int i = 0; i < 2; i++)
if (!canOpenDoors) { return; }
for (int i = 0; i < 5; i++)
{
WayPoint currentWaypoint = null;
WayPoint nextWaypoint = null;
@@ -493,17 +526,21 @@ namespace Barotrauma
{
door = currentPath.Nodes.First().ConnectedDoor;
shouldBeOpen = door != null;
if (i > 0) { break; }
}
else
{
if (i == 0)
bool closeDoors = character.IsBot && character.IsInFriendlySub || character.Params.AI != null && character.Params.AI.KeepDoorsClosed;
if (i == 0 || !closeDoors)
{
currentWaypoint = currentPath.CurrentNode;
nextWaypoint = currentPath.NextNode;
}
else
{
currentWaypoint = currentPath.PrevNode;
int previousIndex = currentPath.CurrentIndex - i;
if (previousIndex < 0) { break; }
currentWaypoint = currentPath.Nodes[previousIndex];
nextWaypoint = currentPath.CurrentNode;
}
if (currentWaypoint?.ConnectedDoor == null) { continue; }
@@ -520,16 +557,18 @@ namespace Barotrauma
}
else
{
float colliderLength = GetColliderLength();
door = currentWaypoint.ConnectedDoor;
if (door.LinkedGap.IsHorizontal)
{
int dir = Math.Sign(nextWaypoint.WorldPosition.X - door.Item.WorldPosition.X);
shouldBeOpen = (door.Item.WorldPosition.X - character.WorldPosition.X) * dir > -50.0f;
float size = character.AnimController.InWater ? colliderLength : GetColliderSize().X;
shouldBeOpen = (door.Item.WorldPosition.X - character.WorldPosition.X) * dir > -size;
}
else
{
int dir = Math.Sign(nextWaypoint.WorldPosition.Y - door.Item.WorldPosition.Y);
shouldBeOpen = (door.Item.WorldPosition.Y - character.WorldPosition.Y) * dir > -80.0f;
shouldBeOpen = (door.Item.WorldPosition.Y - character.WorldPosition.Y) * dir > -colliderLength;
}
}
}
@@ -573,7 +612,7 @@ namespace Barotrauma
}
else if (closestButton != null)
{
if (Vector2.DistanceSquared(closestButton.Item.WorldPosition, character.WorldPosition) < MathUtils.Pow(closestButton.Item.InteractDistance * 2, 2))
if (Vector2.DistanceSquared(closestButton.Item.WorldPosition, character.WorldPosition) < MathUtils.Pow(closestButton.Item.InteractDistance + GetColliderLength(), 2))
{
closestButton.Item.TryInteract(character, false, true);
buttonPressCooldown = ButtonPressInterval;
@@ -19,16 +19,19 @@ namespace Barotrauma
private Body targetBody;
private Vector2 attachSurfaceNormal;
private Submarine targetSubmarine;
private Character targetCharacter;
private readonly Character character;
public bool AttachToSub { get; private set; }
public bool AttachToWalls { get; private set; }
public bool AttachToCharacters { get; private set; }
private readonly float minDeattachSpeed, maxDeattachSpeed;
private readonly float minDeattachSpeed, maxDeattachSpeed, maxAttachDuration;
private readonly float damageOnDetach, detachStun;
private float deattachTimer;
private readonly bool weld;
private float deattachCheckTimer;
private Vector2 wallAttachPos;
private Vector2 _attachPos;
private float attachCooldown;
@@ -38,9 +41,9 @@ namespace Barotrauma
private float jointDir;
public List<WeldJoint> AttachJoints { get; } = new List<WeldJoint>();
public List<Joint> AttachJoints { get; } = new List<Joint>();
public Vector2? WallAttachPos
public Vector2? AttachPos
{
get;
private set;
@@ -48,18 +51,21 @@ namespace Barotrauma
public bool IsAttached => AttachJoints.Count > 0;
public bool IsAttachedToSub => IsAttached && targetSubmarine != null;
public bool IsAttachedToSub => IsAttached && targetSubmarine != null && targetCharacter == null;
public LatchOntoAI(XElement element, EnemyAIController enemyAI)
{
AttachToWalls = element.GetAttributeBool("attachtowalls", false);
AttachToSub = element.GetAttributeBool("attachtosub", false);
AttachToCharacters = element.GetAttributeBool("attachtocharacters", false);
minDeattachSpeed = element.GetAttributeFloat("mindeattachspeed", 5.0f);
maxDeattachSpeed = Math.Max(minDeattachSpeed, element.GetAttributeFloat("maxdeattachspeed", 8.0f));
maxAttachDuration = element.GetAttributeFloat("maxattachduration", -1.0f);
damageOnDetach = element.GetAttributeFloat("damageondetach", 0.0f);
detachStun = element.GetAttributeFloat("detachstun", 0.0f);
localAttachPos = ConvertUnits.ToSimUnits(element.GetAttributeVector2("localattachpos", Vector2.Zero));
attachLimbRotation = MathHelper.ToRadians(element.GetAttributeFloat("attachlimbrotation", 0.0f));
weld = element.GetAttributeBool("weld", true);
string limbString = element.GetAttributeString("attachlimb", null);
attachLimb = enemyAI.Character.AnimController.Limbs.FirstOrDefault(l => string.Equals(l.Name, limbString, StringComparison.OrdinalIgnoreCase));
@@ -81,30 +87,54 @@ namespace Barotrauma
public void SetAttachTarget(Structure wall, Vector2 attachPos, Vector2 attachSurfaceNormal)
{
if (!AttachToSub) { return; }
if (wall == null) { return; }
var sub = wall.Submarine;
if (sub == null) { return; }
Reset();
targetWall = wall;
targetSubmarine = sub;
targetBody = targetSubmarine.PhysicsBody.FarseerBody;
this.attachSurfaceNormal = attachSurfaceNormal;
wallAttachPos = attachPos;
_attachPos = attachPos;
}
public void SetAttachTarget(Character target)
{
if (!AttachToCharacters) { return; }
Reset();
targetCharacter = target;
targetSubmarine = target.Submarine;
targetBody = target.AnimController.Collider.FarseerBody;
attachSurfaceNormal = Vector2.Normalize(character.WorldPosition - target.WorldPosition);
}
public void Update(EnemyAIController enemyAI, float deltaTime)
{
if (character.Submarine != null)
{
DeattachFromBody(reset: true);
return;
if (targetCharacter != null && targetCharacter.Submarine != targetSubmarine ||
character.Submarine != null && targetSubmarine != null && targetCharacter == null)
{
DeattachFromBody(reset: true);
return;
}
}
if (AttachJoints.Count > 0)
if (IsAttached)
{
if (Math.Sign(attachLimb.Dir) != Math.Sign(jointDir))
{
AttachJoints[0].LocalAnchorA =
new Vector2(-AttachJoints[0].LocalAnchorA.X, AttachJoints[0].LocalAnchorA.Y);
AttachJoints[0].ReferenceAngle = -AttachJoints[0].ReferenceAngle;
var attachJoint = AttachJoints[0];
if (attachJoint is WeldJoint weldJoint)
{
weldJoint.LocalAnchorA = new Vector2(-weldJoint.LocalAnchorA.X, weldJoint.LocalAnchorA.Y);
weldJoint.ReferenceAngle = -weldJoint.ReferenceAngle;
}
else if (attachJoint is RevoluteJoint revoluteJoint)
{
revoluteJoint.LocalAnchorA = new Vector2(-revoluteJoint.LocalAnchorA.X, revoluteJoint.LocalAnchorA.Y);
revoluteJoint.ReferenceAngle = -revoluteJoint.ReferenceAngle;
}
jointDir = attachLimb.Dir;
}
for (int i = 0; i < AttachJoints.Count; i++)
@@ -113,31 +143,51 @@ namespace Barotrauma
if (Vector2.DistanceSquared(AttachJoints[i].WorldAnchorB, AttachJoints[i].BodyA.Position) > 10.0f * 10.0f)
{
#if DEBUG
DebugConsole.ThrowError("Limb body of the character \"" + character.Name + "\" is very far from the attach joint anchor -> deattach");
DebugConsole.Log("Limb body of the character \"" + character.Name + "\" is very far from the attach joint anchor -> deattach");
#endif
DeattachFromBody(reset: true);
return;
}
}
if (targetCharacter != null)
{
if (enemyAI.AttackingLimb?.attack == null)
{
DeattachFromBody(reset: true, cooldown: 1);
}
else
{
float range = enemyAI.AttackingLimb.attack.DamageRange * 2f;
if (Vector2.DistanceSquared(targetCharacter.WorldPosition, enemyAI.AttackingLimb.WorldPosition) > range * range)
{
DeattachFromBody(reset: true, cooldown: 1);
}
}
}
}
if (attachCooldown > 0)
{
attachCooldown -= deltaTime;
}
if (deattachTimer > 0)
if (deattachCheckTimer > 0)
{
deattachTimer -= deltaTime;
deattachCheckTimer -= deltaTime;
}
Vector2 transformedAttachPos = wallAttachPos;
if (targetCharacter != null)
{
// Own sim pos -> target where we are
_attachPos = character.SimPosition;
}
Vector2 transformedAttachPos = _attachPos;
if (character.Submarine == null && targetSubmarine != null)
{
transformedAttachPos += ConvertUnits.ToSimUnits(targetSubmarine.Position);
}
if (transformedAttachPos != Vector2.Zero)
{
WallAttachPos = transformedAttachPos;
AttachPos = transformedAttachPos;
}
switch (enemyAI.State)
@@ -151,7 +201,7 @@ namespace Barotrauma
//check if there are any walls nearby the character could attach to
if (raycastTimer < 0.0f)
{
wallAttachPos = Vector2.Zero;
_attachPos = Vector2.Zero;
var cells = Level.Loaded.GetCells(character.WorldPosition, 1);
if (cells.Count > 0)
@@ -169,7 +219,7 @@ namespace Barotrauma
{
attachSurfaceNormal = edge.GetNormal(cell);
targetBody = cell.Body;
wallAttachPos = potentialAttachPos;
_attachPos = potentialAttachPos;
closestDist = distSqr;
}
break;
@@ -183,21 +233,20 @@ namespace Barotrauma
}
else
{
wallAttachPos = Vector2.Zero;
_attachPos = Vector2.Zero;
}
if (wallAttachPos == Vector2.Zero || targetBody == null)
if (_attachPos == Vector2.Zero || targetBody == null)
{
DeattachFromBody(reset: false);
}
else
{
float squaredDistance = Vector2.DistanceSquared(character.SimPosition, wallAttachPos);
float squaredDistance = Vector2.DistanceSquared(character.SimPosition, _attachPos);
float targetDistance = Math.Max(Math.Max(character.AnimController.Collider.radius, character.AnimController.Collider.width), character.AnimController.Collider.height) * 1.2f;
if (squaredDistance < targetDistance * targetDistance)
{
//close enough to a wall -> attach
AttachToBody(wallAttachPos);
AttachToBody(_attachPos);
enemyAI.SteeringManager.Reset();
}
else
@@ -205,25 +254,22 @@ namespace Barotrauma
//move closer to the wall
DeattachFromBody(reset: false);
enemyAI.SteeringManager.SteeringAvoid(deltaTime, 1.0f, 0.1f);
enemyAI.SteeringManager.SteeringSeek(wallAttachPos);
enemyAI.SteeringManager.SteeringSeek(_attachPos);
}
}
break;
case AIState.Attack:
case AIState.Aggressive:
if (enemyAI.AttackingLimb != null)
if (enemyAI.IsSteeringThroughGap) { break; }
if (_attachPos == Vector2.Zero) { break; }
if (!AttachToSub && !AttachToCharacters) { break; }
if (enemyAI.AttackingLimb == null) { break; }
if (targetBody == null) { break; }
if (IsAttached && AttachJoints[0].BodyB == targetBody) { break; }
Vector2 referencePos = targetCharacter != null ? targetCharacter.WorldPosition : ConvertUnits.ToDisplayUnits(transformedAttachPos);
if (Vector2.DistanceSquared(referencePos, enemyAI.AttackingLimb.WorldPosition) < enemyAI.AttackingLimb.attack.DamageRange * enemyAI.AttackingLimb.attack.DamageRange)
{
if (AttachToSub && !enemyAI.IsSteeringThroughGap && wallAttachPos != Vector2.Zero && targetBody != null)
{
// is not attached or is attached to something else
if (!IsAttached || IsAttached && AttachJoints[0].BodyB != targetBody)
{
if (Vector2.DistanceSquared(ConvertUnits.ToDisplayUnits(transformedAttachPos), enemyAI.AttackingLimb.WorldPosition) < enemyAI.AttackingLimb.attack.DamageRange * enemyAI.AttackingLimb.attack.DamageRange)
{
AttachToBody(transformedAttachPos);
}
}
}
AttachToBody(transformedAttachPos);
}
break;
default:
@@ -231,43 +277,50 @@ namespace Barotrauma
break;
}
if (IsAttached && targetBody != null && targetWall != null && targetSubmarine != null && deattachTimer <= 0.0f)
if (IsAttached && targetBody != null && deattachCheckTimer <= 0.0f)
{
bool deattach = false;
// Deattach if the wall is broken enough where we are attached to
int targetSection = targetWall.FindSectionIndex(attachLimb.WorldPosition, world: true, clamp: true);
if (enemyAI.CanPassThroughHole(targetWall, targetSection))
if (maxAttachDuration > 0)
{
deattach = true;
attachCooldown = 2;
}
if (!deattach)
if (!deattach && targetWall != null && targetSubmarine != null)
{
// Deattach if the velocity is high
float velocity = targetSubmarine.Velocity == Vector2.Zero ? 0.0f : targetSubmarine.Velocity.Length();
deattach = velocity > maxDeattachSpeed;
// Deattach if the wall is broken enough where we are attached to
int targetSection = targetWall.FindSectionIndex(attachLimb.WorldPosition, world: true, clamp: true);
if (enemyAI.CanPassThroughHole(targetWall, targetSection))
{
deattach = true;
attachCooldown = 2;
}
if (!deattach)
{
if (velocity > minDeattachSpeed)
// Deattach if the velocity is high
float velocity = targetSubmarine.Velocity == Vector2.Zero ? 0.0f : targetSubmarine.Velocity.Length();
deattach = velocity > maxDeattachSpeed;
if (!deattach)
{
float velocityFactor = (maxDeattachSpeed - minDeattachSpeed <= 0.0f) ?
Math.Sign(Math.Abs(velocity) - minDeattachSpeed) :
(Math.Abs(velocity) - minDeattachSpeed) / (maxDeattachSpeed - minDeattachSpeed);
if (Rand.Range(0.0f, 1.0f) < velocityFactor)
if (velocity > minDeattachSpeed)
{
deattach = true;
character.AddDamage(character.WorldPosition, new List<Affliction>() { AfflictionPrefab.InternalDamage.Instantiate(damageOnDetach) }, detachStun, true);
attachCooldown = detachStun * 2;
float velocityFactor = (maxDeattachSpeed - minDeattachSpeed <= 0.0f) ?
Math.Sign(Math.Abs(velocity) - minDeattachSpeed) :
(Math.Abs(velocity) - minDeattachSpeed) / (maxDeattachSpeed - minDeattachSpeed);
if (Rand.Range(0.0f, 1.0f) < velocityFactor)
{
deattach = true;
character.AddDamage(character.WorldPosition, new List<Affliction>() { AfflictionPrefab.InternalDamage.Instantiate(damageOnDetach) }, detachStun, true);
attachCooldown = detachStun * 2;
}
}
}
}
deattachCheckTimer = 5.0f;
}
if (deattach)
{
DeattachFromBody(reset: true);
}
deattachTimer = 5.0f;
}
}
@@ -315,16 +368,30 @@ namespace Barotrauma
}
collider.SetTransform(attachPos + attachSurfaceNormal * colliderFront.Length(), MathUtils.VectorToAngle(-attachSurfaceNormal) - MathHelper.PiOver2);
var colliderJoint = new WeldJoint(collider.FarseerBody, targetBody, colliderFront, targetBody.GetLocalPoint(attachPos), false)
{
FrequencyHz = 10.0f,
DampingRatio = 0.5f,
KinematicBodyB = true,
CollideConnected = false,
//Length = 0.1f
};
Joint colliderJoint = weld ?
new WeldJoint(collider.FarseerBody, targetBody, colliderFront, targetBody.GetLocalPoint(attachPos), false)
{
FrequencyHz = 10.0f,
DampingRatio = 0.5f,
KinematicBodyB = true,
CollideConnected = false,
} :
new RevoluteJoint(collider.FarseerBody, targetBody, colliderFront, targetBody.GetLocalPoint(attachPos), false)
{
MotorEnabled = true,
MaxMotorTorque = 0.25f
} as Joint;
GameMain.World.Add(colliderJoint);
AttachJoints.Add(colliderJoint);
AttachJoints.Add(colliderJoint);
if (targetCharacter != null)
{
targetCharacter.Latchers.Add(this);
}
if (maxAttachDuration > 0)
{
deattachCheckTimer = maxAttachDuration;
}
}
public void DeattachFromBody(bool reset, float cooldown = 0)
@@ -342,14 +409,23 @@ namespace Barotrauma
{
Reset();
}
if (targetCharacter != null)
{
targetCharacter.Latchers.Remove(this);
}
}
private void Reset()
{
if (targetCharacter != null)
{
targetCharacter.Latchers.Remove(this);
}
targetCharacter = null;
targetWall = null;
targetSubmarine = null;
targetBody = null;
WallAttachPos = null;
AttachPos = null;
}
private void OnCharacterDeath(Character character, CauseOfDeath causeOfDeath)
@@ -96,6 +96,7 @@ namespace Barotrauma
#if DEBUG
if (HumanAIController.debugai && objectiveManager.IsOrder(this) && !objectiveManager.IsCurrentOrder<AIObjectiveGoTo>() && !objectiveManager.IsCurrentOrder<AIObjectiveReturn>())
{
// TODO: dismiss
throw new Exception("Order abandoned!");
}
#endif
@@ -111,7 +111,7 @@ namespace Barotrauma
public CombatMode Mode { get; private set; }
private bool IsOffensiveOrArrest => initialMode == CombatMode.Offensive || initialMode == CombatMode.Arrest;
private bool TargetEliminated => IsEnemyDisabled || Enemy.IsUnconscious;
private bool TargetEliminated => IsEnemyDisabled || (Enemy.IsUnconscious && Enemy.Params.Health.ConstantHealthRegeneration <= 0.0f);
private bool IsEnemyDisabled => Enemy == null || Enemy.Removed || Enemy.IsDead;
private float AimSpeed => HumanAIController.AimSpeed;
@@ -56,7 +56,8 @@ namespace Barotrauma
public static bool IsValidTarget(Character target, Character character)
{
if (target == null || target.Removed) { return false; }
if (target.IsDead || target.IsUnconscious) { return false; }
if (target.IsDead) { return false; }
if (target.IsUnconscious && target.Params.Health.ConstantHealthRegeneration <= 0.0f) { return false; }
if (target == character) { return false; }
if (target.Submarine == null) { return false; }
if (character.Submarine == null) { return false; }
@@ -307,6 +307,8 @@ namespace Barotrauma
foreach (Hull hull in Hull.hullList.OrderByDescending(h => EstimateHullSuitability(h)))
{
if (hull.Submarine == null) { continue; }
// Ruins are mazes filled with water. There's no safe hulls and we don't want to use the resources on it.
if (hull.Submarine.Info.IsRuin) { continue; }
if (!allowChangingTheSubmarine && hull.Submarine != character.Submarine) { continue; }
if (hull.Rect.Height < ConvertUnits.ToDisplayUnits(character.AnimController.ColliderHeightFromFloor) * 2) { continue; }
if (ignoredHulls != null && ignoredHulls.Contains(hull)) { continue; }
@@ -461,11 +461,11 @@ namespace Barotrauma
nodeFilter = n => n.Waypoint.Tunnel != null;
}
PathSteering.SteeringSeek(targetPos, 1,
startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (character.CurrentHull == null),
endNodeFilter,
nodeFilter,
CheckVisibility);
PathSteering.SteeringSeek(targetPos, weight: 1,
startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (character.CurrentHull == null),
endNodeFilter: endNodeFilter,
nodeFilter: nodeFilter,
checkVisiblity: CheckVisibility);
if (!isInside && (PathSteering.CurrentPath == null || PathSteering.IsPathDirty || PathSteering.CurrentPath.Unreachable))
{
@@ -167,7 +167,7 @@ namespace Barotrauma
private readonly List<PathNode> sortedNodes = new List<PathNode>();
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)
public SteeringPath FindPath(Vector2 start, Vector2 end, Submarine hostSub = null, string errorMsgStr = null, float minGapSize = 0, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisibility = true)
{
foreach (PathNode node in nodes)
{
@@ -233,6 +233,10 @@ namespace Barotrauma
// Always check the visibility for the start node
if (!IsWaypointVisible(node, start)) { continue; }
if (node.IsBlocked()) { continue; }
if (node.Waypoint.ConnectedGap != null)
{
if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { continue; }
}
startNode = node;
}
}
@@ -282,6 +286,10 @@ namespace Barotrauma
// Only check the visibility for the end node when allowed (fix leaks)
if (!IsWaypointVisible(node, end, checkVisibility: checkVisibility)) { continue; }
if (node.IsBlocked()) { continue; }
if (node.Waypoint.ConnectedGap != null)
{
if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { continue; }
}
endNode = node;
}
}
@@ -294,40 +302,12 @@ namespace Barotrauma
return new SteeringPath(true);
}
var path = FindPath(startNode, endNode, nodeFilter, errorMsgStr);
var path = FindPath(startNode, endNode, nodeFilter, errorMsgStr, minGapSize);
return path;
}
public SteeringPath FindPath(WayPoint start, WayPoint end)
{
PathNode startNode = null, endNode = null;
foreach (PathNode node in nodes)
{
if (node.Waypoint == start)
{
startNode = node;
if (endNode != null) { break; }
}
if (node.Waypoint == end)
{
endNode = node;
if (startNode != null) { break; }
}
}
if (startNode == null || endNode == null)
{
#if DEBUG
DebugConsole.NewMessage("Pathfinding error, couldn't find matching pathnodes to waypoints.", Color.DarkRed);
#endif
return new SteeringPath(true);
}
return FindPath(startNode, endNode);
}
private SteeringPath FindPath(PathNode start, PathNode end, Func<PathNode, bool> filter = null, string errorMsgStr = "")
private SteeringPath FindPath(PathNode start, PathNode end, Func<PathNode, bool> filter = null, string errorMsgStr = "", float minGapSize = 0)
{
if (start == end)
{
@@ -356,7 +336,10 @@ namespace Barotrauma
if (isCharacter && node.Waypoint.isObstructed) { continue; }
if (filter != null && !filter(node)) { continue; }
if (node.IsBlocked()) { continue; }
if (node.Waypoint.ConnectedGap != null)
{
if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { continue; }
}
dist = node.F;
currNode = node;
}
@@ -460,6 +443,8 @@ namespace Barotrauma
return path;
}
private bool CanFitThroughGap(Gap gap, float minWidth) => gap.IsHorizontal ? gap.RectHeight > minWidth : gap.RectWidth > minWidth;
}
}
@@ -1,12 +1,31 @@
using FarseerPhysics;
using Barotrauma.Items.Components;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
abstract class AnimController : Ragdoll
{
public Vector2 RightHandIKPos { get; protected set; }
public Vector2 LeftHandIKPos { get; protected set; }
protected LimbJoint rightShoulder, leftShoulder;
protected float upperArmLength, forearmLength;
protected float useItemTimer;
protected bool aiming;
protected bool wasAiming;
protected bool aimingMelee;
protected bool wasAimingMelee;
public bool IsAiming => wasAiming;
public bool IsAimingMelee => wasAimingMelee;
public float ArmLength => upperArmLength + forearmLength;
public abstract GroundedMovementParams WalkParams { get; set; }
public abstract GroundedMovementParams RunParams { get; set; }
public abstract SwimParams SwimSlowParams { get; set; }
@@ -60,14 +79,14 @@ namespace Barotrauma
}
else
{
return IsMovingFast? SwimFastParams : SwimSlowParams;
return IsMovingFast ? SwimFastParams : SwimSlowParams;
}
}
}
public bool CanWalk => RagdollParams.CanWalk;
public bool IsMovingBackwards => !InWater && Math.Sign(targetMovement.X) == -Math.Sign(Dir);
// TODO: define death anim duration in XML
protected float deathAnimTimer, deathAnimDuration = 5.0f;
@@ -155,13 +174,9 @@ namespace Barotrauma
public AnimController(Character character, string seed, RagdollParams ragdollParams = null) : base(character, seed, ragdollParams) { }
public virtual void UpdateAnim(float deltaTime) { }
public abstract void UpdateAnim(float deltaTime);
public virtual void HoldItem(float deltaTime, Item item, Vector2[] handlePos, Vector2 holdPos, Vector2 aimPos, bool aim, float holdAngle, float itemAngleRelativeToHoldAngle = 0.0f, bool aimingMelee = false) { }
public virtual void DragCharacter(Character target, float deltaTime) { }
public virtual void UpdateUseItem(bool allowMovement, Vector2 handWorldPos) { }
public abstract void DragCharacter(Character target, float deltaTime);
public virtual float GetSpeed(AnimationType type)
{
@@ -253,5 +268,437 @@ namespace Barotrauma
throw new NotImplementedException(type.ToString());
}
}
public void UpdateUseItem(bool allowMovement, Vector2 handWorldPos)
{
useItemTimer = 0.5f;
Anim = Animation.UsingConstruction;
if (!allowMovement)
{
TargetMovement = Vector2.Zero;
TargetDir = handWorldPos.X > character.WorldPosition.X ? Direction.Right : Direction.Left;
float sqrDist = Vector2.DistanceSquared(character.WorldPosition, handWorldPos);
if (sqrDist > MathUtils.Pow(ConvertUnits.ToDisplayUnits(upperArmLength + forearmLength), 2))
{
TargetMovement = Vector2.Normalize(handWorldPos - character.WorldPosition) * GetCurrentSpeed(false) * Math.Max(character.SpeedMultiplier, 1);
}
}
if (!character.Enabled) { return; }
Vector2 handSimPos = ConvertUnits.ToSimUnits(handWorldPos);
if (character.Submarine != null)
{
handSimPos -= character.Submarine.SimPosition;
}
var leftHand = GetLimb(LimbType.LeftHand);
if (leftHand != null)
{
leftHand.Disabled = true;
leftHand.PullJointEnabled = true;
leftHand.PullJointWorldAnchorB = handSimPos;
}
var rightHand = GetLimb(LimbType.RightHand);
if (rightHand != null)
{
rightHand.Disabled = true;
rightHand.PullJointEnabled = true;
rightHand.PullJointWorldAnchorB = handSimPos;
}
}
public void Grab(Vector2 rightHandPos, Vector2 leftHandPos)
{
for (int i = 0; i < 2; i++)
{
Limb pullLimb = (i == 0) ? GetLimb(LimbType.LeftHand) : GetLimb(LimbType.RightHand);
pullLimb.Disabled = true;
pullLimb.PullJointEnabled = true;
pullLimb.PullJointWorldAnchorB = (i == 0) ? rightHandPos : leftHandPos;
pullLimb.PullJointMaxForce = 500.0f;
}
}
private Direction previousDirection;
private readonly Vector2[] transformedHandlePos = new Vector2[2];
//TODO: refactor this method, it's way too convoluted
public void HoldItem(float deltaTime, Item item, Vector2[] handlePos, Vector2 holdPos, Vector2 aimPos, bool aim, float holdAngle, float itemAngleRelativeToHoldAngle = 0.0f, bool aimMelee = false)
{
aimingMelee = aimMelee;
if (character.Stun > 0.0f || character.IsIncapacitated)
{
aim = false;
}
//calculate the handle positions
Matrix itemTransfrom = Matrix.CreateRotationZ(item.body.Rotation);
float horizontalOffset = ConvertUnits.ToSimUnits((item.Sprite.size.X / 2 - item.Sprite.Origin.X) * item.Scale);
//handlePos[0] = ConvertUnits.ToSimUnits(new Vector2(-45,25) * 0.5f);
//handlePos[1] = ConvertUnits.ToSimUnits(new Vector2(-65,30) * 0.5f);
transformedHandlePos[0] = Vector2.Transform(new Vector2(handlePos[0].X + horizontalOffset, handlePos[0].Y), itemTransfrom);
transformedHandlePos[1] = Vector2.Transform(new Vector2(handlePos[1].X + horizontalOffset, handlePos[1].Y), itemTransfrom);
Limb torso = GetLimb(LimbType.Torso) ?? MainLimb;
Limb leftHand = GetLimb(LimbType.LeftHand);
Limb rightHand = GetLimb(LimbType.RightHand);
Vector2 itemPos = aim ? aimPos : holdPos;
var controller = character.SelectedConstruction?.GetComponent<Controller>();
bool usingController = controller != null && !controller.AllowAiming;
bool isClimbing = character.IsClimbing && Math.Abs(character.AnimController.TargetMovement.Y) > 0.01f;
float itemAngle;
Holdable holdable = item.GetComponent<Holdable>();
float torsoRotation = torso.Rotation;
bool equippedInRightHand = character.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand) == item && rightHand != null && !rightHand.IsSevered;
bool equippedInLefthand = character.Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand) == item && leftHand != null && !leftHand.IsSevered;
if (aim && !isClimbing && !usingController && character.Stun <= 0.0f && itemPos != Vector2.Zero && !character.IsIncapacitated)
{
Vector2 mousePos = ConvertUnits.ToSimUnits(character.SmoothedCursorPosition);
Vector2 diff = holdable.Aimable ? (mousePos - AimSourceSimPos) * Dir : Vector2.UnitX;
holdAngle = MathUtils.VectorToAngle(new Vector2(diff.X, diff.Y * Dir)) - torsoRotation * Dir;
holdAngle += GetAimWobble(rightHand, leftHand, item);
itemAngle = torsoRotation + holdAngle * Dir;
if (holdable.ControlPose)
{
var head = GetLimb(LimbType.Head);
if (head != null)
{
head.body.SmoothRotate(itemAngle, force: 30 * head.Mass);
}
if (TargetMovement == Vector2.Zero && inWater)
{
torso.body.AngularVelocity -= torso.body.AngularVelocity * 0.1f;
torso.body.ApplyForce(torso.body.LinearVelocity * -0.5f);
}
aiming = true;
}
}
else
{
if (holdable.UseHandRotationForHoldAngle)
{
if (equippedInRightHand)
{
itemAngle = rightHand.Rotation + holdAngle * Dir;
}
else if (equippedInLefthand)
{
itemAngle = leftHand.Rotation + holdAngle * Dir;
}
else
{
itemAngle = torsoRotation + holdAngle * Dir;
}
}
else
{
itemAngle = torsoRotation + holdAngle * Dir;
}
}
if (rightShoulder == null) { return; }
Vector2 transformedHoldPos = rightShoulder.WorldAnchorA;
if (itemPos == Vector2.Zero || isClimbing || usingController)
{
if (equippedInRightHand)
{
transformedHoldPos = rightHand.PullJointWorldAnchorA - transformedHandlePos[0];
itemAngle = rightHand.Rotation + (holdAngle - rightHand.Params.GetSpriteOrientation() + MathHelper.PiOver2) * Dir;
}
else if (equippedInLefthand)
{
transformedHoldPos = leftHand.PullJointWorldAnchorA - transformedHandlePos[1];
itemAngle = leftHand.Rotation + (holdAngle - leftHand.Params.GetSpriteOrientation() + MathHelper.PiOver2) * Dir;
}
}
else
{
if (equippedInRightHand)
{
transformedHoldPos = rightShoulder.WorldAnchorA;
rightHand.Disabled = true;
}
if (equippedInLefthand)
{
if (leftShoulder == null) { return; }
transformedHoldPos = leftShoulder.WorldAnchorA;
leftHand.Disabled = true;
}
itemPos.X *= Dir;
transformedHoldPos += Vector2.Transform(itemPos, Matrix.CreateRotationZ(itemAngle));
}
item.body.ResetDynamics();
Vector2 currItemPos = equippedInRightHand ?
rightHand.PullJointWorldAnchorA - transformedHandlePos[0] :
leftHand.PullJointWorldAnchorA - transformedHandlePos[1];
if (!MathUtils.IsValid(currItemPos))
{
string errorMsg = "Attempted to move the item \"" + item + "\" to an invalid position in HumanidAnimController.HoldItem: " +
currItemPos + ", rightHandPos: " + rightHand.PullJointWorldAnchorA + ", leftHandPos: " + leftHand.PullJointWorldAnchorA +
", handlePos[0]: " + handlePos[0] + ", handlePos[1]: " + handlePos[1] +
", transformedHandlePos[0]: " + transformedHandlePos[0] + ", transformedHandlePos[1]:" + transformedHandlePos[1] +
", item pos: " + item.SimPosition + ", itemAngle: " + itemAngle +
", collider pos: " + character.SimPosition;
DebugConsole.Log(errorMsg);
GameAnalyticsManager.AddErrorEventOnce(
"HumanoidAnimController.HoldItem:InvalidPos:" + character.Name + item.Name,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
errorMsg);
return;
}
if (holdable.Pusher != null)
{
if (character.Stun > 0.0f || character.IsIncapacitated)
{
holdable.Pusher.Enabled = false;
}
else
{
if (!holdable.Pusher.Enabled)
{
holdable.Pusher.Enabled = true;
holdable.Pusher.ResetDynamics();
holdable.Pusher.SetTransform(currItemPos, itemAngle);
}
else
{
holdable.Pusher.TargetPosition = currItemPos;
holdable.Pusher.TargetRotation = holdAngle * Dir;
holdable.Pusher.MoveToTargetPosition(true);
currItemPos = holdable.Pusher.SimPosition;
itemAngle = holdable.Pusher.Rotation;
}
}
}
float targetAngle = MathUtils.WrapAngleTwoPi(itemAngle + itemAngleRelativeToHoldAngle * Dir);
float currentRotation = MathUtils.WrapAngleTwoPi(item.body.Rotation);
float itemRotation = MathHelper.SmoothStep(currentRotation, targetAngle, deltaTime * 25);
if (previousDirection != dir || Math.Abs(targetAngle - currentRotation) > MathHelper.Pi)
{
itemRotation = targetAngle;
}
item.SetTransform(currItemPos, itemRotation, setPrevTransform: false);
previousDirection = dir;
if (!isClimbing && !character.IsIncapacitated && itemPos != Vector2.Zero && (aim || !holdable.UseHandRotationForHoldAngle))
{
for (int i = 0; i < 2; i++)
{
if (!character.Inventory.IsInLimbSlot(item, i == 0 ? InvSlotType.RightHand : InvSlotType.LeftHand)) { continue; }
#if DEBUG
if (handlePos[i].LengthSquared() > ArmLength)
{
DebugConsole.AddWarning($"Aim position for the item {item.Name} may be incorrect (further than the length of the character's arm)");
}
#endif
HandIK(i == 0 ? rightHand : leftHand, transformedHoldPos + transformedHandlePos[i], CurrentAnimationParams.ArmIKStrength, CurrentAnimationParams.HandIKStrength);
}
}
}
private float GetAimWobble(Limb rightHand, Limb leftHand, Item heldItem)
{
float wobbleStrength = 0.0f;
if (character.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand) == heldItem)
{
wobbleStrength += Character.CharacterHealth.GetLimbDamage(rightHand, afflictionType: "damage");
}
if (character.Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand) == heldItem)
{
wobbleStrength += Character.CharacterHealth.GetLimbDamage(leftHand, afflictionType: "damage");
}
if (wobbleStrength <= 0.1f) { return 0.0f; }
wobbleStrength = (float)Math.Min(wobbleStrength, 1.0f);
float lowFreqNoise = PerlinNoise.GetPerlin((float)Timing.TotalTime / 320.0f, (float)Timing.TotalTime / 240.0f) - 0.5f;
float highFreqNoise = PerlinNoise.GetPerlin((float)Timing.TotalTime / 40.0f, (float)Timing.TotalTime / 50.0f) - 0.5f;
return (lowFreqNoise * 1.0f + highFreqNoise * 0.1f) * wobbleStrength;
}
public void HandIK(Limb hand, Vector2 pos, float armTorque = 1.0f, float handTorque = 1.0f)
{
Vector2 shoulderPos;
Limb arm, forearm;
if (hand.type == LimbType.LeftHand)
{
if (leftShoulder == null) { return; }
shoulderPos = leftShoulder.WorldAnchorA;
arm = GetLimb(LimbType.LeftArm);
forearm = GetLimb(LimbType.LeftForearm);
LeftHandIKPos = pos;
}
else
{
if (rightShoulder == null) { return; }
shoulderPos = rightShoulder.WorldAnchorA;
arm = GetLimb(LimbType.RightArm);
forearm = GetLimb(LimbType.RightForearm);
RightHandIKPos = pos;
}
if (arm == null) { return; }
//distance from shoulder to holdpos
float c = Vector2.Distance(pos, shoulderPos);
c = MathHelper.Clamp(c, Math.Abs(upperArmLength - forearmLength), forearmLength + upperArmLength - 0.01f);
float armAngle = MathUtils.VectorToAngle(pos - shoulderPos) + arm.Params.GetSpriteOrientation() - MathHelper.PiOver2;
float upperArmAngle = MathUtils.SolveTriangleSSS(forearmLength, upperArmLength, c) * Dir;
float lowerArmAngle = MathUtils.SolveTriangleSSS(upperArmLength, forearmLength, c) * Dir;
//make sure the arm angle "has the same number of revolutions" as the arm
while (arm.Rotation - armAngle > MathHelper.Pi)
{
armAngle += MathHelper.TwoPi;
}
while (arm.Rotation - armAngle < -MathHelper.Pi)
{
armAngle -= MathHelper.TwoPi;
}
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 ? forearmAngle : armAngle;
hand?.body.SmoothRotate(handAngle, 10.0f * handTorque * hand.Mass, wrapAngle: false);
}
public void ApplyPose(Vector2 leftHandPos, Vector2 rightHandPos, Vector2 leftFootPos, Vector2 rightFootPos, float footMoveForce = 10)
{
var leftHand = GetLimb(LimbType.LeftHand);
var rightHand = GetLimb(LimbType.RightHand);
var waist = GetLimb(LimbType.Waist) ?? GetLimb(LimbType.Torso);
if (waist == null) { return; }
Vector2 midPos = waist.SimPosition;
if (leftHand != null)
{
leftHand.Disabled = true;
leftHandPos.X *= Dir;
leftHandPos += midPos;
HandIK(leftHand, leftHandPos);
}
if (rightHand != null)
{
rightHand.Disabled = true;
rightHandPos.X *= Dir;
rightHandPos += midPos;
HandIK(rightHand, rightHandPos);
}
var leftFoot = GetLimb(LimbType.LeftFoot);
if (leftFoot != null)
{
leftFoot.Disabled = true;
leftFootPos = new Vector2(waist.SimPosition.X + leftFootPos.X * Dir, GetColliderBottom().Y + leftFootPos.Y);
MoveLimb(leftFoot, leftFootPos, Math.Abs(leftFoot.SimPosition.X - leftFootPos.X) * footMoveForce * leftFoot.Mass, true);
}
var rightFoot = GetLimb(LimbType.RightFoot);
if (rightFoot != null)
{
rightFoot.Disabled = true;
rightFootPos = new Vector2(waist.SimPosition.X + rightFootPos.X * Dir, GetColliderBottom().Y + rightFootPos.Y);
MoveLimb(rightFoot, rightFootPos, Math.Abs(rightFoot.SimPosition.X - rightFootPos.X) * footMoveForce * rightFoot.Mass, true);
}
}
public void ApplyTestPose()
{
var waist = GetLimb(LimbType.Waist) ?? GetLimb(LimbType.Torso);
if (waist != null)
{
ApplyPose(
new Vector2(-0.75f, -0.2f),
new Vector2(0.75f, -0.2f),
new Vector2(-WalkParams.StepSize.X * 0.5f, -0.1f * RagdollParams.JointScale),
new Vector2(WalkParams.StepSize.X * 0.5f, -0.1f * RagdollParams.JointScale));
}
}
protected void CalculateArmLengths()
{
//calculate arm and forearm length (atm this assumes that both arms are the same size)
Limb rightForearm = GetLimb(LimbType.RightForearm);
Limb rightHand = GetLimb(LimbType.RightHand);
if (rightHand == null) { return; }
rightShoulder = GetJointBetweenLimbs(LimbType.Torso, LimbType.RightArm) ?? GetJointBetweenLimbs(LimbType.Head, LimbType.RightArm) ?? GetJoint(LimbType.RightArm, new LimbType[] { LimbType.RightHand, LimbType.RightForearm });
leftShoulder = GetJointBetweenLimbs(LimbType.Torso, LimbType.LeftArm) ?? GetJointBetweenLimbs(LimbType.Head, LimbType.LeftArm) ?? GetJoint(LimbType.LeftArm, new LimbType[] { LimbType.LeftHand, LimbType.LeftForearm });
Vector2 localAnchorShoulder = Vector2.Zero;
Vector2 localAnchorElbow = Vector2.Zero;
if (rightShoulder != null)
{
localAnchorShoulder = rightShoulder.LimbA.type == LimbType.RightArm ? rightShoulder.LocalAnchorA : rightShoulder.LocalAnchorB;
}
LimbJoint rightElbow = rightForearm == null ?
GetJointBetweenLimbs(LimbType.RightArm, LimbType.RightHand) :
GetJointBetweenLimbs(LimbType.RightArm, LimbType.RightForearm);
if (rightElbow != null)
{
localAnchorElbow = rightElbow.LimbA.type == LimbType.RightArm ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB;
}
upperArmLength = Vector2.Distance(localAnchorShoulder, localAnchorElbow);
if (rightElbow != null)
{
if (rightForearm == null)
{
forearmLength = Vector2.Distance(
rightHand.PullJointLocalAnchorA,
rightElbow.LimbA.type == LimbType.RightHand ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB);
}
else
{
LimbJoint rightWrist = GetJointBetweenLimbs(LimbType.RightForearm, LimbType.RightHand);
if (rightWrist != null)
{
forearmLength = Vector2.Distance(
rightElbow.LimbA.type == LimbType.RightForearm ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB,
rightWrist.LimbA.type == LimbType.RightForearm ? rightWrist.LocalAnchorA : rightWrist.LocalAnchorB);
forearmLength += Vector2.Distance(
rightHand.PullJointLocalAnchorA,
rightElbow.LimbA.type == LimbType.RightHand ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB);
}
}
}
}
protected LimbJoint GetJointBetweenLimbs(LimbType limbTypeA, LimbType limbTypeB)
{
return LimbJoints.FirstOrDefault(lj =>
(lj.LimbA.type == limbTypeA && lj.LimbB.type == limbTypeB) ||
(lj.LimbB.type == limbTypeA && lj.LimbA.type == limbTypeB));
}
protected LimbJoint GetJoint(LimbType matchingType, IEnumerable<LimbType> ignoredTypes)
{
return LimbJoints.FirstOrDefault(lj =>
lj.LimbA.type == matchingType && ignoredTypes.None(t => lj.LimbB.type == t) ||
lj.LimbB.type == matchingType && ignoredTypes.None(t => lj.LimbB.type == t));
}
public override void Recreate(RagdollParams ragdollParams = null)
{
base.Recreate(ragdollParams);
if (Character.Params.CanInteract)
{
CalculateArmLengths();
}
}
}
}
@@ -139,7 +139,7 @@ namespace Barotrauma
if (MainLimb == null) { return; }
var mainLimb = MainLimb;
levitatingCollider = true;
levitatingCollider = !IsHanging;
if (!character.CanMove)
{
@@ -192,6 +192,11 @@ namespace Barotrauma
strongestImpact = 0.0f;
}
if (aiming)
{
TargetMovement = TargetMovement.ClampLength(2);
}
if (inWater && !forceStanding)
{
Collider.FarseerBody.FixedRotation = false;
@@ -202,7 +207,7 @@ namespace Barotrauma
if (CurrentGroundedParams != null)
{
//rotate collider back upright
float standAngle = dir == Direction.Right ? CurrentGroundedParams.ColliderStandAngleInRadians : -CurrentGroundedParams.ColliderStandAngleInRadians;
float standAngle = CurrentGroundedParams.ColliderStandAngleInRadians * Dir;
if (Math.Abs(MathUtils.GetShortestAngle(Collider.Rotation, standAngle)) > 0.001f)
{
Collider.AngularVelocity = MathUtils.GetShortestAngle(Collider.Rotation, standAngle) * 60.0f;
@@ -215,17 +220,19 @@ namespace Barotrauma
}
UpdateWalkAnim(deltaTime);
}
if (character.SelectedCharacter != null)
{
DragCharacter(character.SelectedCharacter, deltaTime);
return;
}
if (character.AnimController.AnimationTestPose)
{
ApplyTestPose();
}
//don't flip when simply physics is enabled
if (SimplePhysicsEnabled) { return; }
if (!character.IsRemotelyControlled && (character.AIController == null || character.AIController.CanFlip))
if (!character.IsRemotelyControlled && (character.AIController == null || character.AIController.CanFlip) && !aiming)
{
if (!inWater || (CurrentSwimParams != null && CurrentSwimParams.Mirror))
{
@@ -290,6 +297,10 @@ namespace Barotrauma
{
flipTimer = 0.0f;
}
wasAiming = aiming;
aiming = false;
wasAimingMelee = aimingMelee;
aimingMelee = false;
}
private bool CanDrag(Character target)
@@ -449,6 +460,16 @@ namespace Barotrauma
//limbs are disabled when simple physics is enabled, no need to move them
if (SimplePhysicsEnabled) { return; }
mainLimb.PullJointEnabled = true;
if (aiming && movement.Length() <= 0.1f)
{
Vector2 mousePos = ConvertUnits.ToSimUnits(character.CursorPosition);
Vector2 diff = (mousePos - (GetLimb(LimbType.Torso) ?? MainLimb).SimPosition) * Dir;
TargetMovement = new Vector2(0.0f, -0.1f);
float newRotation = MathUtils.VectorToAngle(diff);
Collider.SmoothRotate(newRotation, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
}
if (!isMoving)
{
WalkPos = MathHelper.SmoothStep(WalkPos, MathHelper.PiOver2, deltaTime * 5);
@@ -146,34 +146,8 @@ namespace Barotrauma
public bool Crouching;
private float upperArmLength = 0.0f, forearmLength = 0.0f;
public float ArmLength => upperArmLength + forearmLength;
public Vector2 RightHandIKPos
{
get;
private set;
}
public Vector2 LeftHandIKPos
{
get;
private set;
}
private LimbJoint rightShoulder, leftShoulder;
private float upperLegLength = 0.0f, lowerLegLength = 0.0f;
private bool aiming;
private bool wasAiming;
private bool aimingMelee;
private bool wasAimingMelee;
public bool IsAiming => wasAiming;
public bool IsAimingMelee => wasAimingMelee;
private readonly float movementLerp;
private float cprAnimTimer;
@@ -184,7 +158,6 @@ namespace Barotrauma
//prevents rapid switches between swimming/walking if the water level is fluctuating around the minimum swimming depth
private float swimmingStateLockTimer;
private float useItemTimer;
public float HeadLeanAmount => CurrentGroundedParams.HeadLeanAmount;
public float TorsoLeanAmount => CurrentGroundedParams.TorsoLeanAmount;
public Vector2 FootMoveOffset => CurrentGroundedParams.FootMoveOffset * RagdollParams.JointScale;
@@ -219,61 +192,12 @@ namespace Barotrauma
movementLerp = RagdollParams.MainElement.GetAttributeFloat("movementlerp", 0.4f);
}
public override void Recreate(RagdollParams ragdollParams)
public override void Recreate(RagdollParams ragdollParams = null)
{
base.Recreate(ragdollParams);
CalculateArmLengths();
CalculateLegLengths();
}
private void CalculateArmLengths()
{
//calculate arm and forearm length (atm this assumes that both arms are the same size)
Limb rightForearm = GetLimb(LimbType.RightForearm);
Limb rightHand = GetLimb(LimbType.RightHand);
if (rightHand == null) { return; }
rightShoulder = GetJointBetweenLimbs(LimbType.Torso, LimbType.RightArm);
leftShoulder = GetJointBetweenLimbs(LimbType.Torso, LimbType.LeftArm);
Vector2 localAnchorShoulder = Vector2.Zero;
Vector2 localAnchorElbow = Vector2.Zero;
if (rightShoulder != null)
{
localAnchorShoulder = rightShoulder.LimbA.type == LimbType.RightArm ? rightShoulder.LocalAnchorA : rightShoulder.LocalAnchorB;
}
LimbJoint rightElbow = rightForearm == null ?
GetJointBetweenLimbs(LimbType.RightArm, LimbType.RightHand) :
GetJointBetweenLimbs(LimbType.RightArm, LimbType.RightForearm);
if (rightElbow != null)
{
localAnchorElbow = rightElbow.LimbA.type == LimbType.RightArm ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB;
}
upperArmLength = Vector2.Distance(localAnchorShoulder, localAnchorElbow);
if (rightElbow != null)
{
if (rightForearm == null)
{
forearmLength = Vector2.Distance(
rightHand.PullJointLocalAnchorA,
rightElbow.LimbA.type == LimbType.RightHand ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB);
}
else
{
LimbJoint rightWrist = GetJointBetweenLimbs(LimbType.RightForearm, LimbType.RightHand);
if (rightWrist != null)
{
forearmLength = Vector2.Distance(
rightElbow.LimbA.type == LimbType.RightForearm ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB,
rightWrist.LimbA.type == LimbType.RightForearm ? rightWrist.LocalAnchorA : rightWrist.LocalAnchorB);
forearmLength += Vector2.Distance(
rightHand.PullJointLocalAnchorA,
rightElbow.LimbA.type == LimbType.RightHand ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB);
}
}
}
}
private void CalculateLegLengths()
{
//calculate upper and lower leg length (atm this assumes that both legs are the same size)
@@ -304,21 +228,16 @@ namespace Barotrauma
ankleJoint.LimbA.type == footType ? ankleJoint.LocalAnchorA : ankleJoint.LocalAnchorB,
GetLimb(footType).PullJointLocalAnchorA);
}
private LimbJoint GetJointBetweenLimbs(LimbType limbTypeA, LimbType limbTypeB)
{
return LimbJoints.FirstOrDefault(lj =>
(lj.LimbA.type == limbTypeA && lj.LimbB.type == limbTypeB) ||
(lj.LimbB.type == limbTypeA && lj.LimbA.type == limbTypeB));
}
public override void UpdateAnim(float deltaTime)
{
if (Frozen) return;
if (MainLimb == null) { return; }
levitatingCollider = true;
levitatingCollider = !IsHanging;
ColliderIndex = Crouching && !swimming ? 1 : 0;
if (character.SelectedConstruction?.GetComponent<Controller>()?.ControlCharacterPose ?? false ||
character.SelectedConstruction?.GetComponent<Ladder>() != null ||
(ForceSelectAnimationType != AnimationType.Crouch && ForceSelectAnimationType != AnimationType.NotDefined))
{
Crouching = false;
@@ -422,41 +341,25 @@ 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, CurrentHumanAnimParams.ArmIKStrength, CurrentHumanAnimParams.HandIKStrength);
HandIK(leftHand, midPos, CurrentHumanAnimParams.ArmIKStrength, CurrentHumanAnimParams.HandIKStrength);
HandIK(rightHand, midPos, CurrentAnimationParams.ArmIKStrength, CurrentAnimationParams.HandIKStrength);
HandIK(leftHand, midPos, CurrentAnimationParams.ArmIKStrength, CurrentAnimationParams.HandIKStrength);
}
else if (character.AnimController.AnimationTestPose)
{
var leftHand = GetLimb(LimbType.LeftHand);
var rightHand = GetLimb(LimbType.RightHand);
var waist = GetLimb(LimbType.Waist) ?? GetLimb(LimbType.Torso);
rightHand.Disabled = true;
leftHand.Disabled = true;
Vector2 midPos = waist.SimPosition;
HandIK(rightHand, midPos + new Vector2(-1, -0.2f) * Dir);
HandIK(leftHand, midPos + new Vector2(1, -0.2f) * Dir);
var leftFoot = GetLimb(LimbType.LeftFoot);
var rightFoot = GetLimb(LimbType.RightFoot);
rightFoot.Disabled = true;
leftFoot.Disabled = true;
// The code here is a bit obscure, but it's pretty much copy-pasted from the block that is used for crouching.
for (int i = -1; i < 2; i += 2)
{
Vector2 footPos = GetColliderBottom();
footPos = new Vector2(waist.SimPosition.X + Math.Sign(WalkParams.StepSize.X * i) * Dir * 0.3f, footPos.Y - 0.1f * RagdollParams.JointScale);
var foot = i == -1 ? rightFoot : leftFoot;
MoveLimb(foot, footPos, Math.Abs(foot.SimPosition.X - footPos.X) * 100.0f, true);
}
ApplyTestPose();
}
else
{
if (Anim != Animation.UsingConstruction) ResetPullJoints();
if (Anim != Animation.UsingConstruction)
{
ResetPullJoints();
}
}
if (SimplePhysicsEnabled)
{
UpdateStandingSimple();
IsHanging = false;
return;
}
@@ -520,12 +423,11 @@ namespace Barotrauma
{
limb.Disabled = false;
}
wasAiming = aiming;
aiming = false;
wasAimingMelee = aimingMelee;
aimingMelee = false;
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) return;
IsHanging = false;
}
void UpdateStanding()
@@ -844,16 +746,18 @@ namespace Barotrauma
var arm = GetLimb(armType);
if (arm != null && Math.Abs(arm.body.AngularVelocity) < 10.0f)
{
arm.body.SmoothRotate(MathHelper.Clamp(-arm.body.AngularVelocity, -0.1f, 0.1f), arm.Mass * 10.0f);
arm.body.SmoothRotate(MathHelper.Clamp(-arm.body.AngularVelocity, -0.5f, 0.5f), arm.Mass * 50.0f);
}
//get the elbow to a neutral rotation
if (Math.Abs(hand.body.AngularVelocity) < 10.0f)
{
LimbJoint elbow = GetJointBetweenLimbs(armType, hand.type) ?? GetJointBetweenLimbs(armType, foreArmType);
var forearm = GetLimb(foreArmType) ?? hand;
LimbJoint elbow = GetJointBetweenLimbs(armType, foreArmType) ?? GetJointBetweenLimbs(armType, hand.type);
if (elbow != null)
{
hand.body.ApplyTorque(MathHelper.Clamp(-elbow.JointAngle, -MathHelper.PiOver2, MathHelper.PiOver2) * hand.Mass * 10.0f);
float diff = elbow.JointAngle - (Dir > 0 ? elbow.LowerLimit : elbow.UpperLimit);
forearm.body.ApplyTorque(MathHelper.Clamp(-diff, -MathHelper.PiOver2, MathHelper.PiOver2) * forearm.Mass * 100.0f);
}
}
}
@@ -1099,6 +1003,7 @@ namespace Barotrauma
rightHandPos.X = (Dir == 1.0f) ? Math.Max(0.3f, rightHandPos.X) : Math.Min(-0.3f, rightHandPos.X);
rightHandPos = Vector2.Transform(rightHandPos, rotationMatrix);
float speedMultiplier = character.SpeedMultiplier * (1 - Character.GetRightHandPenalty());
// Limb hand, Vector2 pos, float force = 1.0f
HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier);
}
@@ -1390,6 +1295,8 @@ namespace Barotrauma
{
target.Oxygen += deltaTime * 0.5f; //Stabilize them
}
bool powerfulCPR = character.HasAbilityFlag(AbilityFlags.PowerfulCPR);
int skill = (int)character.GetSkillLevel("medical");
//pump for 15 seconds (cprAnimTimer 0-15), then do mouth-to-mouth for 2 seconds (cprAnimTimer 15-17)
@@ -1406,13 +1313,19 @@ namespace Barotrauma
{
if (target.Oxygen < -10.0f)
{
//stabilize the oxygen level but don't allow it to go positive and revive the character yet
float stabilizationAmount = skill * CPRSettings.StabilizationPerSkill;
stabilizationAmount = MathHelper.Clamp(stabilizationAmount, CPRSettings.StabilizationMin, CPRSettings.StabilizationMax);
character.Oxygen -= (1.0f / stabilizationAmount) * deltaTime; //Worse skill = more oxygen required
if (character.Oxygen > 0.0f) target.Oxygen += stabilizationAmount * deltaTime; //we didn't suffocate yet did we
//DebugConsole.NewMessage("CPR Us: " + character.Oxygen + " Them: " + target.Oxygen + " How good we are: restore " + cpr + " use " + (30.0f - cpr), Color.Aqua);
if (powerfulCPR)
{
//prevent the patient from suffocating no matter how fast their oxygen level is dropping
target.Oxygen = Math.Max(target.Oxygen, -10.0f);
}
else
{
//stabilize the oxygen level but don't allow it to go positive and revive the character yet
float stabilizationAmount = skill * CPRSettings.StabilizationPerSkill;
stabilizationAmount = MathHelper.Clamp(stabilizationAmount, CPRSettings.StabilizationMin, CPRSettings.StabilizationMax);
character.Oxygen -= 1.0f / stabilizationAmount * deltaTime; //Worse skill = more oxygen required
if (character.Oxygen > 0.0f) { target.Oxygen += stabilizationAmount * deltaTime; } //we didn't suffocate yet did we
}
}
}
}
@@ -1447,6 +1360,8 @@ namespace Barotrauma
reviveChance = (float)Math.Pow(reviveChance, CPRSettings.ReviveChanceExponent);
reviveChance = MathHelper.Clamp(reviveChance, CPRSettings.ReviveChanceMin, CPRSettings.ReviveChanceMax);
if (powerfulCPR) { reviveChance *= 2.0f; }
if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) <= reviveChance)
{
//increase oxygen and clamp it above zero
@@ -1706,248 +1621,6 @@ namespace Barotrauma
}
}
public void Grab(Vector2 rightHandPos, Vector2 leftHandPos)
{
for (int i = 0; i < 2; i++)
{
Limb pullLimb = (i == 0) ? GetLimb(LimbType.LeftHand) : GetLimb(LimbType.RightHand);
pullLimb.Disabled = true;
pullLimb.PullJointEnabled = true;
pullLimb.PullJointWorldAnchorB = (i == 0) ? rightHandPos : leftHandPos;
pullLimb.PullJointMaxForce = 500.0f;
}
}
//TODO: refactor this method, it's way too convoluted
public override void HoldItem(float deltaTime, Item item, Vector2[] handlePos, Vector2 holdPos, Vector2 aimPos, bool aim, float holdAngle, float itemAngleRelativeToHoldAngle = 0.0f, bool aimingMelee = false)
{
if (character.Stun > 0.0f || character.IsIncapacitated)
{
aim = false;
}
//calculate the handle positions
Matrix itemTransfrom = Matrix.CreateRotationZ(item.body.Rotation);
// TODO: don't create new arrays, reuse
Vector2[] transformedHandlePos = new Vector2[2];
transformedHandlePos[0] = Vector2.Transform(handlePos[0], itemTransfrom);
transformedHandlePos[1] = Vector2.Transform(handlePos[1], itemTransfrom);
Limb head = GetLimb(LimbType.Head);
Limb torso = GetLimb(LimbType.Torso);
Limb leftHand = GetLimb(LimbType.LeftHand);
Limb rightHand = GetLimb(LimbType.RightHand);
// TODO: Remove this. Provide the position in params.
Vector2 itemPos = aim ? aimPos : holdPos;
var controller = character.SelectedConstruction?.GetComponent<Controller>();
bool usingController = controller != null && !controller.AllowAiming;
bool isClimbing = character.IsClimbing && Math.Abs(character.AnimController.TargetMovement.Y) > 0.01f;
float itemAngle;
Holdable holdable = item.GetComponent<Holdable>();
this.aimingMelee = aimingMelee;
if (!isClimbing && !usingController && character.Stun <= 0.0f && aim && itemPos != Vector2.Zero && !character.IsIncapacitated)
{
Vector2 mousePos = ConvertUnits.ToSimUnits(character.SmoothedCursorPosition);
Vector2 diff = holdable.Aimable ? (mousePos - AimSourceSimPos) * Dir : Vector2.UnitX;
holdAngle = MathUtils.VectorToAngle(new Vector2(diff.X, diff.Y * Dir)) - torso.body.Rotation * Dir;
holdAngle += GetAimWobble(rightHand, leftHand, item);
itemAngle = torso.body.Rotation + holdAngle * Dir;
if (holdable.ControlPose)
{
head?.body.SmoothRotate(itemAngle);
if (TargetMovement == Vector2.Zero && inWater)
{
torso.body.AngularVelocity -= torso.body.AngularVelocity * 0.1f;
torso.body.ApplyForce(torso.body.LinearVelocity * -0.5f);
}
aiming = true;
}
}
else
{
itemAngle = torso.body.Rotation + holdAngle * Dir;
}
Vector2 transformedHoldPos = rightShoulder.WorldAnchorA;
if (itemPos == Vector2.Zero || isClimbing || usingController)
{
if (character.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand) == item)
{
if (rightHand == null || rightHand.IsSevered) { return; }
transformedHoldPos = rightHand.PullJointWorldAnchorA - transformedHandlePos[0];
itemAngle = (rightHand.Rotation + (holdAngle - MathHelper.PiOver2) * Dir);
}
else if (character.Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand) == item)
{
if (leftHand == null || leftHand.IsSevered) { return; }
transformedHoldPos = leftHand.PullJointWorldAnchorA - transformedHandlePos[1];
itemAngle = (leftHand.Rotation + (holdAngle - MathHelper.PiOver2) * Dir);
}
}
else
{
if (character.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand) == item)
{
if (rightHand == null || rightHand.IsSevered) { return; }
transformedHoldPos = rightShoulder.WorldAnchorA;
rightHand.Disabled = true;
}
if (character.Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand) == item)
{
if (leftHand == null || leftHand.IsSevered) { return; }
transformedHoldPos = leftShoulder.WorldAnchorA;
leftHand.Disabled = true;
}
itemPos.X *= Dir;
transformedHoldPos += Vector2.Transform(itemPos, Matrix.CreateRotationZ(itemAngle));
}
item.body.ResetDynamics();
Vector2 currItemPos = (character.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand) == item) ?
rightHand.PullJointWorldAnchorA - transformedHandlePos[0] :
leftHand.PullJointWorldAnchorA - transformedHandlePos[1];
if (!MathUtils.IsValid(currItemPos))
{
string errorMsg = "Attempted to move the item \"" + item + "\" to an invalid position in HumanidAnimController.HoldItem: " +
currItemPos + ", rightHandPos: " + rightHand.PullJointWorldAnchorA + ", leftHandPos: " + leftHand.PullJointWorldAnchorA +
", handlePos[0]: " + handlePos[0] + ", handlePos[1]: " + handlePos[1] +
", transformedHandlePos[0]: " + transformedHandlePos[0] + ", transformedHandlePos[1]:" + transformedHandlePos[1] +
", item pos: " + item.SimPosition + ", itemAngle: " + itemAngle +
", collider pos: " + character.SimPosition;
DebugConsole.Log(errorMsg);
GameAnalyticsManager.AddErrorEventOnce(
"HumanoidAnimController.HoldItem:InvalidPos:" + character.Name + item.Name,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
errorMsg);
return;
}
if (holdable.Pusher != null)
{
if (character.Stun > 0.0f || character.IsIncapacitated)
{
holdable.Pusher.Enabled = false;
}
else
{
if (!holdable.Pusher.Enabled)
{
holdable.Pusher.Enabled = true;
holdable.Pusher.ResetDynamics();
holdable.Pusher.SetTransform(currItemPos, itemAngle);
}
else
{
holdable.Pusher.TargetPosition = currItemPos;
holdable.Pusher.TargetRotation = holdAngle * Dir;
holdable.Pusher.MoveToTargetPosition(true);
currItemPos = holdable.Pusher.SimPosition;
itemAngle = holdable.Pusher.Rotation;
}
}
}
item.SetTransform(currItemPos, itemAngle + itemAngleRelativeToHoldAngle * Dir, setPrevTransform: false);
if (!isClimbing && !character.IsIncapacitated && itemPos != Vector2.Zero)
{
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], CurrentHumanAnimParams.ArmIKStrength, CurrentHumanAnimParams.HandIKStrength);
}
}
}
private float GetAimWobble(Limb rightHand, Limb leftHand, Item heldItem)
{
float wobbleStrength = 0.0f;
if (character.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand) == heldItem)
{
wobbleStrength += Character.CharacterHealth.GetLimbDamage(rightHand, afflictionType: "damage");
}
if (character.Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand) == heldItem)
{
wobbleStrength += Character.CharacterHealth.GetLimbDamage(leftHand, afflictionType: "damage");
}
if (wobbleStrength <= 0.1f) { return 0.0f; }
wobbleStrength = (float)Math.Min(wobbleStrength, 1.0f);
float lowFreqNoise = PerlinNoise.GetPerlin((float)Timing.TotalTime / 320.0f, (float)Timing.TotalTime / 240.0f) - 0.5f;
float highFreqNoise = PerlinNoise.GetPerlin((float)Timing.TotalTime / 40.0f, (float)Timing.TotalTime / 50.0f) - 0.5f;
return (lowFreqNoise * 1.0f + highFreqNoise * 0.1f) * wobbleStrength;
}
private void HandIK(Limb hand, Vector2 pos, float armTorque = 1.0f, float handTorque = 1.0f)
{
Vector2 shoulderPos;
Limb arm, forearm;
if (hand.type == LimbType.LeftHand)
{
if (leftShoulder == null) { return; }
shoulderPos = leftShoulder.WorldAnchorA;
arm = GetLimb(LimbType.LeftArm);
forearm = GetLimb(LimbType.LeftForearm);
LeftHandIKPos = pos;
}
else
{
if (rightShoulder == null) { return; }
shoulderPos = rightShoulder.WorldAnchorA;
arm = GetLimb(LimbType.RightArm);
forearm = GetLimb(LimbType.RightForearm);
RightHandIKPos = pos;
}
if (arm == null) { return; }
//distance from shoulder to holdpos
float c = Vector2.Distance(pos, shoulderPos);
c = MathHelper.Clamp(c, Math.Abs(upperArmLength - forearmLength), forearmLength + upperArmLength - 0.01f);
float armAngle = MathUtils.VectorToAngle(pos - shoulderPos) + MathHelper.PiOver2;
float upperArmAngle = MathUtils.SolveTriangleSSS(forearmLength, upperArmLength, c) * Dir;
float lowerArmAngle = MathUtils.SolveTriangleSSS(upperArmLength, forearmLength, c) * Dir;
//make sure the arm angle "has the same number of revolutions" as the arm
while (arm.Rotation - armAngle > MathHelper.Pi)
{
armAngle += MathHelper.TwoPi;
}
while (arm.Rotation - armAngle < -MathHelper.Pi)
{
armAngle -= MathHelper.TwoPi;
}
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)
{
if (!MathUtils.IsValid(pos))
@@ -2014,47 +1687,6 @@ namespace Barotrauma
foot.body.SmoothRotate((legAngle - (lowerLegAngle + footAngle) * Dir), foot.Mass * footTorque, wrapAngle: false);
}
public override void UpdateUseItem(bool allowMovement, Vector2 handWorldPos)
{
useItemTimer = 0.5f;
Anim = Animation.UsingConstruction;
if (!allowMovement)
{
TargetMovement = Vector2.Zero;
TargetDir = handWorldPos.X > character.WorldPosition.X ? Direction.Right : Direction.Left;
float sqrDist = Vector2.DistanceSquared(character.WorldPosition, handWorldPos);
if (sqrDist > MathUtils.Pow(ConvertUnits.ToDisplayUnits(upperArmLength + forearmLength), 2))
{
TargetMovement = Vector2.Normalize(handWorldPos - character.WorldPosition) * GetCurrentSpeed(false) * Math.Max(character.SpeedMultiplier, 1);
}
}
if (!character.Enabled) { return; }
Vector2 handSimPos = ConvertUnits.ToSimUnits(handWorldPos);
if (character.Submarine != null)
{
handSimPos -= character.Submarine.SimPosition;
}
var leftHand = GetLimb(LimbType.LeftHand);
if (leftHand != null)
{
leftHand.Disabled = true;
leftHand.PullJointEnabled = true;
leftHand.PullJointWorldAnchorB = handSimPos;
}
var rightHand = GetLimb(LimbType.RightHand);
if (rightHand != null)
{
rightHand.Disabled = true;
rightHand.PullJointEnabled = true;
rightHand.PullJointWorldAnchorB = handSimPos;
}
}
public override void Flip()
{
base.Flip();
@@ -2073,7 +1705,8 @@ namespace Barotrauma
{
heldItem.FlipX(relativeToSub: false);
}
heldItem.FlipX(relativeToSub: false);
// TODO: was this added by a mistake?
//heldItem.FlipX(relativeToSub: false);
}
foreach (Limb limb in Limbs)
@@ -125,7 +125,8 @@ namespace Barotrauma
protected float surfaceY;
protected bool inWater, headInWater;
public bool onGround;
protected bool onGround;
public bool OnGround => onGround;
private Vector2 lastFloorCheckPos;
private bool lastFloorCheckIgnoreStairs, lastFloorCheckIgnorePlatforms;
@@ -887,7 +888,7 @@ namespace Barotrauma
/// <param name="pullFromCenter">if false, force is applied to the position of pullJoint</param>
protected void MoveLimb(Limb limb, Vector2 pos, float amount, bool pullFromCenter = false)
public void MoveLimb(Limb limb, Vector2 pos, float amount, bool pullFromCenter = false)
{
limb.MoveToPos(pos, amount, pullFromCenter);
}
@@ -974,8 +975,7 @@ namespace Barotrauma
Vector2 newSubPos = newHull.Submarine == null ? Vector2.Zero : newHull.Submarine.Position;
Vector2 prevSubPos = currentHull.Submarine == null ? Vector2.Zero : currentHull.Submarine.Position;
Teleport(ConvertUnits.ToSimUnits(prevSubPos - newSubPos),
Vector2.Zero);
Teleport(ConvertUnits.ToSimUnits(prevSubPos - newSubPos), Vector2.Zero);
}
}
@@ -1099,6 +1099,7 @@ namespace Barotrauma
}
public bool forceStanding;
public bool forceNotStanding;
public void Update(float deltaTime, Camera cam)
{
@@ -1270,6 +1271,7 @@ namespace Barotrauma
}
}
UpdateProjSpecific(deltaTime, cam);
forceNotStanding = false;
}
private void CheckBodyInRest(float deltaTime)
@@ -1569,7 +1571,7 @@ namespace Barotrauma
return closestFraction;
}, rayStart, rayEnd, Physics.CollisionStairs | Physics.CollisionPlatform | Physics.CollisionWall | Physics.CollisionLevel);
if (standOnFloorFixture != null)
if (standOnFloorFixture != null && !IsHanging)
{
standOnFloorY = rayStart.Y + (rayEnd.Y - rayStart.Y) * standOnFloorFraction;
if (rayStart.Y - standOnFloorY < Collider.height * 0.5f + Collider.radius + ColliderHeightFromFloor * 1.2f)
@@ -1606,6 +1608,13 @@ namespace Barotrauma
}
if (MainLimb == null) { return; }
if (Character.AIController is EnemyAIController enemyAI && enemyAI.LatchOntoAI != null && enemyAI.LatchOntoAI.IsAttached)
{
enemyAI.LatchOntoAI.DeattachFromBody(reset: true);
}
Character.Latchers.ForEachMod(l => l.DeattachFromBody(reset: true));
Character.Latchers.Clear();
Vector2 limbMoveAmount = forceMainLimbToCollider ? simPosition - MainLimb.SimPosition : simPosition - Collider.SimPosition;
if (lerp)
{
@@ -1629,6 +1638,16 @@ namespace Barotrauma
}
}
public bool IsHanging { get; protected set; }
public void Hang()
{
ResetPullJoints();
onGround = false;
levitatingCollider = false;
IsHanging = true;
}
protected void TrySetLimbPosition(Limb limb, Vector2 original, Vector2 simPosition, bool lerp = false, bool ignorePlatforms = true)
{
Vector2 movePos = simPosition;
@@ -129,6 +129,8 @@ namespace Barotrauma
}
}
public readonly HashSet<LatchOntoAI> Latchers = new HashSet<LatchOntoAI>();
protected readonly Dictionary<string, ActiveTeamChange> activeTeamChanges = new Dictionary<string, ActiveTeamChange>();
protected ActiveTeamChange currentTeamChange;
const string OriginalTeamIdentifier = "original";
@@ -264,6 +266,7 @@ namespace Barotrauma
public readonly CharacterParams Params;
public string SpeciesName => Params.SpeciesName;
public string Group => Params.Group;
public bool IsHumanoid => Params.Humanoid;
public bool IsHusk => Params.Husk;
@@ -473,8 +476,7 @@ namespace Barotrauma
return true;
}
}
public bool CanInteract => AllowInput && IsHumanoid && !LockHands;
public bool CanInteract => AllowInput && Params.CanInteract && !LockHands;
// Eating is not implemented for humanoids. If we implement that at some point, we could remove this restriction.
public bool CanEat => !IsHumanoid && Params.CanEat && AllowInput && AnimController.GetLimb(LimbType.Head) != null;
@@ -592,6 +594,7 @@ namespace Barotrauma
get
{
if (IsUnconscious) { return true; }
if (IsDead) { return true; }
return CharacterHealth.Afflictions.Any(a => a.Prefab.AfflictionType == "paralysis" && a.Strength >= a.Prefab.MaxStrength);
}
}
@@ -626,7 +629,7 @@ namespace Barotrauma
public float Stun
{
get { return IsRagdolled ? 1.0f : CharacterHealth.Stun; }
get { return IsRagdolled && !AnimController.IsHanging ? 1.0f : CharacterHealth.Stun; }
set
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
@@ -1172,6 +1175,7 @@ namespace Barotrauma
{
LoadHeadAttachments();
}
ApplyStatusEffects(ActionType.OnSpawn, 1.0f);
}
partial void InitProjSpecific(XElement mainElement);
@@ -1669,7 +1673,7 @@ namespace Barotrauma
}
if (!aiControlled &&
AnimController.onGround &&
AnimController.OnGround &&
!AnimController.InWater &&
AnimController.Anim != AnimController.Animation.UsingConstruction &&
AnimController.Anim != AnimController.Animation.CPR &&
@@ -2697,6 +2701,11 @@ namespace Barotrauma
ApplyStatusEffects(AnimController.InWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
ApplyStatusEffects(ActionType.OnActive, deltaTime);
if (aiTarget != null)
{
aiTarget.InDetectable = false;
}
UpdateControlled(deltaTime, cam);
//Health effects
@@ -2720,7 +2729,7 @@ namespace Barotrauma
//Do ragdoll shenanigans before Stun because it's still technically a stun, innit? Less network updates for us!
bool allowRagdoll = GameMain.NetworkMember?.ServerSettings?.AllowRagdollButton ?? true;
bool tooFastToUnragdoll = AnimController.Collider.LinearVelocity.LengthSquared() > 2.5f * 2.5f;
bool tooFastToUnragdoll = AnimController.Collider.LinearVelocity.LengthSquared() > 8.0f * 8.0f;
bool wasRagdolled = false;
bool selfRagdolled = false;
@@ -2838,7 +2847,7 @@ namespace Barotrauma
// If the damage is very low, let's not forget so quickly, or we can't cumulate the damage from repair tools (high frequency, low damage)
reduction *= 0.5f;
}
enemy.Damage = Math.Max(0.0f, enemy.Damage-reduction);
enemy.Damage = Math.Max(0.0f, enemy.Damage - reduction);
}
}
}
@@ -3514,7 +3523,7 @@ namespace Barotrauma
if (Removed) { return new AttackResult(); }
if (attacker != null && GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowFriendlyFire)
if (attacker != null && attacker != this && GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowFriendlyFire)
{
if (attacker.TeamID == TeamID) { return new AttackResult(); }
}
@@ -3676,6 +3685,7 @@ namespace Barotrauma
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && !isNetworkMessage) { return; }
if (Screen.Selected != GameMain.GameScreen) { return; }
if (newStun > 0 && Params.Health.StunImmunity) { return; }
if ((newStun <= Stun && !allowStunDecrease) || !MathUtils.IsValid(newStun)) { return; }
if (Math.Sign(newStun) != Math.Sign(Stun))
{
@@ -3876,9 +3886,12 @@ namespace Barotrauma
AnimController.movement = Vector2.Zero;
AnimController.TargetMovement = Vector2.Zero;
foreach (Item heldItem in HeldItems.ToList())
if (!LockHands)
{
heldItem.Drop(this);
foreach (Item heldItem in HeldItems.ToList())
{
heldItem.Drop(this);
}
}
SelectedConstruction = null;
@@ -4435,6 +4448,11 @@ namespace Barotrauma
/// </summary>
private readonly Dictionary<StatTypes, float> statValues = new Dictionary<StatTypes, float>();
/// <summary>
/// A dictionary with temporary values, updated when the character equips/unequips wearables. Used to reduce unnecessary inventory checking.
/// </summary>
private readonly Dictionary<StatTypes, float> wearableStatValues = new Dictionary<StatTypes, float>();
public float GetStatValue(StatTypes statType)
{
if (!IsHuman) { return 0f; }
@@ -4453,23 +4471,37 @@ namespace Barotrauma
// could be optimized by instead updating the Character.cs statvalues dictionary whenever the CharacterInfo.cs values change
statValue += Info.GetSavedStatValue(statType);
}
//replace by updating the character wearable stat values when equipping or unequipping wearables
for (int i = 0; i < Inventory.Capacity; i++)
if (wearableStatValues.TryGetValue(statType, out float wearableValue))
{
if (Inventory.SlotTypes[i] != InvSlotType.Any && Inventory.SlotTypes[i] != InvSlotType.LeftHand && Inventory.SlotTypes[i] != InvSlotType.RightHand
&& Inventory.GetItemAt(i)?.GetComponent<Wearable>() is Wearable wearable)
{
if (wearable.WearableStatValues.TryGetValue(statType, out float wearableValue))
{
statValue += wearableValue;
}
}
statValue += wearableValue;
}
return statValue;
}
public void OnWearablesChanged()
{
wearableStatValues.Clear();
for (int i = 0; i < Inventory.Capacity; i++)
{
if (Inventory.SlotTypes[i] != InvSlotType.Any && Inventory.SlotTypes[i] != InvSlotType.LeftHand && Inventory.SlotTypes[i] != InvSlotType.RightHand
&& Inventory.GetItemAt(i)?.GetComponent<Wearable>() is Wearable wearable)
{
foreach (var statValuePair in wearable.WearableStatValues)
{
if (wearableStatValues.ContainsKey(statValuePair.Key))
{
wearableStatValues[statValuePair.Key] += statValuePair.Value;
}
else
{
wearableStatValues.Add(statValuePair.Key, statValuePair.Value);
}
}
}
}
}
public void ChangeStat(StatTypes statType, float value)
{
if (statValues.ContainsKey(statType))
@@ -4516,7 +4548,7 @@ namespace Barotrauma
public bool HasAbilityFlag(AbilityFlags abilityFlag)
{
return abilityFlags.Contains(abilityFlag);
return abilityFlags.Contains(abilityFlag) || CharacterHealth.HasFlag(abilityFlag);
}
private readonly Dictionary<string, float> abilityResistances = new Dictionary<string, float>();
@@ -218,30 +218,30 @@ namespace Barotrauma
public int AdditionalTalentPoints { get; set; }
private Sprite headSprite;
private Sprite _headSprite;
public Sprite HeadSprite
{
get
{
if (headSprite == null)
if (_headSprite == null)
{
LoadHeadSprite();
}
#if CLIENT
if (headSprite != null)
if (_headSprite != null)
{
CalculateHeadPosition(headSprite);
CalculateHeadPosition(_headSprite);
}
#endif
return headSprite;
return _headSprite;
}
private set
{
if (headSprite != null)
if (_headSprite != null)
{
headSprite.Remove();
_headSprite.Remove();
}
headSprite = value;
_headSprite = value;
}
}
@@ -287,6 +287,7 @@ namespace Barotrauma
Character.CharacterHealth.ApplyAffliction(Character.AnimController.GetLimb(LimbType.Head), AfflictionPrefab.List.FirstOrDefault(a => a.Identifier.Equals("disguised", StringComparison.OrdinalIgnoreCase)).Instantiate(100f));
}
idCard ??= Character.Inventory?.GetItemInLimbSlot(InvSlotType.Card)?.GetComponent<IdCard>();
if (idCard != null)
{
#if CLIENT
@@ -294,19 +295,6 @@ namespace Barotrauma
#endif
return;
}
if (Character.Inventory != null)
{
idCard = Character.Inventory.GetItemInLimbSlot(InvSlotType.Card)?.GetComponent<IdCard>();
if (idCard != null)
{
#if CLIENT
GetDisguisedSprites(idCard);
#endif
return;
}
}
}
#if CLIENT
@@ -1085,7 +1073,7 @@ namespace Barotrauma
}
}
private static List<XElement> AddEmpty(IEnumerable<XElement> elements, WearableType type, float commonness = 1)
public static List<XElement> AddEmpty(IEnumerable<XElement> elements, WearableType type, float commonness = 1)
{
// Let's add an empty element so that there's a chance that we don't get any actual element -> allows bald and beardless guys, for example.
var emptyElement = new XElement("EmptyWearable", type.ToString(), new XAttribute("commonness", commonness));
@@ -1094,9 +1082,9 @@ namespace Barotrauma
return list;
}
private XElement GetRandomElement(IEnumerable<XElement> elements)
public XElement GetRandomElement(IEnumerable<XElement> elements)
{
var filtered = elements.Where(e => IsWearableAllowed(e));
var filtered = elements.Where(IsWearableAllowed);
if (filtered.Count() == 0) { return null; }
var element = ToolBox.SelectWeightedRandom(filtered.ToList(), GetWeights(filtered).ToList(), Rand.RandSync.Unsynced);
return element == null || element.Name == "Empty" ? null : element;
@@ -1121,7 +1109,7 @@ namespace Barotrauma
return true;
}
private static bool IsValidIndex(int index, List<XElement> list) => index >= 0 && index < list.Count;
public static bool IsValidIndex(int index, List<XElement> list) => index >= 0 && index < list.Count;
private static IEnumerable<float> GetWeights(IEnumerable<XElement> elements) => elements.Select(h => h.GetAttributeFloat("commonness", 1f));
@@ -1219,8 +1207,8 @@ namespace Barotrauma
OnExperienceChanged(prevAmount, ExperiencePoints, Character.Position + Vector2.UnitY * 150.0f);
}
const int BaseExperienceRequired = 150;
const int AddedExperienceRequiredPerLevel = 350;
const int BaseExperienceRequired = 50;
const int AddedExperienceRequiredPerLevel = 450;
public int GetTotalTalentPoints()
{
@@ -191,6 +191,30 @@ namespace Barotrauma
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
}
public Color GetFaceTint()
{
if (Strength < Prefab.ActivationThreshold) { return Color.TransparentBlack; }
AfflictionPrefab.Effect currentEffect = GetActiveEffect();
if (currentEffect == null) { return Color.TransparentBlack; }
return Color.Lerp(
currentEffect.MinFaceTint,
currentEffect.MaxFaceTint,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
}
public Color GetBodyTint()
{
if (Strength < Prefab.ActivationThreshold) { return Color.TransparentBlack; }
AfflictionPrefab.Effect currentEffect = GetActiveEffect();
if (currentEffect == null) { return Color.TransparentBlack; }
return Color.Lerp(
currentEffect.MinBodyTint,
currentEffect.MaxBodyTint,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
}
public float GetScreenBlurStrength()
{
if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
@@ -277,6 +301,13 @@ namespace Barotrauma
return 0.0f;
}
public bool HasFlag(AbilityFlags flagType)
{
if (!(GetViableEffect() is AfflictionPrefab.Effect currentEffect)) { return false; }
return currentEffect.AfflictionAbilityFlags.Contains(flagType);
}
private AfflictionPrefab.Effect GetViableEffect()
{
if (Strength < Prefab.ActivationThreshold) { return null; }
@@ -3,6 +3,7 @@ using System.Linq;
using System.Xml.Linq;
using System;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
@@ -216,6 +217,11 @@ namespace Barotrauma
XElement parentElement = new XElement("CharacterInfo");
XElement infoElement = character.Info?.Save(parentElement);
CharacterInfo huskCharacterInfo = infoElement == null ? null : new CharacterInfo(infoElement);
var bodyTint = GetBodyTint();
huskCharacterInfo.SkinColor =
Color.Lerp(huskCharacterInfo.SkinColor, bodyTint.Opaque(), bodyTint.A / 255.0f);
var husk = Character.Create(huskedSpeciesName, character.WorldPosition, ToolBox.RandomSeed(8), huskCharacterInfo, isRemotePlayer: false, hasAi: true);
if (husk.Info != null)
{
@@ -1,11 +1,10 @@
using Microsoft.Xna.Framework;
using Barotrauma.Abilities;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;
using System.Linq;
using System.Security.Cryptography;
using Barotrauma.Abilities;
namespace Barotrauma
{
@@ -223,7 +222,20 @@ namespace Barotrauma
[Serialize("", false)]
public string DialogFlag { get; private set; }
[Serialize("0,0,0,0", false)]
public Color MinFaceTint { get; private set; }
[Serialize("0,0,0,0", false)]
public Color MaxFaceTint { get; private set; }
[Serialize("0,0,0,0", false)]
public Color MinBodyTint { get; private set; }
[Serialize("0,0,0,0", false)]
public Color MaxBodyTint { get; private set; }
public readonly Dictionary<StatTypes, (float minValue, float maxValue)> AfflictionStatValues = new Dictionary<StatTypes, (float minValue, float maxValue)>();
public readonly HashSet<AbilityFlags> AfflictionAbilityFlags = new HashSet<AbilityFlags>();
//statuseffects applied on the character when the affliction is active
public readonly List<StatusEffect> StatusEffects = new List<StatusEffect>();
@@ -250,6 +262,10 @@ namespace Barotrauma
AfflictionStatValues.TryAdd(statType, (minValue, maxValue));
break;
case "abilityflag":
var flagType = CharacterAbilityGroup.ParseFlagType(subElement.GetAttributeString("flagtype", ""), parentDebugName);
AfflictionAbilityFlags.Add(flagType);
break;
}
}
}
@@ -133,7 +133,7 @@ namespace Barotrauma
public bool IsUnconscious
{
get { return Vitality <= 0.0f || Character.IsDead; }
get { return (Vitality <= 0.0f || Character.IsDead) && !Character.HasAbilityFlag(AbilityFlags.AlwaysStayConscious); }
}
public float PressureKillDelay { get; private set; } = 5.0f;
@@ -169,6 +169,20 @@ namespace Barotrauma
}
}
public Color DefaultFaceTint = Color.TransparentBlack;
public Color FaceTint
{
get;
private set;
}
public Color BodyTint
{
get;
private set;
}
public float OxygenAmount
{
get
@@ -409,7 +423,7 @@ namespace Barotrauma
return strength;
}
public void ApplyAffliction(Limb targetLimb, Affliction affliction)
public void ApplyAffliction(Limb targetLimb, Affliction affliction, bool allowStacking = true)
{
if (!affliction.Prefab.IsBuff && Unkillable || Character.GodMode) { return; }
if (affliction.Prefab.LimbSpecific)
@@ -419,17 +433,17 @@ namespace Barotrauma
//if a limb-specific affliction is applied to no specific limb, apply to all limbs
foreach (LimbHealth limbHealth in limbHealths)
{
AddLimbAffliction(limbHealth, affliction);
AddLimbAffliction(limbHealth, affliction, allowStacking: allowStacking);
}
}
else
{
AddLimbAffliction(targetLimb, affliction);
AddLimbAffliction(targetLimb, affliction, allowStacking: allowStacking);
}
}
else
{
AddAffliction(affliction);
AddAffliction(affliction, allowStacking: allowStacking);
}
}
@@ -453,6 +467,15 @@ namespace Barotrauma
return value;
}
public bool HasFlag(AbilityFlags flagType)
{
for (int i = 0; i < afflictions.Count; i++)
{
if (afflictions[i].HasFlag(flagType)) { return true; }
}
return false;
}
private readonly List<Affliction> matchingAfflictions = new List<Affliction>();
public void ReduceAffliction(Limb targetLimb, string affliction, float amount, ActionType? treatmentAction = null)
{
@@ -671,6 +694,7 @@ namespace Barotrauma
private void AddAffliction(Affliction newAffliction, bool allowStacking = true)
{
if (!DoesBleed && newAffliction is AfflictionBleeding) { return; }
if (Character.Params.Health.StunImmunity && newAffliction.Prefab.AfflictionType == "stun") { return; }
if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; }
if (newAffliction.Prefab is AfflictionPrefabHusk huskPrefab)
{
@@ -725,6 +749,8 @@ namespace Barotrauma
StunTimer = Stun > 0 ? StunTimer + deltaTime : 0;
FaceTint = DefaultFaceTint;
for (int i = 0; i < limbHealths.Count; i++)
{
for (int j = limbHealths[i].Afflictions.Count - 1; j >= 0; j--)
@@ -749,10 +775,14 @@ namespace Barotrauma
{
UpdateBleedingProjSpecific(bleeding, targetLimb, deltaTime);
}
Color faceTint = affliction.GetFaceTint();
if (faceTint.A > FaceTint.A) { FaceTint = faceTint; }
Color bodyTint = affliction.GetBodyTint();
if (bodyTint.A > BodyTint.A) { BodyTint = bodyTint; }
Character.StackSpeedMultiplier(affliction.GetSpeedMultiplier());
}
}
for (int i = afflictions.Count - 1; i >= 0; i--)
{
var affliction = afflictions[i];
@@ -768,6 +798,10 @@ namespace Barotrauma
var affliction = afflictions[i];
affliction.Update(this, null, deltaTime);
affliction.DamagePerSecondTimer += deltaTime;
Color faceTint = affliction.GetFaceTint();
if (faceTint.A > FaceTint.A) { FaceTint = faceTint; }
Color bodyTint = affliction.GetBodyTint();
if (bodyTint.A > BodyTint.A) { BodyTint = bodyTint; }
Character.StackSpeedMultiplier(affliction.GetSpeedMultiplier());
}
@@ -543,6 +543,8 @@ namespace Barotrauma
get
{
if (character.IsHumanoid) { return false; }
// TODO: We might need this or solve the cases where a limb is severed while holding on to an item
//if (character.Params.CanInteract) { return false; }
if (this == character.AnimController.MainLimb) { return false; }
if (character.AnimController.CanWalk)
{
@@ -129,6 +129,12 @@ namespace Barotrauma
[Serialize(AnimationType.NotDefined, true), Editable]
public virtual AnimationType AnimationType { get; protected 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 static string GetDefaultFileName(string speciesName, AnimationType animType) => $"{speciesName.CapitaliseFirstInvariant()}{animType}";
public static string GetDefaultFile(string speciesName, AnimationType animType) => Path.Combine(GetFolder(speciesName), $"{GetDefaultFileName(speciesName, animType)}.xml");
@@ -94,12 +94,6 @@ namespace Barotrauma
[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
@@ -153,12 +147,6 @@ namespace Barotrauma
[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
@@ -169,9 +157,5 @@ namespace Barotrauma
float ArmMoveStrength { get; set; }
float HandMoveStrength { get; set; }
float ArmIKStrength { get; set; }
float HandIKStrength { get; set; }
}
}
@@ -34,6 +34,9 @@ namespace Barotrauma
[Serialize(false, true), Editable(ReadOnly = true)]
public bool HasInfo { get; private set; }
[Serialize(false, true, description: "Can the creature interact with items?"), Editable]
public bool CanInteract { get; private set; }
[Serialize(false, true), Editable]
public bool Husk { get; private set; }
@@ -454,6 +457,9 @@ namespace Barotrauma
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
public float HealthRegenerationWhenEating { get; private set; }
[Serialize(false, true), Editable]
public bool StunImmunity { get; set; }
// TODO: limbhealths, sprite?
public HealthParams(XElement element, CharacterParams character) : base(element, character) { }
@@ -553,15 +559,24 @@ namespace Barotrauma
[Serialize(false, true, description: "If enabled, the character chooses randomly from the available attacks. The priority is used as a weight for weighted random."), Editable]
public bool RandomAttack { get; private set; }
[Serialize(false, true, description:"Does the creature know how to open doors (still requires a proper ID card). Only applies on humanoids. Humans can always open doors (They don't use this AI definition)."), Editable]
[Serialize(false, true, description:"Does the creature know how to open doors (still requires a proper ID card). Humans can always open doors (They don't use this AI definition)."), Editable]
public bool CanOpenDoors { get; private set; }
[Serialize(false, true, description: "Does the creature close the doors behind it. Humans don't use this AI definition."), Editable]
public bool KeepDoorsClosed { get; private set; }
[Serialize(true, true, "Is the creature allowed to navigate from and into the depths of the abyss? When enabled, the creatures will try to avoid the depths."), Editable]
public bool AvoidAbyss { get; set; }
[Serialize(false, true, "Does the creature try to keep in the abyss? Has effect only when AvoidAbyss is false."), Editable]
public bool StayInAbyss { get; set; }
[Serialize(false, true, "Does the creature patrol the flooded hulls while idling inside a friendly submarine?"), Editable]
public bool PatrolFlooded { get; set; }
[Serialize(false, true, "Does the creature patrol the dry hulls while idling inside a friendly submarine?"), Editable]
public bool PatrolDry { get; set; }
[Serialize(0f, true, description: ""), Editable]
public float StartAggression { get; private set; }
@@ -682,8 +697,14 @@ namespace Barotrauma
[Serialize(false, true), Editable]
public bool IgnoreIncapacitated { get; set; }
[Serialize(0f, true, description: "How much damage the protected target should take from an attacker before the creature starts defending it."), Editable]
public float DamageThreshold { get; private set; }
[Serialize(0f, true, description: "A generic threshold. For example, how much damage the protected target should take from an attacker before the creature starts defending it."), Editable]
public float Threshold { get; private set; }
[Serialize(-1f, true, description: "A generic min threshold. Not used if set to negative."), Editable]
public float ThresholdMin { get; private set; }
[Serialize(-1f, true, description: "A generic max threshold. Not used if set to negative."), Editable]
public float ThresholdMax { get; private set; }
[Serialize(AttackPattern.Straight, true), Editable]
public AttackPattern AttackPattern { get; set; }
@@ -37,7 +37,7 @@ namespace Barotrauma
[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)]
[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'."), Editable(-360, 360)]
public float SpritesheetOrientation { get; set; }
public bool IsSpritesheetOrientationHorizontal
@@ -572,7 +572,9 @@ namespace Barotrauma
/// <summary>
/// The orientation of the sprite as drawn on the sprite sheet (in radians).
/// </summary>
public float GetSpriteOrientation() => MathHelper.ToRadians(float.IsNaN(SpriteOrientation) ? Ragdoll.SpritesheetOrientation : SpriteOrientation);
public float GetSpriteOrientation() => MathHelper.ToRadians(GetSpriteOrientationInDegrees());
public float GetSpriteOrientationInDegrees() => float.IsNaN(SpriteOrientation) ? Ragdoll.SpritesheetOrientation : SpriteOrientation;
[Serialize("", true), Editable]
public string Notes { get; set; }
@@ -592,7 +594,7 @@ namespace Barotrauma
[Serialize(false, true, description: "Disable drawing for this limb."), Editable()]
public bool Hide { get; set; }
[Serialize(float.NaN, true, description: "The orientation of the sprite as drawn on the sprite sheet. Overrides the value defined in the Ragdoll settings. Used mainly for animations and widgets."), Editable(-360, 360, ValueStep = 90, DecimalCount = 0)]
[Serialize(float.NaN, true, description: "The orientation of the sprite as drawn on the sprite sheet. Overrides the value defined in the Ragdoll settings."), Editable(-360, 360, ValueStep = 90, DecimalCount = 0)]
public float SpriteOrientation { get; set; }
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
@@ -23,7 +23,7 @@ namespace Barotrauma.Abilities
if (afflictions.Any())
{
if (!afflictions.Any(a => attackResult.Afflictions.Select(c => c.Identifier).Contains(a))) { return false; }
if (attackResult.Afflictions == null || !afflictions.Any(a => attackResult.Afflictions.Select(c => c.Identifier).Contains(a))) { return false; }
}
return true;
@@ -2,10 +2,10 @@
namespace Barotrauma.Abilities
{
class AbilityConditionScavenger : AbilityConditionData
class AbilityConditionItemOutsideSubmarine : AbilityConditionData
{
public AbilityConditionScavenger(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
public AbilityConditionItemOutsideSubmarine(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
protected override bool MatchesConditionSpecific(AbilityObject abilityObject)
{
@@ -2,10 +2,10 @@
namespace Barotrauma.Abilities
{
class AbilityConditionScrounger : AbilityConditionData
class AbilityConditionItemWreck : AbilityConditionData
{
public AbilityConditionScrounger(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
public AbilityConditionItemWreck(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
protected override bool MatchesConditionSpecific(AbilityObject abilityObject)
{
@@ -5,19 +5,25 @@ namespace Barotrauma.Abilities
{
class AbilityConditionHasPermanentStat : AbilityConditionDataless
{
private readonly string statIdentifier;
private readonly StatTypes statType;
private readonly float min;
public AbilityConditionHasPermanentStat(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
statType = CharacterAbilityGroup.ParseStatType(conditionElement.GetAttributeString("stattype", ""), characterTalent.DebugIdentifier);
statIdentifier = conditionElement.GetAttributeString("statidentifier", string.Empty);
if (string.IsNullOrEmpty(statIdentifier))
{
DebugConsole.ThrowError($"No stat identifier defined for {this} in talent {characterTalent.DebugIdentifier}!");
}
string statTypeName = conditionElement.GetAttributeString("stattype", string.Empty);
statType = string.IsNullOrEmpty(statTypeName) ? StatTypes.None : CharacterAbilityGroup.ParseStatType(statTypeName, characterTalent.DebugIdentifier);
min = conditionElement.GetAttributeFloat("min", 0f);
}
protected override bool MatchesConditionSpecific()
{
// should consider decoupling this from stat values entirely
return character.Info.GetSavedStatValue(statType) >= min;
return character.Info.GetSavedStatValue(statType, statIdentifier) >= min;
}
}
}
@@ -0,0 +1,24 @@
using Barotrauma.Items.Components;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionHasSkill : AbilityConditionDataless
{
private readonly string skillIdentifier;
private readonly float minValue;
public AbilityConditionHasSkill(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
skillIdentifier = conditionElement.GetAttributeString("skillidentifier", string.Empty);
minValue = conditionElement.GetAttributeFloat("minvalue", 0f);
}
protected override bool MatchesConditionSpecific()
{
return character.GetSkillLevel(skillIdentifier) >= minValue;
}
}
}
@@ -2,7 +2,7 @@
namespace Barotrauma.Abilities
{
class AbilityObject
abstract class AbilityObject
{
// kept as blank for now, as we are using a composition and only using this object to enforce parameter types
}
@@ -160,6 +160,22 @@ namespace Barotrauma.Abilities
}
}
class AbilityApplyTreatment : AbilityObject, IAbilityCharacter, IAbilityItem
{
public Character Character { get; set; }
public Character User { get; set; }
public Item Item { get; set; }
public AbilityApplyTreatment(Character user, Character target, Item item)
{
Character = target;
User = user;
Item = item;
}
}
class AbilityAttackResult : AbilityObject, IAbilityAttackResult
{
public AttackResult AttackResult { get; set; }
@@ -62,7 +62,7 @@ namespace Barotrauma.Abilities
protected virtual void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
{
DebugConsole.ThrowError($"Ability {this} does not have an implementation for VerifyState! This ability does not work in interval ability groups.");
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}: Ability {this} does not have an implementation for VerifyState! This ability does not work in interval ability groups.");
}
public void ApplyAbilityEffect(AbilityObject abilityObject)
@@ -128,14 +128,5 @@ namespace Barotrauma.Abilities
DebugConsole.AddWarning("Instantiated " + characterAbility + " for talent " + characterAbilityGroup.CharacterTalent.DebugIdentifier);
return characterAbility;
}
public static AbilityFlags ParseFlagType(string flagTypeString, string debugIdentifier)
{
AbilityFlags flagType = AbilityFlags.None;
if (!Enum.TryParse(flagTypeString, true, out flagType))
{
DebugConsole.ThrowError("Invalid flag type type \"" + flagTypeString + "\" in CharacterTalent (" + debugIdentifier + ")");
}
return flagType;
}
}
}
@@ -0,0 +1,44 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityGiveAffliction : CharacterAbility
{
private readonly string afflictionId;
private readonly float strength;
private readonly string multiplyStrengthBySkill;
private readonly bool setValue;
public CharacterAbilityGiveAffliction(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
afflictionId = abilityElement.GetAttributeString("afflictionid", abilityElement.GetAttributeString("affliction", string.Empty));
strength = abilityElement.GetAttributeFloat("strength", 0f);
multiplyStrengthBySkill = abilityElement.GetAttributeString("multiplystrengthbyskill", string.Empty);
setValue = abilityElement.GetAttributeBool("setvalue", false);
if (string.IsNullOrEmpty(afflictionId))
{
DebugConsole.ThrowError("Error in CharacterAbilityGiveAffliction - affliction identifier not set.");
}
}
protected override void ApplyEffect(AbilityObject abilityObject)
{
if (abilityObject is IAbilityCharacter character)
{
var afflictionPrefab = AfflictionPrefab.Prefabs.Find(a => a.Identifier.Equals(afflictionId, System.StringComparison.OrdinalIgnoreCase));
if (afflictionPrefab == null)
{
DebugConsole.ThrowError($"Error in CharacterAbilityGiveAffliction - could not find an affliction with the identifier \"{afflictionId}\".");
return;
}
float strength = this.strength;
if (!string.IsNullOrEmpty(multiplyStrengthBySkill))
{
strength *= Character.GetSkillLevel(multiplyStrengthBySkill);
}
character.Character.CharacterHealth.ApplyAffliction(null, afflictionPrefab.Instantiate(strength), allowStacking: !setValue);
}
}
}
}
@@ -4,12 +4,12 @@ namespace Barotrauma.Abilities
{
class CharacterAbilityGiveFlag : CharacterAbility
{
private AbilityFlags abilityFlag;
private readonly AbilityFlags abilityFlag;
// this and resistance giving should probably be moved directly to charactertalent attributes, as they don't need to interact with either ability group types
public CharacterAbilityGiveFlag(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
abilityFlag = ParseFlagType(abilityElement.GetAttributeString("flagtype", ""), CharacterTalent.DebugIdentifier);
abilityFlag = CharacterAbilityGroup.ParseFlagType(abilityElement.GetAttributeString("flagtype", ""), CharacterTalent.DebugIdentifier);
}
public override void InitializeAbility(bool addingFirstTime)
@@ -7,20 +7,20 @@ namespace Barotrauma.Abilities
public override bool AppliesEffectOnIntervalUpdate => true;
private readonly int amount;
private readonly StatTypes scalingStatType;
private readonly string scalingStatIdentifier;
public CharacterAbilityGiveMoney(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
amount = abilityElement.GetAttributeInt("amount", 0);
scalingStatType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("scalingstattype", "None"), CharacterTalent.DebugIdentifier);
scalingStatIdentifier = abilityElement.GetAttributeString("scalingstatidentifier", string.Empty);
}
private void ApplyEffectSpecific(Character targetCharacter)
{
float multiplier = 1f;
if (scalingStatType != StatTypes.None)
if (!string.IsNullOrEmpty(scalingStatIdentifier))
{
multiplier = 0 + Character.Info.GetSavedStatValue(scalingStatType);
multiplier = 0 + Character.Info.GetSavedStatValue(StatTypes.None, scalingStatIdentifier);
}
targetCharacter.GiveMoney((int)(multiplier * amount));
@@ -11,7 +11,6 @@ namespace Barotrauma.Abilities
private readonly float maxValue;
private readonly bool targetAllies;
private readonly bool removeOnDeath;
private readonly bool removeAfterRound;
private readonly bool giveOnAddingFirstTime;
private readonly bool setValue;
@@ -22,12 +21,12 @@ namespace Barotrauma.Abilities
public CharacterAbilityGivePermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statIdentifier = abilityElement.GetAttributeString("statidentifier", "").ToLowerInvariant();
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
string statTypeName = abilityElement.GetAttributeString("stattype", string.Empty);
statType = string.IsNullOrEmpty(statTypeName) ? StatTypes.None : CharacterAbilityGroup.ParseStatType(statTypeName, CharacterTalent.DebugIdentifier);
value = abilityElement.GetAttributeFloat("value", 0f);
maxValue = abilityElement.GetAttributeFloat("maxvalue", float.MaxValue);
targetAllies = abilityElement.GetAttributeBool("targetallies", false);
removeOnDeath = abilityElement.GetAttributeBool("removeondeath", true);
removeAfterRound = abilityElement.GetAttributeBool("removeafterround", false);
giveOnAddingFirstTime = abilityElement.GetAttributeBool("giveonaddingfirsttime", characterAbilityGroup.AbilityEffectType == AbilityEffectType.None);
setValue = abilityElement.GetAttributeBool("setvalue", false);
}
@@ -54,11 +53,11 @@ namespace Barotrauma.Abilities
{
if (targetAllies)
{
Character.GetFriendlyCrew(Character).ForEach(c => c?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, removeAfterRound, maxValue, setValue));
Character.GetFriendlyCrew(Character).ForEach(c => c?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, maxValue: maxValue, setValue: setValue));
}
else
{
Character?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, removeAfterRound, maxValue, setValue);
Character?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, maxValue: maxValue, setValue: setValue);
}
}
}
@@ -5,13 +5,13 @@ namespace Barotrauma.Abilities
{
class CharacterAbilityModifyFlag : CharacterAbility
{
private AbilityFlags abilityFlag;
private readonly AbilityFlags abilityFlag;
private bool lastState;
public CharacterAbilityModifyFlag(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
abilityFlag = ParseFlagType(abilityElement.GetAttributeString("flagtype", ""), CharacterTalent.DebugIdentifier);
abilityFlag = CharacterAbilityGroup.ParseFlagType(abilityElement.GetAttributeString("flagtype", ""), CharacterTalent.DebugIdentifier);
}
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
@@ -208,5 +208,15 @@ namespace Barotrauma.Abilities
return afflictions;
}
public static AbilityFlags ParseFlagType(string flagTypeString, string debugIdentifier)
{
AbilityFlags flagType = AbilityFlags.None;
if (!Enum.TryParse(flagTypeString, true, out flagType))
{
DebugConsole.ThrowError("Invalid flag type type \"" + flagTypeString + "\" in CharacterTalent (" + debugIdentifier + ")");
}
return flagType;
}
}
}
@@ -30,7 +30,7 @@ namespace Barotrauma
{
ConfigElement = element;
string jobIdentifier = element.GetAttributeString("jobidentifier", "");
string jobIdentifier = element.GetAttributeString("jobidentifier", "").ToLowerInvariant();
if (string.IsNullOrEmpty(jobIdentifier))
{