Unstable 0.1500.2.0 (Rokvach's dog edition)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -95,14 +96,26 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
protected bool HasValidPath(bool requireNonDirty = false) =>
|
||||
steeringManager is IndoorsSteeringManager pathSteering && pathSteering.CurrentPath != null && !pathSteering.CurrentPath.Finished && !pathSteering.CurrentPath.Unreachable && (!requireNonDirty || !pathSteering.IsPathDirty);
|
||||
public bool HasValidPath(bool requireNonDirty = false, bool requireUnfinished = true) =>
|
||||
steeringManager is IndoorsSteeringManager pathSteering &&
|
||||
pathSteering.CurrentPath != null &&
|
||||
(!requireUnfinished || !pathSteering.CurrentPath.Finished) &&
|
||||
!pathSteering.CurrentPath.Unreachable &&
|
||||
(!requireNonDirty || !pathSteering.IsPathDirty);
|
||||
|
||||
protected readonly float colliderWidth;
|
||||
protected readonly float colliderLength;
|
||||
protected readonly float avoidLookAheadDistance;
|
||||
|
||||
public AIController (Character c)
|
||||
{
|
||||
Character = c;
|
||||
hullVisibilityTimer = Rand.Range(0f, hullVisibilityTimer);
|
||||
Enabled = true;
|
||||
var size = Character.AnimController.Collider.GetSize();
|
||||
colliderWidth = size.X;
|
||||
colliderLength = size.Y;
|
||||
avoidLookAheadDistance = Math.Max(Math.Max(colliderWidth, colliderLength) * 3, 1.5f);
|
||||
}
|
||||
|
||||
public virtual void OnAttacked(Character attacker, AttackResult attackResult) { }
|
||||
@@ -327,6 +340,119 @@ namespace Barotrauma
|
||||
unequippedItems.Clear();
|
||||
}
|
||||
|
||||
#region Escape
|
||||
public abstract void Escape(float deltaTime);
|
||||
|
||||
public Gap EscapeTarget { get; private set; }
|
||||
|
||||
private readonly float escapeTargetSeekInterval = 2;
|
||||
private float escapeTimer;
|
||||
protected bool allGapsSearched;
|
||||
protected readonly HashSet<Gap> unreachableGaps = new HashSet<Gap>();
|
||||
protected bool UpdateEscape(float deltaTime, bool canAttackDoors)
|
||||
{
|
||||
IndoorsSteeringManager pathSteering = SteeringManager as IndoorsSteeringManager;
|
||||
if (allGapsSearched)
|
||||
{
|
||||
escapeTimer -= deltaTime;
|
||||
if (escapeTimer <= 0)
|
||||
{
|
||||
allGapsSearched = false;
|
||||
}
|
||||
}
|
||||
if (Character.CurrentHull != null && pathSteering != null)
|
||||
{
|
||||
// Seek exit if inside
|
||||
if (!allGapsSearched)
|
||||
{
|
||||
float closestDistance = 0;
|
||||
foreach (Gap gap in Gap.GapList)
|
||||
{
|
||||
if (gap == null || gap.Removed) { continue; }
|
||||
if (EscapeTarget == gap) { continue; }
|
||||
if (unreachableGaps.Contains(gap)) { continue; }
|
||||
if (gap.Submarine != Character.Submarine) { continue; }
|
||||
if (gap.IsRoomToRoom) { continue; }
|
||||
float multiplier = 1;
|
||||
var door = gap.ConnectedDoor;
|
||||
if (door != null)
|
||||
{
|
||||
if (!door.CanBeTraversed)
|
||||
{
|
||||
if (!door.HasAccess(Character))
|
||||
{
|
||||
if (!canAttackDoors) { continue; }
|
||||
// Treat doors that don't have access to like they were farther, because it will take time to break them.
|
||||
multiplier = 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (gap.Open < 1) { continue; }
|
||||
bool canGetThrough = ConvertUnits.ToDisplayUnits(colliderWidth) < gap.Size;
|
||||
if (!canGetThrough) { continue; }
|
||||
}
|
||||
if (gap.FlowTargetHull == Character.CurrentHull)
|
||||
{
|
||||
// If the gap is in the same room, it's close enough.
|
||||
EscapeTarget = gap;
|
||||
break;
|
||||
}
|
||||
float distance = Vector2.DistanceSquared(Character.WorldPosition, gap.WorldPosition) * multiplier;
|
||||
if (EscapeTarget == null || distance < closestDistance)
|
||||
{
|
||||
EscapeTarget = gap;
|
||||
closestDistance = distance;
|
||||
}
|
||||
}
|
||||
allGapsSearched = true;
|
||||
escapeTimer = escapeTargetSeekInterval;
|
||||
}
|
||||
else if (EscapeTarget != null && EscapeTarget.FlowTargetHull != Character.CurrentHull)
|
||||
{
|
||||
if (pathSteering.CurrentPath != null && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
|
||||
{
|
||||
unreachableGaps.Add(EscapeTarget);
|
||||
EscapeTarget = null;
|
||||
allGapsSearched = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (EscapeTarget != null)
|
||||
{
|
||||
SteeringManager.SteeringSeek(EscapeTarget.SimPosition, 10);
|
||||
float sqrDist = Vector2.DistanceSquared(Character.SimPosition, EscapeTarget.SimPosition);
|
||||
if (sqrDist < 0.5f || Character.CurrentHull == null || HasValidPath(requireNonDirty: true, requireUnfinished: false) && pathSteering.CurrentPath.Finished)
|
||||
{
|
||||
// Very close to the target, outside, or at the end of the path -> just steer towards it manually without using the path
|
||||
SteeringManager.Reset();
|
||||
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(EscapeTarget.WorldPosition - Character.WorldPosition));
|
||||
if (sqrDist < 4)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Can't find the target
|
||||
EscapeTarget = null;
|
||||
allGapsSearched = false;
|
||||
unreachableGaps.Clear();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ResetEscape()
|
||||
{
|
||||
EscapeTarget = null;
|
||||
allGapsSearched = false;
|
||||
unreachableGaps.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected virtual void OnStateChanged(AIState from, AIState to) { }
|
||||
protected virtual void OnTargetChanged(AITarget previousTarget, AITarget newTarget) { }
|
||||
|
||||
|
||||
@@ -60,8 +60,6 @@ namespace Barotrauma
|
||||
// Min priority for the memorized targets. The actual value fades gradually, unless kept fresh by selecting the target.
|
||||
private const float minPriority = 10;
|
||||
|
||||
private readonly float avoidLookAheadDistance;
|
||||
|
||||
private IndoorsSteeringManager PathSteering => insideSteering as IndoorsSteeringManager;
|
||||
private SteeringManager outsideSteering, insideSteering;
|
||||
|
||||
@@ -113,20 +111,21 @@ namespace Barotrauma
|
||||
lastAttackUpdateTime = Timing.TotalTime;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public AITargetMemory SelectedTargetMemory => selectedTargetMemory;
|
||||
private AITargetMemory selectedTargetMemory;
|
||||
private float targetValue;
|
||||
private CharacterParams.TargetParams selectedTargetingParams;
|
||||
|
||||
private Dictionary<AITarget, AITargetMemory> targetMemories;
|
||||
|
||||
private readonly float colliderWidth;
|
||||
private readonly float colliderLength;
|
||||
private readonly int requiredHoleCount;
|
||||
private bool canAttackWalls;
|
||||
public bool CanAttackDoors => canAttackDoors;
|
||||
private bool canAttackDoors;
|
||||
private bool canAttackCharacters;
|
||||
|
||||
public float PriorityFearIncrement => priorityFearIncreasement;
|
||||
private readonly float priorityFearIncreasement = 2;
|
||||
private readonly float memoryFadeTime = 0.5f;
|
||||
|
||||
@@ -299,12 +298,8 @@ namespace Barotrauma
|
||||
steeringManager = outsideSteering;
|
||||
State = AIState.Idle;
|
||||
|
||||
var size = Character.AnimController.Collider.GetSize();
|
||||
colliderWidth = size.X;
|
||||
colliderLength = size.Y;
|
||||
requiredHoleCount = (int)Math.Ceiling(ConvertUnits.ToDisplayUnits(colliderWidth) / Structure.WallSectionSize);
|
||||
|
||||
avoidLookAheadDistance = Math.Max(Math.Max(colliderWidth, colliderLength) * 3, 1.5f);
|
||||
myBodies = Character.AnimController.Limbs.Select(l => l.body.FarseerBody);
|
||||
}
|
||||
|
||||
@@ -440,9 +435,9 @@ namespace Barotrauma
|
||||
if (Math.Abs(Character.AnimController.movement.X) > 0.1f && !Character.AnimController.InWater &&
|
||||
(GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer || Character.Controlled == Character))
|
||||
{
|
||||
if (SelectedAiTarget?.Entity != null || escapeTarget != null)
|
||||
if (SelectedAiTarget?.Entity != null || EscapeTarget != null)
|
||||
{
|
||||
Entity t = SelectedAiTarget?.Entity ?? escapeTarget;
|
||||
Entity t = SelectedAiTarget?.Entity ?? EscapeTarget;
|
||||
float referencePos = Vector2.DistanceSquared(Character.WorldPosition, t.WorldPosition) > 100 * 100 && HasValidPath(true) ? PathSteering.CurrentPath.CurrentNode.WorldPosition.X : t.WorldPosition.X;
|
||||
Character.AnimController.TargetDir = Character.WorldPosition.X < referencePos ? Direction.Right : Direction.Left;
|
||||
}
|
||||
@@ -585,7 +580,7 @@ namespace Barotrauma
|
||||
case AIState.Escape:
|
||||
case AIState.Flee:
|
||||
run = true;
|
||||
UpdateEscape(deltaTime);
|
||||
Escape(deltaTime);
|
||||
break;
|
||||
case AIState.Avoid:
|
||||
case AIState.PassiveAggressive:
|
||||
@@ -602,7 +597,7 @@ namespace Barotrauma
|
||||
run = true;
|
||||
if (State == AIState.Avoid)
|
||||
{
|
||||
UpdateEscape(deltaTime);
|
||||
Escape(deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -827,142 +822,6 @@ namespace Barotrauma
|
||||
|
||||
#endregion
|
||||
|
||||
#region Escape
|
||||
private readonly float escapeTargetSeekInterval = 2;
|
||||
private float escapeTimer;
|
||||
private Gap escapeTarget;
|
||||
private bool allGapsSearched;
|
||||
private readonly HashSet<Gap> unreachableGaps = new HashSet<Gap>();
|
||||
private void UpdateEscape(float deltaTime)
|
||||
{
|
||||
if (SelectedAiTarget != null && (SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed))
|
||||
{
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
else if (selectedTargetMemory != null && SelectedAiTarget?.Entity is Character)
|
||||
{
|
||||
selectedTargetMemory.Priority += deltaTime * priorityFearIncreasement;
|
||||
}
|
||||
IndoorsSteeringManager pathSteering = SteeringManager as IndoorsSteeringManager;
|
||||
bool hasValidPath = pathSteering?.CurrentPath != null && !pathSteering.IsPathDirty && !pathSteering.CurrentPath.Unreachable;
|
||||
if (allGapsSearched)
|
||||
{
|
||||
escapeTimer -= deltaTime;
|
||||
if (escapeTimer <= 0)
|
||||
{
|
||||
allGapsSearched = false;
|
||||
}
|
||||
}
|
||||
if (Character.CurrentHull != null && pathSteering != null)
|
||||
{
|
||||
// Seek exit if inside
|
||||
if (!allGapsSearched)
|
||||
{
|
||||
float closestDistance = 0;
|
||||
foreach (Gap gap in Gap.GapList)
|
||||
{
|
||||
if (gap == null || gap.Removed) { continue; }
|
||||
if (escapeTarget == gap) { continue; }
|
||||
if (unreachableGaps.Contains(gap)) { continue; }
|
||||
if (gap.Submarine != Character.Submarine) { continue; }
|
||||
if (gap.IsRoomToRoom) { continue; }
|
||||
float multiplier = 1;
|
||||
var door = gap.ConnectedDoor;
|
||||
if (door != null)
|
||||
{
|
||||
if (!door.CanBeTraversed)
|
||||
{
|
||||
if (!door.HasAccess(Character))
|
||||
{
|
||||
if (!canAttackDoors) { continue; }
|
||||
// Treat doors that don't have access to like they were farther, because it will take time to break them.
|
||||
multiplier = 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (gap.Open < 1) { continue; }
|
||||
bool canGetThrough = ConvertUnits.ToDisplayUnits(colliderWidth) < gap.Size;
|
||||
if (!canGetThrough) { continue; }
|
||||
}
|
||||
if (gap.FlowTargetHull == Character.CurrentHull)
|
||||
{
|
||||
// If the gap is in the same room, it's close enough.
|
||||
escapeTarget = gap;
|
||||
break;
|
||||
}
|
||||
float distance = Vector2.DistanceSquared(Character.WorldPosition, gap.WorldPosition) * multiplier;
|
||||
if (escapeTarget == null || distance < closestDistance)
|
||||
{
|
||||
escapeTarget = gap;
|
||||
closestDistance = distance;
|
||||
}
|
||||
}
|
||||
allGapsSearched = true;
|
||||
escapeTimer = escapeTargetSeekInterval;
|
||||
}
|
||||
else if (escapeTarget != null && escapeTarget.FlowTargetHull != Character.CurrentHull)
|
||||
{
|
||||
if (pathSteering.CurrentPath != null && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
|
||||
{
|
||||
unreachableGaps.Add(escapeTarget);
|
||||
escapeTarget = null;
|
||||
allGapsSearched = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (escapeTarget != null && Character.CurrentHull != null && Vector2.DistanceSquared(Character.SimPosition, escapeTarget.SimPosition) > 0.5f)
|
||||
{
|
||||
if (hasValidPath && pathSteering.CurrentPath.Finished)
|
||||
{
|
||||
// Steer manually towards the gap
|
||||
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(escapeTarget.WorldPosition - Character.WorldPosition));
|
||||
}
|
||||
else if (SelectedAiTarget?.Entity is Character targetCharacter && targetCharacter.CurrentHull == Character.CurrentHull)
|
||||
{
|
||||
// Steer away from the target if in the same room
|
||||
Vector2 escapeDir = Vector2.Normalize(SelectedAiTarget != null ? WorldPosition - SelectedAiTarget.WorldPosition : Character.AnimController.TargetMovement);
|
||||
if (!MathUtils.IsValid(escapeDir)) { escapeDir = Vector2.UnitY; }
|
||||
SteeringManager.SteeringManual(deltaTime, escapeDir);
|
||||
return;
|
||||
}
|
||||
else if (pathSteering != null)
|
||||
{
|
||||
if (hasValidPath && canAttackDoors)
|
||||
{
|
||||
var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor;
|
||||
if (door != null && !door.CanBeTraversed && !door.HasAccess(Character))
|
||||
{
|
||||
if (SelectedAiTarget != door.Item.AiTarget || State != AIState.Attack)
|
||||
{
|
||||
SelectTarget(door.Item.AiTarget, selectedTargetMemory.Priority);
|
||||
State = AIState.Attack;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SteeringManager.SteeringSeek(escapeTarget.SimPosition, 10);
|
||||
}
|
||||
else
|
||||
{
|
||||
escapeTarget = null;
|
||||
allGapsSearched = false;
|
||||
Vector2 escapeDir = Vector2.Normalize(SelectedAiTarget != null ? WorldPosition - SelectedAiTarget.WorldPosition : Character.AnimController.TargetMovement);
|
||||
if (!MathUtils.IsValid(escapeDir)) escapeDir = Vector2.UnitY;
|
||||
SteeringManager.SteeringManual(deltaTime, escapeDir);
|
||||
if (Character.CurrentHull == null)
|
||||
{
|
||||
SteeringManager.SteeringWander();
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Attack
|
||||
|
||||
private Vector2 attackWorldPos;
|
||||
@@ -3129,11 +2988,9 @@ namespace Barotrauma
|
||||
{
|
||||
LatchOntoAI?.DeattachFromBody(reset: true);
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
escapeTarget = null;
|
||||
AttackingLimb = null;
|
||||
movementMargin = 0;
|
||||
allGapsSearched = false;
|
||||
unreachableGaps.Clear();
|
||||
ResetEscape();
|
||||
if (isStateChanged && to == AIState.Idle && from != to)
|
||||
{
|
||||
SetStateResetTimer();
|
||||
@@ -3283,6 +3140,71 @@ namespace Barotrauma
|
||||
|
||||
public bool CanPassThroughHole(Structure wall, int sectionIndex) => CanPassThroughHole(wall, sectionIndex, requiredHoleCount);
|
||||
|
||||
public override void Escape(float deltaTime)
|
||||
{
|
||||
if (SelectedAiTarget != null && (SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed))
|
||||
{
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
else if (SelectedTargetMemory is AITargetMemory targetMemory && SelectedAiTarget?.Entity is Character)
|
||||
{
|
||||
targetMemory.Priority += deltaTime * PriorityFearIncrement;
|
||||
}
|
||||
bool isSteeringThroughGap = UpdateEscape(deltaTime, canAttackDoors);
|
||||
if (!isSteeringThroughGap)
|
||||
{
|
||||
if (SelectedAiTarget?.Entity is Character targetCharacter && targetCharacter.CurrentHull == Character.CurrentHull)
|
||||
{
|
||||
SteerAwayFromTheEnemy();
|
||||
}
|
||||
else if (canAttackDoors && HasValidPath(requireNonDirty: true, requireUnfinished: true))
|
||||
{
|
||||
var door = PathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? PathSteering.CurrentPath.NextNode?.ConnectedDoor;
|
||||
if (door != null && !door.CanBeTraversed && !door.HasAccess(Character))
|
||||
{
|
||||
if (SelectedAiTarget != door.Item.AiTarget || State != AIState.Attack)
|
||||
{
|
||||
SelectTarget(door.Item.AiTarget, SelectedTargetMemory.Priority);
|
||||
State = AIState.Attack;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (EscapeTarget == null)
|
||||
{
|
||||
if (SelectedAiTarget?.Entity is Character)
|
||||
{
|
||||
SteerAwayFromTheEnemy();
|
||||
}
|
||||
else
|
||||
{
|
||||
SteeringManager.SteeringWander();
|
||||
if (Character.CurrentHull == null)
|
||||
{
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SteerAwayFromTheEnemy()
|
||||
{
|
||||
if (SelectedAiTarget == null) { return; }
|
||||
Vector2 escapeDir = Vector2.Normalize(WorldPosition - SelectedAiTarget.WorldPosition);
|
||||
if (Character.CurrentHull != null && !Character.AnimController.InWater)
|
||||
{
|
||||
// Inside
|
||||
escapeDir = new Vector2(Math.Sign(escapeDir.X), 0);
|
||||
}
|
||||
if (!MathUtils.IsValid(escapeDir))
|
||||
{
|
||||
escapeDir = Vector2.UnitY;
|
||||
}
|
||||
SteeringManager.Reset();
|
||||
SteeringManager.SteeringManual(deltaTime, escapeDir);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<Limb> targetLimbs = new List<Limb>();
|
||||
public Limb GetTargetLimb(Limb attackLimb, Character target, LimbType targetLimbType = LimbType.None)
|
||||
{
|
||||
|
||||
@@ -20,6 +20,11 @@ namespace Barotrauma
|
||||
private float reactTimer;
|
||||
private float unreachableClearTimer;
|
||||
private bool shouldCrouch;
|
||||
public bool IsInsideCave { get; private set; }
|
||||
/// <summary>
|
||||
/// Resets each frame
|
||||
/// </summary>
|
||||
public bool AutoFaceMovement = true;
|
||||
|
||||
const float reactionTime = 0.3f;
|
||||
const float crouchRaycastInterval = 1;
|
||||
@@ -52,7 +57,7 @@ namespace Barotrauma
|
||||
private readonly float steeringBufferIncreaseSpeed = 100;
|
||||
private float steeringBuffer;
|
||||
|
||||
private readonly float obstacleRaycastInterval = 1;
|
||||
private readonly float obstacleRaycastIntervalShort = 1, obstacleRaycastIntervalLong = 5;
|
||||
private float obstacleRaycastTimer;
|
||||
|
||||
private readonly float enemyCheckInterval = 0.2f;
|
||||
@@ -86,6 +91,8 @@ namespace Barotrauma
|
||||
|
||||
private readonly SteeringManager outsideSteering, insideSteering;
|
||||
|
||||
public bool UseIndoorSteeringOutside { get; set; } = false;
|
||||
|
||||
public IndoorsSteeringManager PathSteering => insideSteering as IndoorsSteeringManager;
|
||||
public HumanoidAnimController AnimController => Character.AnimController as HumanoidAnimController;
|
||||
|
||||
@@ -207,33 +214,78 @@ namespace Barotrauma
|
||||
IgnoredItems.Clear();
|
||||
}
|
||||
|
||||
bool IsCloseEnoughToTargetSub(float threshold) => SelectedAiTarget?.Entity?.Submarine is Submarine sub && sub != null && Vector2.DistanceSquared(Character.WorldPosition, sub.WorldPosition) < MathUtils.Pow(Math.Max(sub.Borders.Size.X, sub.Borders.Size.Y) / 2 + threshold, 2);
|
||||
bool IsCloseEnoughToTarget(float threshold, bool useTargetSub = true)
|
||||
{
|
||||
Entity target = SelectedAiTarget?.Entity;
|
||||
if (target == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (useTargetSub)
|
||||
{
|
||||
if (target.Submarine is Submarine sub)
|
||||
{
|
||||
target = sub;
|
||||
threshold += Math.Max(sub.Borders.Size.X, sub.Borders.Size.Y) / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return Vector2.DistanceSquared(Character.WorldPosition, target.WorldPosition) < MathUtils.Pow(threshold, 2);
|
||||
}
|
||||
|
||||
bool hasValidPath = HasValidPath();
|
||||
|
||||
if (Character.Submarine == null)
|
||||
{
|
||||
if (hasValidPath)
|
||||
// When the character is outside, far enough from the target, and the direct route is blocked,
|
||||
// use the indoor steering with the main and side path waypoints to help avoid getting stuck in level walls
|
||||
if (SelectedAiTarget?.Entity != null && !IsCloseEnoughToTarget(2000, useTargetSub: false))
|
||||
{
|
||||
obstacleRaycastTimer -= deltaTime;
|
||||
if (obstacleRaycastTimer <= 0)
|
||||
{
|
||||
obstacleRaycastTimer = obstacleRaycastInterval;
|
||||
// Swimming outside and using the path finder -> check that the path is not blocked with anything (the path finder doesn't know about other subs).
|
||||
foreach (var connectedSub in Submarine.MainSub.GetConnectedSubs())
|
||||
obstacleRaycastTimer = obstacleRaycastIntervalLong;
|
||||
Vector2 rayEnd = SelectedAiTarget.Entity.SimPosition;
|
||||
if (SelectedAiTarget.Entity.Submarine != null)
|
||||
{
|
||||
if (connectedSub == Submarine.MainSub) { continue; }
|
||||
Vector2 rayStart = SimPosition - connectedSub.SimPosition;
|
||||
Vector2 dir = PathSteering.CurrentPath.CurrentNode.WorldPosition - WorldPosition;
|
||||
Vector2 rayEnd = rayStart + dir.ClampLength(Character.AnimController.Collider.GetLocalFront().Length() * 5);
|
||||
if (Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true) != null)
|
||||
rayEnd += SelectedAiTarget.Entity.Submarine.SimPosition;
|
||||
}
|
||||
UseIndoorSteeringOutside = Submarine.PickBody(SimPosition, rayEnd, collisionCategory: Physics.CollisionLevel) != null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UseIndoorSteeringOutside = false;
|
||||
if (hasValidPath)
|
||||
{
|
||||
obstacleRaycastTimer -= deltaTime;
|
||||
if (obstacleRaycastTimer <= 0)
|
||||
{
|
||||
obstacleRaycastTimer = obstacleRaycastIntervalShort;
|
||||
// Swimming outside and using the path finder -> check that the path is not blocked with anything (the path finder doesn't know about other subs).
|
||||
foreach (var connectedSub in Submarine.MainSub.GetConnectedSubs())
|
||||
{
|
||||
PathSteering.CurrentPath.Unreachable = true;
|
||||
break;
|
||||
if (connectedSub == Submarine.MainSub) { continue; }
|
||||
Vector2 rayStart = SimPosition - connectedSub.SimPosition;
|
||||
Vector2 dir = PathSteering.CurrentPath.CurrentNode.WorldPosition - WorldPosition;
|
||||
Vector2 rayEnd = rayStart + dir.ClampLength(Character.AnimController.Collider.GetLocalFront().Length() * 5);
|
||||
if (Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true) != null)
|
||||
{
|
||||
PathSteering.CurrentPath.Unreachable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UseIndoorSteeringOutside = false;
|
||||
}
|
||||
|
||||
if (Character.Submarine == null || !IsOnFriendlyTeam(Character.TeamID, Character.Submarine.TeamID) && !Character.IsEscorted)
|
||||
{
|
||||
@@ -273,13 +325,31 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Character.Submarine != null || hasValidPath && IsCloseEnoughToTargetSub(maxSteeringBuffer) || IsCloseEnoughToTargetSub(steeringBuffer))
|
||||
|
||||
// Check whether the character is inside a cave
|
||||
if (IsInsideCave)
|
||||
{
|
||||
// If the character was inside a cave, require them to move a bit further from the area to set the field back to false
|
||||
// This is to avoid any twitchy behavior with the steering managers
|
||||
IsInsideCave = Character.CurrentHull == null && Level.Loaded?.Caves.FirstOrDefault(c =>
|
||||
{
|
||||
var area = c.Area;
|
||||
area.Inflate(new Vector2(100));
|
||||
return area.Contains(Character.WorldPosition);
|
||||
}) is Level.Cave;
|
||||
}
|
||||
else
|
||||
{
|
||||
IsInsideCave = Character.CurrentHull == null && Level.Loaded?.Caves.FirstOrDefault(c => c.Area.Contains(Character.WorldPosition)) is Level.Cave;
|
||||
}
|
||||
|
||||
if (UseIndoorSteeringOutside || IsInsideCave || Character.Submarine != null || hasValidPath && IsCloseEnoughToTarget(maxSteeringBuffer) || IsCloseEnoughToTarget(steeringBuffer))
|
||||
{
|
||||
if (steeringManager != insideSteering)
|
||||
{
|
||||
insideSteering.Reset();
|
||||
steeringManager = insideSteering;
|
||||
}
|
||||
steeringManager = insideSteering;
|
||||
steeringBuffer += steeringBufferIncreaseSpeed * deltaTime;
|
||||
}
|
||||
else
|
||||
@@ -287,8 +357,8 @@ namespace Barotrauma
|
||||
if (steeringManager != outsideSteering)
|
||||
{
|
||||
outsideSteering.Reset();
|
||||
steeringManager = outsideSteering;
|
||||
}
|
||||
steeringManager = outsideSteering;
|
||||
steeringBuffer = minSteeringBuffer;
|
||||
}
|
||||
steeringBuffer = Math.Clamp(steeringBuffer, minSteeringBuffer, maxSteeringBuffer);
|
||||
@@ -419,7 +489,7 @@ namespace Barotrauma
|
||||
Character.SelectedConstruction.SecondaryUse(deltaTime, Character);
|
||||
}
|
||||
}
|
||||
else if (Math.Abs(Character.AnimController.TargetMovement.X) > 0.1f && !Character.AnimController.InWater)
|
||||
else if (AutoFaceMovement && Math.Abs(Character.AnimController.TargetMovement.X) > 0.1f && !Character.AnimController.InWater)
|
||||
{
|
||||
newDir = Character.AnimController.TargetMovement.X > 0.0f ? Direction.Right : Direction.Left;
|
||||
}
|
||||
@@ -429,6 +499,7 @@ namespace Barotrauma
|
||||
flipTimer = FlipInterval;
|
||||
}
|
||||
}
|
||||
AutoFaceMovement = true;
|
||||
|
||||
MentalStateManager?.Update(deltaTime);
|
||||
ShipCommandManager?.Update(deltaTime);
|
||||
@@ -1240,10 +1311,7 @@ namespace Barotrauma
|
||||
{
|
||||
var objective = new AIObjectiveCombat(Character, target, mode, objectiveManager)
|
||||
{
|
||||
HoldPosition =
|
||||
Character.Info?.Job?.Prefab.Identifier == "watchman" ||
|
||||
Character.CurrentHull == null ||
|
||||
Character.IsOnPlayerTeam && !target.IsPlayer && ObjectiveManager.GetActiveObjective<AIObjectiveGoTo>()?.Target is Character followTarget && followTarget.IsPlayer,
|
||||
HoldPosition = Character.Info?.Job?.Prefab.Identifier == "watchman",
|
||||
AbortCondition = abortCondition,
|
||||
allowHoldFire = allowHoldFire,
|
||||
};
|
||||
@@ -1293,6 +1361,11 @@ namespace Barotrauma
|
||||
ObjectiveManager.WaitTimer = waitDuration;
|
||||
}
|
||||
|
||||
public override void Escape(float deltaTime)
|
||||
{
|
||||
UpdateEscape(deltaTime, canAttackDoors: false);
|
||||
}
|
||||
|
||||
private void CheckCrouching(float deltaTime)
|
||||
{
|
||||
crouchRaycastTimer -= deltaTime;
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace Barotrauma
|
||||
|
||||
public IndoorsSteeringManager(ISteerable host, bool canOpenDoors, bool canBreakDoors) : base(host)
|
||||
{
|
||||
pathFinder = new PathFinder(WayPoint.WayPointList.FindAll(wp => wp.SpawnType == SpawnType.Path), indoorsSteering: true);
|
||||
pathFinder = new PathFinder(WayPoint.WayPointList.FindAll(wp => wp.SpawnType == SpawnType.Path), true);
|
||||
pathFinder.GetNodePenalty = GetNodePenalty;
|
||||
|
||||
this.canOpenDoors = canOpenDoors;
|
||||
@@ -160,26 +160,34 @@ 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)
|
||||
{
|
||||
Vector2 targetDiff = target - currentTarget;
|
||||
if (currentPath != null && currentPath.Nodes.Any() && character.Submarine != null)
|
||||
bool needsNewPath = currentPath == null || currentPath.Unreachable;
|
||||
if (!needsNewPath && character.Submarine != null && character.Params.PathFinderPriority > 0.5f)
|
||||
{
|
||||
//target in a different sub than where the character is now
|
||||
//take that into account when calculating if the target has moved
|
||||
Submarine currentPathSub = currentPath?.CurrentNode?.Submarine;
|
||||
if (currentPathSub == character.Submarine) { currentPathSub = currentPath?.Nodes.LastOrDefault()?.Submarine; }
|
||||
if (currentPathSub != character.Submarine && targetDiff.LengthSquared() > 1 && currentPathSub != null)
|
||||
Vector2 targetDiff = target - currentTarget;
|
||||
if (currentPath != null && currentPath.Nodes.Any() && character.Submarine != null)
|
||||
{
|
||||
Vector2 subDiff = character.Submarine.SimPosition - currentPathSub.SimPosition;
|
||||
targetDiff += subDiff;
|
||||
//target in a different sub than where the character is now
|
||||
//take that into account when calculating if the target has moved
|
||||
Submarine currentPathSub = currentPath?.CurrentNode?.Submarine;
|
||||
if (currentPathSub == character.Submarine) { currentPathSub = currentPath?.Nodes.LastOrDefault()?.Submarine; }
|
||||
if (currentPathSub != character.Submarine && targetDiff.LengthSquared() > 1 && currentPathSub != null)
|
||||
{
|
||||
Vector2 subDiff = character.Submarine.SimPosition - currentPathSub.SimPosition;
|
||||
targetDiff += subDiff;
|
||||
}
|
||||
}
|
||||
if (targetDiff.LengthSquared() > 1)
|
||||
{
|
||||
needsNewPath = true;
|
||||
}
|
||||
}
|
||||
bool needsNewPath = character.Params.PathFinderPriority > 0.5f && (currentPath == null || currentPath.Unreachable || targetDiff.LengthSquared() > 1);
|
||||
//find a new path if one hasn't been found yet or the target is different from the current target
|
||||
if (needsNewPath || findPathTimer < -1.0f)
|
||||
{
|
||||
IsPathDirty = true;
|
||||
if (findPathTimer < 0)
|
||||
{
|
||||
SkipCurrentPathNodes();
|
||||
currentTarget = target;
|
||||
Vector2 currentPos = host.SimPosition;
|
||||
if (character != null && character.Submarine == null)
|
||||
@@ -193,7 +201,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);
|
||||
bool useNewPath = needsNewPath || currentPath == null || currentPath.CurrentNode == null || findPathTimer < -1 && Math.Abs(character.AnimController.TargetMovement.X) <= 0;
|
||||
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)
|
||||
{
|
||||
// Check if the new path is the same as the old, in which case we just ignore it and continue using the old path (or the progress would reset).
|
||||
@@ -206,7 +214,7 @@ namespace Barotrauma
|
||||
// Use the new path if it has significantly lower cost (don't change the path if it has marginally smaller cost. This reduces navigating backwards due to new path that is calculated from the node just behind us).
|
||||
float t = (float)currentPath.CurrentIndex / (currentPath.Nodes.Count - 1);
|
||||
useNewPath = newPath.Cost < currentPath.Cost * MathHelper.Lerp(0.95f, 0, t);
|
||||
if (!useNewPath)
|
||||
if (!useNewPath && character.Submarine != null)
|
||||
{
|
||||
// It's possible that the current path was calculated from a start point that is no longer valid.
|
||||
// Therefore, let's accept also paths with a greater cost than the current, if the current node is much farther than the new start node.
|
||||
@@ -239,6 +247,32 @@ namespace Barotrauma
|
||||
findPathTimer = priority * Rand.Range(1.0f, 1.2f);
|
||||
IsPathDirty = false;
|
||||
return DiffToCurrentNode();
|
||||
|
||||
void SkipCurrentPathNodes()
|
||||
{
|
||||
if (!character.AnimController.InWater || character.Submarine != null) { return; }
|
||||
if (CurrentPath == null || CurrentPath.Unreachable || CurrentPath.Finished) { return; }
|
||||
if (CurrentPath.CurrentIndex < 0 || CurrentPath.CurrentIndex >= CurrentPath.Nodes.Count - 1) { return; }
|
||||
// Check if we could skip ahead to NextNode when the character is swimming and using waypoints outside.
|
||||
// Do this to optimize the old path before creating and evaluating a new path.
|
||||
// In general, this is to avoid behavior where:
|
||||
// a) the character goes back to first reach CurrentNode when the second node would be closer; or
|
||||
// b) the character moves along the path when they could cut through open space to reduce the total distance.
|
||||
float pathDistance = Vector2.Distance(character.WorldPosition, CurrentPath.CurrentNode.WorldPosition);
|
||||
pathDistance += CurrentPath.GetLength(startIndex: CurrentPath.CurrentIndex);
|
||||
for (int i = CurrentPath.Nodes.Count - 1; i > CurrentPath.CurrentIndex + 1; i--)
|
||||
{
|
||||
var waypoint = CurrentPath.Nodes[i];
|
||||
float directDistance = Vector2.DistanceSquared(character.WorldPosition, waypoint.WorldPosition);
|
||||
if (directDistance > (pathDistance * pathDistance) || Submarine.PickBody(host.SimPosition, waypoint.SimPosition, collisionCategory: Physics.CollisionLevel) != null)
|
||||
{
|
||||
pathDistance -= CurrentPath.GetLength(startIndex: i - 1, endIndex: i);
|
||||
continue;
|
||||
}
|
||||
CurrentPath.SkipToNode(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,18 +312,15 @@ namespace Barotrauma
|
||||
CheckDoorsInPath();
|
||||
}
|
||||
Vector2 pos = host.SimPosition;
|
||||
if (character != null && currentPath.CurrentNode != null)
|
||||
if (character != null && CurrentPath.CurrentNode?.Submarine != null)
|
||||
{
|
||||
if (CurrentPath.CurrentNode.Submarine != null)
|
||||
if (character.Submarine == null)
|
||||
{
|
||||
if (character.Submarine == null)
|
||||
{
|
||||
pos -= CurrentPath.CurrentNode.Submarine.SimPosition;
|
||||
}
|
||||
else if (character.Submarine != currentPath.CurrentNode.Submarine)
|
||||
{
|
||||
pos -= ConvertUnits.ToSimUnits(currentPath.CurrentNode.Submarine.Position - character.Submarine.Position);
|
||||
}
|
||||
pos -= CurrentPath.CurrentNode.Submarine.SimPosition;
|
||||
}
|
||||
else if (character.Submarine != currentPath.CurrentNode.Submarine)
|
||||
{
|
||||
pos -= ConvertUnits.ToSimUnits(currentPath.CurrentNode.Submarine.Position - character.Submarine.Position);
|
||||
}
|
||||
}
|
||||
bool isDiving = character.AnimController.InWater && character.AnimController.HeadInWater;
|
||||
|
||||
@@ -94,7 +94,7 @@ namespace Barotrauma
|
||||
if (_abandon)
|
||||
{
|
||||
#if DEBUG
|
||||
if (HumanAIController.debugai && objectiveManager.IsOrder(this) && !objectiveManager.IsCurrentOrder<AIObjectiveGoTo>())
|
||||
if (HumanAIController.debugai && objectiveManager.IsOrder(this) && !objectiveManager.IsCurrentOrder<AIObjectiveGoTo>() && !objectiveManager.IsCurrentOrder<AIObjectiveReturn>())
|
||||
{
|
||||
throw new Exception("Order abandoned!");
|
||||
}
|
||||
|
||||
+7
-6
@@ -83,12 +83,13 @@ namespace Barotrauma
|
||||
if (suitableContainer != null)
|
||||
{
|
||||
bool equip = item.GetComponent<Holdable>() != null ||
|
||||
item.AllowedSlots.None(s =>
|
||||
s == InvSlotType.Card ||
|
||||
s == InvSlotType.Head ||
|
||||
s == InvSlotType.Headset ||
|
||||
s == InvSlotType.InnerClothes ||
|
||||
s == InvSlotType.OuterClothes);
|
||||
item.AllowedSlots.Any(s => s != InvSlotType.Any) &&
|
||||
item.AllowedSlots.None(s =>
|
||||
s == InvSlotType.Card ||
|
||||
s == InvSlotType.Head ||
|
||||
s == InvSlotType.Headset ||
|
||||
s == InvSlotType.InnerClothes ||
|
||||
s == InvSlotType.OuterClothes);
|
||||
|
||||
TryAddSubObjective(ref decontainObjective, () => new AIObjectiveDecontainItem(character, item, objectiveManager, targetContainer: suitableContainer.GetComponent<ItemContainer>())
|
||||
{
|
||||
|
||||
+56
-2
@@ -252,9 +252,40 @@ namespace Barotrauma
|
||||
{
|
||||
case CombatMode.Offensive:
|
||||
case CombatMode.Arrest:
|
||||
Engage();
|
||||
Engage(deltaTime);
|
||||
break;
|
||||
case CombatMode.Defensive:
|
||||
if (character.IsOnPlayerTeam && !Enemy.IsPlayer && objectiveManager.IsCurrentOrder<AIObjectiveGoTo>())
|
||||
{
|
||||
if ((character.CurrentHull == null || character.CurrentHull == Enemy.CurrentHull) && sqrDistance < 200 * 200)
|
||||
{
|
||||
Engage(deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Keep following the goto target
|
||||
var gotoObjective = objectiveManager.GetOrder<AIObjectiveGoTo>();
|
||||
if (gotoObjective != null)
|
||||
{
|
||||
gotoObjective.ForceAct(deltaTime);
|
||||
if (!character.AnimController.InWater)
|
||||
{
|
||||
HumanAIController.FaceTarget(Enemy);
|
||||
ForceWalk = true;
|
||||
HumanAIController.AutoFaceMovement = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Retreat(deltaTime);
|
||||
}
|
||||
break;
|
||||
case CombatMode.Retreat:
|
||||
Retreat(deltaTime);
|
||||
break;
|
||||
@@ -671,6 +702,14 @@ namespace Barotrauma
|
||||
{
|
||||
RemoveSubObjective(ref retreatObjective);
|
||||
}
|
||||
if (character.Submarine == null && sqrDistance < MathUtils.Pow2(maxDistance))
|
||||
{
|
||||
// Swim away
|
||||
SteeringManager.Reset();
|
||||
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.WorldPosition - Enemy.WorldPosition));
|
||||
SteeringManager.SteeringAvoid(deltaTime, 5, weight: 2);
|
||||
return;
|
||||
}
|
||||
if (retreatTarget == null || (retreatObjective != null && !retreatObjective.CanBeCompleted))
|
||||
{
|
||||
if (findHullTimer > 0)
|
||||
@@ -704,7 +743,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void Engage()
|
||||
private void Engage(float deltaTime)
|
||||
{
|
||||
if (WeaponComponent == null)
|
||||
{
|
||||
@@ -722,6 +761,21 @@ namespace Barotrauma
|
||||
RemoveSubObjective(ref retreatObjective);
|
||||
RemoveSubObjective(ref seekAmmunitionObjective);
|
||||
RemoveSubObjective(ref seekWeaponObjective);
|
||||
if (character.Submarine == null && WeaponComponent is MeleeWeapon meleeWeapon)
|
||||
{
|
||||
if (sqrDistance > MathUtils.Pow2(meleeWeapon.Range))
|
||||
{
|
||||
// Swim towards the target
|
||||
SteeringManager.Reset();
|
||||
SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Enemy), weight: 10);
|
||||
SteeringManager.SteeringAvoid(deltaTime, 5, weight: 15);
|
||||
}
|
||||
else
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (followTargetObjective != null && followTargetObjective.Target != Enemy)
|
||||
{
|
||||
RemoveFollowTarget();
|
||||
|
||||
+21
-21
@@ -46,7 +46,10 @@ namespace Barotrauma
|
||||
}
|
||||
if (character.CurrentHull == null)
|
||||
{
|
||||
Priority = (objectiveManager.IsCurrentOrder<AIObjectiveGoTo>() || objectiveManager.HasActiveObjective<AIObjectiveCombat>()) && HumanAIController.HasDivingSuit(character) ? 0 : 100;
|
||||
Priority = (objectiveManager.IsCurrentOrder<AIObjectiveGoTo>() ||
|
||||
objectiveManager.IsCurrentOrder<AIObjectiveReturn>() ||
|
||||
objectiveManager.Objectives.Any(o => o.Priority > 0 && o is AIObjectiveCombat))
|
||||
&& HumanAIController.HasDivingSuit(character) ? 0 : 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -57,9 +60,11 @@ namespace Barotrauma
|
||||
{
|
||||
Priority = 100;
|
||||
}
|
||||
else if (objectiveManager.IsCurrentOrder<AIObjectiveGoTo>() && character.Submarine != null && !HumanAIController.IsOnFriendlyTeam(character.TeamID, character.Submarine.TeamID))
|
||||
else if ((objectiveManager.IsCurrentOrder<AIObjectiveGoTo>() || objectiveManager.IsCurrentOrder<AIObjectiveReturn>()) &&
|
||||
character.Submarine != null && !HumanAIController.IsOnFriendlyTeam(character.TeamID, character.Submarine.TeamID))
|
||||
{
|
||||
// Ordered to follow/hold position inside a hostile sub -> ignore find safety unless we need to find a diving gear
|
||||
// Ordered to follow, hold position, or return back to main sub inside a hostile sub
|
||||
// -> ignore find safety unless we need to find a diving gear
|
||||
Priority = 0;
|
||||
}
|
||||
Priority = MathHelper.Clamp(Priority, 0, 100);
|
||||
@@ -298,6 +303,7 @@ namespace Barotrauma
|
||||
|
||||
Hull bestHull = null;
|
||||
float bestValue = 0;
|
||||
bool bestIsAirlock = false;
|
||||
foreach (Hull hull in Hull.hullList.OrderByDescending(h => EstimateHullSuitability(h)))
|
||||
{
|
||||
if (hull.Submarine == null) { continue; }
|
||||
@@ -306,9 +312,10 @@ namespace Barotrauma
|
||||
if (ignoredHulls != null && ignoredHulls.Contains(hull)) { continue; }
|
||||
if (HumanAIController.UnreachableHulls.Contains(hull)) { continue; }
|
||||
float hullSafety = 0;
|
||||
if (character.CurrentHull != null && character.Submarine != null)
|
||||
bool hullIsAirlock = false;
|
||||
bool isCharacterInside = character.CurrentHull != null && character.Submarine != null;
|
||||
if (isCharacterInside)
|
||||
{
|
||||
// Inside
|
||||
if (!character.Submarine.IsConnectedTo(hull.Submarine)) { continue; }
|
||||
hullSafety = HumanAIController.GetHullSafety(hull, hull.GetConnectedHulls(true, 1), character);
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - hull.WorldPosition.Y);
|
||||
@@ -343,24 +350,16 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
// Outside
|
||||
if (hull.RoomName != null && hull.RoomName.Contains("airlock", StringComparison.OrdinalIgnoreCase))
|
||||
// TODO: could also target gaps that get us inside?
|
||||
if (hull.IsTaggedAirlock())
|
||||
{
|
||||
hullSafety = 100;
|
||||
hullIsAirlock = true;
|
||||
}
|
||||
else if(!bestIsAirlock && hull.LeadsOutside(character))
|
||||
{
|
||||
hullSafety = 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: could also target gaps that get us inside?
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.CurrentHull != hull && item.HasTag("airlock"))
|
||||
{
|
||||
hullSafety = 100;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: could we get a closest door to the outside and target the flowing hull if no airlock is found?
|
||||
// Huge preference for closer targets
|
||||
float distance = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition);
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, MathUtils.Pow(100000, 2), distance));
|
||||
@@ -372,10 +371,11 @@ namespace Barotrauma
|
||||
hullSafety /= 10;
|
||||
}
|
||||
}
|
||||
if (hullSafety > bestValue)
|
||||
if (hullSafety > bestValue || (!isCharacterInside && hullIsAirlock && !bestIsAirlock))
|
||||
{
|
||||
bestHull = hull;
|
||||
bestValue = hullSafety;
|
||||
bestIsAirlock = hullIsAirlock;
|
||||
}
|
||||
}
|
||||
return bestHull;
|
||||
|
||||
+13
-2
@@ -159,6 +159,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void ForceAct(float deltaTime) => Act(deltaTime);
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (followControlledCharacter)
|
||||
@@ -240,7 +242,7 @@ namespace Barotrauma
|
||||
if (getDivingGearIfNeeded && !character.LockHands)
|
||||
{
|
||||
Character followTarget = Target as Character;
|
||||
bool needsDivingSuit = targetIsOutside;
|
||||
bool needsDivingSuit = !isInside || targetIsOutside;
|
||||
bool needsDivingGear = needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit);
|
||||
if (mimic)
|
||||
{
|
||||
@@ -444,13 +446,22 @@ namespace Barotrauma
|
||||
}
|
||||
if (SteeringManager == PathSteering)
|
||||
{
|
||||
Vector2 targetPos = character.GetRelativeSimPosition(Target);
|
||||
Func<PathNode, bool> nodeFilter = null;
|
||||
if (isInside && !AllowGoingOutside)
|
||||
{
|
||||
nodeFilter = n => n.Waypoint.CurrentHull != null;
|
||||
}
|
||||
else if (!isInside && HumanAIController.UseIndoorSteeringOutside)
|
||||
{
|
||||
if (character.Submarine == null && Target.Submarine != null)
|
||||
{
|
||||
targetPos += Target.Submarine.SimPosition;
|
||||
}
|
||||
nodeFilter = n => n.Waypoint.Tunnel != null;
|
||||
}
|
||||
|
||||
PathSteering.SteeringSeek(character.GetRelativeSimPosition(Target), 1,
|
||||
PathSteering.SteeringSeek(targetPos, 1,
|
||||
startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (character.CurrentHull == null),
|
||||
endNodeFilter,
|
||||
nodeFilter,
|
||||
|
||||
+31
-7
@@ -233,11 +233,7 @@ namespace Barotrauma
|
||||
if (orderObjective == null) { return; }
|
||||
#if DEBUG
|
||||
// Note: don't automatically remove orders here. Removing orders needs to be done via dismissing.
|
||||
if (orderObjective.IsCompleted)
|
||||
{
|
||||
DebugConsole.NewMessage($"{character.Name}: ORDER {orderObjective.DebugTag} IS COMPLETED. CURRENTLY ALL ORDERS SHOULD BE LOOPING.", Color.Red);
|
||||
}
|
||||
else if (!orderObjective.CanBeCompleted)
|
||||
if (!orderObjective.CanBeCompleted)
|
||||
{
|
||||
DebugConsole.NewMessage($"{character.Name}: ORDER {orderObjective.DebugTag}, CANNOT BE COMPLETED.", Color.Red);
|
||||
}
|
||||
@@ -281,9 +277,9 @@ namespace Barotrauma
|
||||
ForcedOrder?.CalculatePriority();
|
||||
AIObjective orderWithHighestPriority = null;
|
||||
float highestPriority = 0;
|
||||
foreach (var currentOrder in CurrentOrders)
|
||||
for (int i = CurrentOrders.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var orderObjective = currentOrder.Objective;
|
||||
var orderObjective = CurrentOrders[i].Objective;
|
||||
if (orderObjective == null) { continue; }
|
||||
orderObjective.CalculatePriority();
|
||||
if (orderWithHighestPriority == null || orderObjective.Priority > highestPriority)
|
||||
@@ -467,6 +463,11 @@ namespace Barotrauma
|
||||
AllowGoingOutside = character.Submarine == null || (order.TargetSpatialEntity != null && character.Submarine != order.TargetSpatialEntity.Submarine)
|
||||
};
|
||||
break;
|
||||
case "return":
|
||||
newObjective = new AIObjectiveReturn(character, this, priorityModifier: priorityModifier);
|
||||
newObjective.Abandoned += () => DismissSelf(order, option);
|
||||
newObjective.Completed += () => DismissSelf(order, option);
|
||||
break;
|
||||
case "fixleaks":
|
||||
newObjective = new AIObjectiveFixLeaks(character, this, priorityModifier: priorityModifier, prioritizedHull: order.TargetEntity as Hull);
|
||||
break;
|
||||
@@ -586,6 +587,27 @@ namespace Barotrauma
|
||||
return newObjective;
|
||||
}
|
||||
|
||||
private void DismissSelf(Order order, string option)
|
||||
{
|
||||
var currentOrder = CurrentOrders.FirstOrDefault(oi => oi.MatchesOrder(order, option));
|
||||
if (currentOrder.Order == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Tried to self-dismiss an order, but no matching current order was found");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
#if CLIENT
|
||||
if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.IsSinglePlayer)
|
||||
{
|
||||
GameMain.GameSession?.CrewManager?.SetCharacterOrder(character, Order.GetPrefab("dismissed"), Order.GetDismissOrderOption(currentOrder), currentOrder.ManualPriority, character);
|
||||
}
|
||||
#else
|
||||
GameMain.Server?.SendOrderChatMessage(new OrderChatMessage(Order.GetPrefab("dismissed"), Order.GetDismissOrderOption(currentOrder), currentOrder.ManualPriority, currentOrder.Order?.TargetSpatialEntity, character, character));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
private bool IsAllowedToWait()
|
||||
{
|
||||
if (!character.IsOnPlayerTeam) { return false; }
|
||||
@@ -606,6 +628,8 @@ namespace Barotrauma
|
||||
public bool IsActiveObjective<T>() where T : AIObjective => GetActiveObjective() is T;
|
||||
|
||||
public AIObjective GetActiveObjective() => CurrentObjective?.GetActiveObjective();
|
||||
public T GetOrder<T>() where T : AIObjective => CurrentOrders.FirstOrDefault(o => o.Objective is T).Objective as T;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the last active objective of the specific type.
|
||||
/// </summary>
|
||||
|
||||
+243
@@ -0,0 +1,243 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveReturn : AIObjective
|
||||
{
|
||||
public override string Identifier { get; set; } = "return";
|
||||
private AIObjectiveGoTo moveInsideObjective, moveInCaveObjective, moveOutsideObjective;
|
||||
private bool usingEscapeBehavior;
|
||||
public Submarine ReturnTarget { get; }
|
||||
|
||||
public AIObjectiveReturn(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1.0f) : base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
ReturnTarget = GetReturnTarget(Submarine.MainSubs) ?? GetReturnTarget(Submarine.Loaded);
|
||||
if (ReturnTarget == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error with a Return objective: no suitable return target found");
|
||||
Abandon = true;
|
||||
}
|
||||
|
||||
Submarine GetReturnTarget(IEnumerable<Submarine> subs)
|
||||
{
|
||||
Submarine returnTarget = null;
|
||||
foreach (var sub in subs)
|
||||
{
|
||||
if (sub?.TeamID != character.TeamID) { continue; }
|
||||
returnTarget = sub;
|
||||
break;
|
||||
}
|
||||
return returnTarget;
|
||||
}
|
||||
}
|
||||
|
||||
protected override float GetPriority()
|
||||
{
|
||||
if (!Abandon && !IsCompleted && objectiveManager.IsOrder(this))
|
||||
{
|
||||
Priority = objectiveManager.GetOrderPriority(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Consider if this needs to be addressed
|
||||
Priority = 0;
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (ReturnTarget == null)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
bool shouldUseEscapeBehavior = false;
|
||||
if (character.CurrentHull != null)
|
||||
{
|
||||
if (character.Submarine == null || !character.Submarine.IsConnectedTo(ReturnTarget))
|
||||
{
|
||||
// Character is on another sub that is not connected to the target sub, use the escape behavior to get them out
|
||||
shouldUseEscapeBehavior = true;
|
||||
if (!usingEscapeBehavior)
|
||||
{
|
||||
HumanAIController.ResetEscape();
|
||||
}
|
||||
HumanAIController.Escape(deltaTime);
|
||||
if (HumanAIController.EscapeTarget == null || !HumanAIController.HasValidPath(requireNonDirty: true, requireUnfinished: false))
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
}
|
||||
else if (character.Submarine != ReturnTarget)
|
||||
{
|
||||
// Character is on another sub that is connected to the target sub, create a Go To objective to reach the target sub
|
||||
if (moveInsideObjective == null)
|
||||
{
|
||||
Hull targetHull = null;
|
||||
foreach (var d in ReturnTarget.ConnectedDockingPorts.Values)
|
||||
{
|
||||
if (!d.Docked) { continue; }
|
||||
if (d.DockingTarget == null) { continue; }
|
||||
if (d.DockingTarget.Item.Submarine != character.Submarine) { continue; }
|
||||
targetHull = d.Item.CurrentHull;
|
||||
break;
|
||||
}
|
||||
if (targetHull != null)
|
||||
{
|
||||
RemoveSubObjective(ref moveInCaveObjective);
|
||||
RemoveSubObjective(ref moveOutsideObjective);
|
||||
// TODO: Check 'repeat' and 'onAbandon' parameters
|
||||
TryAddSubObjective(ref moveInsideObjective,
|
||||
constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager),
|
||||
onCompleted: () => moveInsideObjective = null);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Error with a Return objective: no suitable target for 'moveInsideObjective'");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Character is on the target sub, the objective is completed
|
||||
IsCompleted = true;
|
||||
}
|
||||
}
|
||||
else if (moveInCaveObjective == null && moveOutsideObjective == null)
|
||||
{
|
||||
if (HumanAIController.IsInsideCave)
|
||||
{
|
||||
WayPoint closestOutsideWaypoint = null;
|
||||
float closestDistance = float.MaxValue;
|
||||
foreach (var w in WayPoint.WayPointList)
|
||||
{
|
||||
if (w.Tunnel == null) { continue; }
|
||||
if (w.Tunnel.Type == Level.TunnelType.Cave) { continue; }
|
||||
if (w.linkedTo.None(l => l is WayPoint linkedWaypoint && linkedWaypoint.Tunnel?.Type == Level.TunnelType.Cave)) { continue; }
|
||||
float distance = Vector2.DistanceSquared(character.WorldPosition, w.WorldPosition);
|
||||
if (closestOutsideWaypoint == null || distance < closestDistance)
|
||||
{
|
||||
closestOutsideWaypoint = w;
|
||||
closestDistance = distance;
|
||||
}
|
||||
}
|
||||
if (closestOutsideWaypoint != null)
|
||||
{
|
||||
RemoveSubObjective(ref moveInsideObjective);
|
||||
RemoveSubObjective(ref moveOutsideObjective);
|
||||
// TODO: Check 'repeat' and 'onAbandon' parameters
|
||||
TryAddSubObjective(ref moveInCaveObjective,
|
||||
constructor: () => new AIObjectiveGoTo(closestOutsideWaypoint, character, objectiveManager)
|
||||
{
|
||||
endNodeFilter = n => n.Waypoint == closestOutsideWaypoint
|
||||
},
|
||||
onCompleted: () => moveInCaveObjective = null);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Error with a Return objective: no suitable main or side path node target found for 'moveOutsideObjective'");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Hull targetHull = null;
|
||||
float targetDistanceSquared = float.MaxValue;
|
||||
bool targetIsAirlock = false;
|
||||
foreach (var hull in ReturnTarget.GetHulls(false))
|
||||
{
|
||||
bool hullIsAirlock = hull.IsTaggedAirlock();
|
||||
if(hullIsAirlock || (!targetIsAirlock && hull.LeadsOutside(character)))
|
||||
{
|
||||
float distanceSquared = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition);
|
||||
if (targetHull == null || distanceSquared < targetDistanceSquared)
|
||||
{
|
||||
targetHull = hull;
|
||||
targetDistanceSquared = distanceSquared;
|
||||
targetIsAirlock = hullIsAirlock;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetHull != null)
|
||||
{
|
||||
RemoveSubObjective(ref moveInsideObjective);
|
||||
RemoveSubObjective(ref moveInCaveObjective);
|
||||
// TODO: Check 'repeat' and 'onAbandon' parameters
|
||||
TryAddSubObjective(ref moveOutsideObjective,
|
||||
constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager),
|
||||
onCompleted: () => moveOutsideObjective = null);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Error with a Return objective: no suitable target for 'moveOutsideObjective'");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (HumanAIController.IsInsideCave)
|
||||
{
|
||||
if (moveOutsideObjective != null)
|
||||
{
|
||||
RemoveSubObjective(ref moveOutsideObjective);
|
||||
moveOutsideObjective = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (moveInCaveObjective != null)
|
||||
{
|
||||
RemoveSubObjective(ref moveInCaveObjective);
|
||||
moveInCaveObjective = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
usingEscapeBehavior = shouldUseEscapeBehavior;
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific()
|
||||
{
|
||||
if (IsCompleted)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (ReturnTarget == null)
|
||||
{
|
||||
Abandon = true;
|
||||
return false;
|
||||
}
|
||||
if (character.Submarine == ReturnTarget)
|
||||
{
|
||||
IsCompleted = true;
|
||||
}
|
||||
return IsCompleted;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
moveInsideObjective = null;
|
||||
moveInCaveObjective = null;
|
||||
moveOutsideObjective = null;
|
||||
usingEscapeBehavior = false;
|
||||
HumanAIController.ResetEscape();
|
||||
}
|
||||
|
||||
protected override void OnAbandon()
|
||||
{
|
||||
base.OnAbandon();
|
||||
SteeringManager.Reset();
|
||||
if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder == objectiveManager.CurrentObjective)
|
||||
{
|
||||
string msg = TextManager.Get("dialogcannotreturn", returnNull: true);
|
||||
if (msg != null)
|
||||
{
|
||||
character.Speak(msg, identifier: "dialogcannotreturn", minDurationBetweenSimilar: 5.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -23,6 +24,8 @@ namespace Barotrauma
|
||||
public readonly Vector2 Position;
|
||||
public readonly int WayPointID;
|
||||
|
||||
public bool blocked;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"PathNode {WayPointID}";
|
||||
@@ -86,21 +89,19 @@ namespace Barotrauma
|
||||
public GetNodePenaltyHandler GetNodePenalty;
|
||||
|
||||
private readonly List<PathNode> nodes;
|
||||
public readonly bool IndoorsSteering;
|
||||
private readonly bool isCharacter;
|
||||
|
||||
public bool InsideSubmarine { get; set; }
|
||||
public bool ApplyPenaltyToOutsideNodes { get; set; }
|
||||
|
||||
public PathFinder(List<WayPoint> wayPoints, bool indoorsSteering = false)
|
||||
public PathFinder(List<WayPoint> wayPoints, bool isCharacter)
|
||||
{
|
||||
nodes = PathNode.GenerateNodes(wayPoints.FindAll(w => w.Submarine != null == indoorsSteering), removeOrphans: true);
|
||||
|
||||
nodes = PathNode.GenerateNodes(wayPoints.FindAll(w => (w.Submarine != null == isCharacter) || (isCharacter && w.Tunnel != null)), removeOrphans: true);
|
||||
foreach (WayPoint wp in wayPoints)
|
||||
{
|
||||
wp.OnLinksChanged += WaypointLinksChanged;
|
||||
}
|
||||
|
||||
IndoorsSteering = indoorsSteering;
|
||||
this.isCharacter = isCharacter;
|
||||
}
|
||||
|
||||
void WaypointLinksChanged(WayPoint wp)
|
||||
@@ -145,6 +146,8 @@ namespace Barotrauma
|
||||
|
||||
public SteeringPath FindPath(Vector2 start, Vector2 end, Submarine hostSub = null, string errorMsgStr = null, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisibility = true)
|
||||
{
|
||||
UpdateBlockedNodes();
|
||||
|
||||
//sort nodes roughly according to distance
|
||||
sortedNodes.Clear();
|
||||
foreach (PathNode node in nodes)
|
||||
@@ -152,7 +155,9 @@ namespace Barotrauma
|
||||
node.TempPosition = node.Position;
|
||||
if (hostSub != null)
|
||||
{
|
||||
Vector2 diff = hostSub.SimPosition - node.Waypoint.Submarine.SimPosition;
|
||||
Vector2 diff = node.Waypoint.Submarine != null ?
|
||||
hostSub.SimPosition - node.Waypoint.Submarine.SimPosition :
|
||||
hostSub.SimPosition - node.Waypoint.SimPosition;
|
||||
node.TempPosition -= diff;
|
||||
}
|
||||
float xDiff = Math.Abs(start.X - node.TempPosition.X);
|
||||
@@ -174,29 +179,34 @@ namespace Barotrauma
|
||||
sortedNodes.Insert(i, node);
|
||||
}
|
||||
|
||||
bool IsWaypointVisible(PathNode node, Vector2 rayStart, bool checkVisibility = true)
|
||||
{
|
||||
//if searching for a path inside the sub, make sure the waypoint is visible
|
||||
if (checkVisibility && isCharacter)
|
||||
{
|
||||
if (node.Waypoint.isObstructed) { return false; }
|
||||
var body = Submarine.PickBody(rayStart, node.TempPosition,
|
||||
collisionCategory: Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs);
|
||||
if (body != null)
|
||||
{
|
||||
if (body.UserData is Structure s && !s.IsPlatform) { return false; }
|
||||
if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { return false; }
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//find the most suitable start node, starting from the ones that are the closest
|
||||
PathNode startNode = null;
|
||||
foreach (PathNode node in sortedNodes)
|
||||
{
|
||||
if (startNode == null || node.TempDistance < startNode.TempDistance)
|
||||
{
|
||||
if (node.blocked) { continue; }
|
||||
if (nodeFilter != null && !nodeFilter(node)) { continue; }
|
||||
if (startNodeFilter != null && !startNodeFilter(node)) { continue; }
|
||||
//if searching for a path inside the sub, make sure the waypoint is visible
|
||||
if (IndoorsSteering)
|
||||
{
|
||||
if (node.Waypoint.isObstructed) { continue; }
|
||||
|
||||
// Always check the visibility for the start node
|
||||
var body = Submarine.PickBody(
|
||||
start, node.TempPosition, null,
|
||||
Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs);
|
||||
if (body != null)
|
||||
{
|
||||
if (body.UserData is Structure && !((Structure)body.UserData).IsPlatform) { continue; }
|
||||
if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { continue; }
|
||||
}
|
||||
}
|
||||
// Always check the visibility for the start node
|
||||
if (!IsWaypointVisible(node, start)) { continue; }
|
||||
startNode = node;
|
||||
}
|
||||
}
|
||||
@@ -241,24 +251,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (endNode == null || node.TempDistance < endNode.TempDistance)
|
||||
{
|
||||
if (node.blocked) { continue; }
|
||||
if (nodeFilter != null && !nodeFilter(node)) { continue; }
|
||||
if (endNodeFilter != null && !endNodeFilter(node)) { continue; }
|
||||
if (IndoorsSteering)
|
||||
{
|
||||
if (node.Waypoint.isObstructed) { continue; }
|
||||
//if searching for a path inside the sub, make sure the waypoint is visible
|
||||
if (checkVisibility)
|
||||
{
|
||||
// Only check the visibility for the end node when allowed (fix leaks)
|
||||
var body = Submarine.PickBody(end, node.TempPosition, null,
|
||||
Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs);
|
||||
if (body != null)
|
||||
{
|
||||
if (body.UserData is Structure && !((Structure)body.UserData).IsPlatform) { continue; }
|
||||
if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { continue; }
|
||||
}
|
||||
}
|
||||
}
|
||||
// Only check the visibility for the end node when allowed (fix leaks)
|
||||
if (!IsWaypointVisible(node, end, checkVisibility: checkVisibility)) { continue; }
|
||||
endNode = node;
|
||||
}
|
||||
}
|
||||
@@ -330,7 +327,8 @@ namespace Barotrauma
|
||||
foreach (PathNode node in nodes)
|
||||
{
|
||||
if (node.state != 1) { continue; }
|
||||
if (IndoorsSteering && node.Waypoint.isObstructed) { continue; }
|
||||
if (isCharacter && node.Waypoint.isObstructed) { continue; }
|
||||
if (node.blocked) { continue; }
|
||||
if (filter != null && !filter(node)) { continue; }
|
||||
if (node.F < dist)
|
||||
{
|
||||
@@ -438,6 +436,25 @@ namespace Barotrauma
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private void UpdateBlockedNodes()
|
||||
{
|
||||
if (!isCharacter) { return; }
|
||||
foreach (var n in nodes)
|
||||
{
|
||||
n.blocked = false;
|
||||
if (n.Waypoint.Submarine != null) { continue; }
|
||||
if (n.Waypoint.Tunnel?.Type != Level.TunnelType.Cave) { continue; }
|
||||
foreach (var w in Level.Loaded.ExtraWalls)
|
||||
{
|
||||
if (!(w is DestructibleLevelWall d)) { continue; }
|
||||
if (d.Destroyed) { continue; }
|
||||
if (!d.IsPointInside(n.Waypoint.Position)) { continue; }
|
||||
n.blocked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -24,16 +25,47 @@ namespace Barotrauma
|
||||
if (Unreachable) { return float.PositiveInfinity; }
|
||||
if (!totalLength.HasValue)
|
||||
{
|
||||
totalLength = 0.0f;
|
||||
for (int i = 0; i < nodes.Count - 1; i++)
|
||||
{
|
||||
totalLength += Vector2.Distance(nodes[i].WorldPosition, nodes[i + 1].WorldPosition);
|
||||
}
|
||||
CalculateTotalLength();
|
||||
}
|
||||
return totalLength.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public float GetLength(int? startIndex = null, int? endIndex = null)
|
||||
{
|
||||
if (Unreachable) { return float.PositiveInfinity; }
|
||||
startIndex ??= 0;
|
||||
endIndex ??= Nodes.Count - 1;
|
||||
if (startIndex == 0 && endIndex == Nodes.Count - 1)
|
||||
{
|
||||
return TotalLength;
|
||||
}
|
||||
if (!totalLength.HasValue)
|
||||
{
|
||||
CalculateTotalLength();
|
||||
}
|
||||
float length = 0.0f;
|
||||
for (int i = startIndex.Value; i < endIndex.Value; i++)
|
||||
{
|
||||
length += nodeDistances[i];
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
private void CalculateTotalLength()
|
||||
{
|
||||
totalLength = 0.0f;
|
||||
nodeDistances.Clear();
|
||||
for (int i = 0; i < nodes.Count - 1; i++)
|
||||
{
|
||||
float distance = Vector2.Distance(nodes[i].WorldPosition, nodes[i + 1].WorldPosition);
|
||||
totalLength += distance;
|
||||
nodeDistances.Add(distance);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<float> nodeDistances = new List<float>();
|
||||
|
||||
public SteeringPath(bool unreachable = false)
|
||||
{
|
||||
nodes = new List<WayPoint>();
|
||||
@@ -107,6 +139,11 @@ namespace Barotrauma
|
||||
currentIndex++;
|
||||
}
|
||||
|
||||
public void SkipToNode(int nodeIndex)
|
||||
{
|
||||
currentIndex = nodeIndex;
|
||||
}
|
||||
|
||||
public WayPoint CheckProgress(Vector2 simPosition, float minSimDistance = 0.1f)
|
||||
{
|
||||
if (nodes.Count == 0 || currentIndex > nodes.Count - 1) { return null; }
|
||||
|
||||
Reference in New Issue
Block a user