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