v1.0.20.1 (summer patch)

This commit is contained in:
itchyOwl
2023-06-15 16:46:54 +03:00
parent 6acac1d143
commit 83de72e3d2
209 changed files with 4497 additions and 2488 deletions
@@ -26,7 +26,8 @@ namespace Barotrauma
public Func<float> PriorityGetter;
public bool IsFollowOrderObjective;
public bool IsFollowOrder;
public bool IsWaitOrder;
public bool Mimic;
public bool SpeakIfFails { get; set; } = true;
@@ -59,7 +60,7 @@ namespace Barotrauma
{
get
{
if (IsFollowOrderObjective && Target is Character targetCharacter && (targetCharacter.CurrentHull == null) != (character.CurrentHull == null))
if (IsFollowOrder && Target is Character targetCharacter && (targetCharacter.CurrentHull == null) != (character.CurrentHull == null))
{
// Keep close when the target is going inside/outside
return minDistance;
@@ -220,15 +221,45 @@ namespace Barotrauma
}
}
Hull targetHull = GetTargetHull();
if (!IsFollowOrderObjective)
if (!IsFollowOrder)
{
// Abandon if going through unsafe paths. Note ignores unsafe nodes when following an order or when the objective is set to ignore unsafe hulls.
bool containsUnsafeNodes = character.IsDismissed && !HumanAIController.ObjectiveManager.CurrentObjective.IgnoreUnsafeHulls
&& PathSteering != null && PathSteering.CurrentPath != null
&& PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull));
if (containsUnsafeNodes || HumanAIController.UnreachableHulls.Contains(targetHull))
bool isUnreachable = HumanAIController.UnreachableHulls.Contains(targetHull);
if (!objectiveManager.CurrentObjective.IgnoreUnsafeHulls)
{
Abandon = true;
if (HumanAIController.UnsafeHulls.Contains(targetHull))
{
isUnreachable = true;
HumanAIController.AskToRecalculateHullSafety(targetHull);
}
else if (PathSteering?.CurrentPath != null)
{
foreach (WayPoint wp in PathSteering.CurrentPath.Nodes)
{
if (wp.CurrentHull == null) { continue; }
if (HumanAIController.UnsafeHulls.Contains(wp.CurrentHull))
{
isUnreachable = true;
HumanAIController.AskToRecalculateHullSafety(wp.CurrentHull);
}
}
}
}
if (isUnreachable)
{
SteeringManager.Reset();
if (PathSteering?.CurrentPath != null)
{
PathSteering.CurrentPath.Unreachable = true;
}
if (repeat)
{
SpeakCannotReach();
}
else
{
Abandon = true;
}
return;
}
}
bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty;
@@ -250,6 +281,7 @@ namespace Barotrauma
if (repeat)
{
SpeakCannotReach();
return;
}
else
{
@@ -262,310 +294,285 @@ namespace Barotrauma
waitUntilPathUnreachable = pathWaitingTime;
}
}
if (!Abandon)
if (Abandon) { return; }
if (getDivingGearIfNeeded)
{
if (getDivingGearIfNeeded)
Character followTarget = Target as Character;
bool needsDivingSuit = (!isInside || hasOutdoorNodes) && !character.IsImmuneToPressure;
bool tryToGetDivingGear = needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit);
bool tryToGetDivingSuit = needsDivingSuit;
if (Mimic && !character.IsImmuneToPressure)
{
Character followTarget = Target as Character;
bool needsDivingSuit = (!isInside || hasOutdoorNodes) && !character.IsImmuneToPressure;
bool tryToGetDivingGear = needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit);
bool tryToGetDivingSuit = needsDivingSuit;
if (Mimic && !character.IsImmuneToPressure)
if (HumanAIController.HasDivingSuit(followTarget))
{
if (HumanAIController.HasDivingSuit(followTarget))
tryToGetDivingGear = true;
tryToGetDivingSuit = true;
}
else if (HumanAIController.HasDivingMask(followTarget) && character.CharacterHealth.OxygenLowResistance < 1)
{
tryToGetDivingGear = true;
}
}
bool needsEquipment = false;
float minOxygen = AIObjectiveFindDivingGear.GetMinOxygen(character);
if (tryToGetDivingSuit)
{
needsEquipment = !HumanAIController.HasDivingSuit(character, minOxygen);
}
else if (tryToGetDivingGear)
{
needsEquipment = !HumanAIController.HasDivingGear(character, minOxygen);
}
if (character.LockHands)
{
cantFindDivingGear = true;
}
if (cantFindDivingGear && needsDivingSuit)
{
// Don't try to reach the target without a suit because it's lethal.
Abandon = true;
return;
}
if (needsEquipment && !cantFindDivingGear)
{
SteeringManager.Reset();
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: tryToGetDivingSuit, objectiveManager),
onAbandon: () =>
{
tryToGetDivingGear = true;
tryToGetDivingSuit = true;
}
else if (HumanAIController.HasDivingMask(followTarget) && character.CharacterHealth.OxygenLowResistance < 1)
{
tryToGetDivingGear = true;
}
}
bool needsEquipment = false;
float minOxygen = AIObjectiveFindDivingGear.GetMinOxygen(character);
if (tryToGetDivingSuit)
{
needsEquipment = !HumanAIController.HasDivingSuit(character, minOxygen);
}
else if (tryToGetDivingGear)
{
needsEquipment = !HumanAIController.HasDivingGear(character, minOxygen);
}
if (character.LockHands)
{
cantFindDivingGear = true;
}
if (cantFindDivingGear && needsDivingSuit)
{
// Don't try to reach the target without a suit because it's lethal.
Abandon = true;
return;
}
if (needsEquipment && !cantFindDivingGear)
{
SteeringManager.Reset();
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: tryToGetDivingSuit, objectiveManager),
onAbandon: () =>
{
cantFindDivingGear = true;
if (needsDivingSuit)
{
// Shouldn't try to reach the target without a suit, because it's lethal.
Abandon = true;
}
else
{
// Try again without requiring the diving suit
RemoveSubObjective(ref findDivingGear);
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: false, objectiveManager),
onAbandon: () =>
{
Abandon = character.CurrentHull != null && (objectiveManager.CurrentOrder != this || Target.Submarine == null);
RemoveSubObjective(ref findDivingGear);
},
onCompleted: () =>
{
RemoveSubObjective(ref findDivingGear);
});
}
},
onCompleted: () => RemoveSubObjective(ref findDivingGear));
return;
}
}
if (repeat)
{
if (IsCloseEnough)
{
if (requiredCondition == null || requiredCondition())
if (needsDivingSuit)
{
if (character.CanSeeTarget(Target))
{
OnCompleted();
return;
}
}
}
}
float maxGapDistance = 500;
Character targetCharacter = Target as Character;
if (character.AnimController.InWater)
{
if (character.CurrentHull == null ||
IsFollowOrderObjective &&
targetCharacter != null && (targetCharacter.CurrentHull == null) != (character.CurrentHull == null) &&
Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition) < maxGapDistance * maxGapDistance)
{
if (seekGapsTimer > 0)
{
seekGapsTimer -= deltaTime;
// Shouldn't try to reach the target without a suit, because it's lethal.
Abandon = true;
}
else
{
bool isRuins = character.Submarine?.Info.IsRuin != null || Target.Submarine?.Info.IsRuin != null;
bool isEitherOneInside = isInside || Target.Submarine != null;
if (isEitherOneInside && (!isRuins || !HumanAIController.HasValidPath()))
{
SeekGaps(maxGapDistance);
seekGapsTimer = seekGapsInterval * Rand.Range(0.1f, 1.1f);
if (TargetGap != null)
// Try again without requiring the diving suit
RemoveSubObjective(ref findDivingGear);
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: false, objectiveManager),
onAbandon: () =>
{
// Check that nothing is blocking the way
Vector2 rayStart = character.SimPosition;
Vector2 rayEnd = TargetGap.SimPosition;
if (TargetGap.Submarine != null && character.Submarine == null)
{
rayStart -= TargetGap.Submarine.SimPosition;
}
else if (TargetGap.Submarine == null && character.Submarine != null)
{
rayEnd -= character.Submarine.SimPosition;
}
var closestBody = Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true);
if (closestBody != null)
{
TargetGap = null;
}
}
Abandon = character.CurrentHull != null && (objectiveManager.CurrentOrder != this || Target.Submarine == null);
RemoveSubObjective(ref findDivingGear);
},
onCompleted: () =>
{
RemoveSubObjective(ref findDivingGear);
});
}
else
{
TargetGap = null;
}
}
},
onCompleted: () => RemoveSubObjective(ref findDivingGear));
return;
}
}
if (repeat && IsCloseEnough)
{
if (requiredCondition == null || requiredCondition())
{
if (character.CanSeeTarget(Target) && (!character.IsClimbing || IsFollowOrder))
{
OnCompleted();
return;
}
}
}
float maxGapDistance = 500;
Character targetCharacter = Target as Character;
if (character.AnimController.InWater)
{
if (character.CurrentHull == null ||
IsFollowOrder &&
targetCharacter != null && (targetCharacter.CurrentHull == null) != (character.CurrentHull == null) &&
Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition) < maxGapDistance * maxGapDistance)
{
if (seekGapsTimer > 0)
{
seekGapsTimer -= deltaTime;
}
else
{
TargetGap = null;
}
if (TargetGap != null)
{
if (TargetGap.FlowTargetHull != null && HumanAIController.SteerThroughGap(TargetGap, IsFollowOrderObjective ? Target.WorldPosition : TargetGap.FlowTargetHull.WorldPosition, deltaTime))
bool isRuins = character.Submarine?.Info.IsRuin != null || Target.Submarine?.Info.IsRuin != null;
bool isEitherOneInside = isInside || Target.Submarine != null;
if (isEitherOneInside && (!isRuins || !HumanAIController.HasValidPath()))
{
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 1);
return;
SeekGaps(maxGapDistance);
seekGapsTimer = seekGapsInterval * Rand.Range(0.1f, 1.1f);
if (TargetGap != null)
{
// Check that nothing is blocking the way
Vector2 rayStart = character.SimPosition;
Vector2 rayEnd = TargetGap.SimPosition;
if (TargetGap.Submarine != null && character.Submarine == null)
{
rayStart -= TargetGap.Submarine.SimPosition;
}
else if (TargetGap.Submarine == null && character.Submarine != null)
{
rayEnd -= character.Submarine.SimPosition;
}
var closestBody = Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true);
if (closestBody != null)
{
TargetGap = null;
}
}
}
else
{
TargetGap = null;
}
}
if (checkScooterTimer <= 0)
{
useScooter = false;
checkScooterTimer = checkScooterTime * Rand.Range(0.75f, 1.25f);
Identifier scooterTag = "scooter".ToIdentifier();
Identifier batteryTag = "mobilebattery".ToIdentifier();
Item scooter = null;
bool shouldUseScooter = Mimic && targetCharacter != null && targetCharacter.HasEquippedItem(scooterTag, allowBroken: false);
if (!shouldUseScooter)
{
float threshold = 500;
if (isInside)
{
Vector2 diff = Target.WorldPosition - character.WorldPosition;
shouldUseScooter = Math.Abs(diff.X) > threshold || Math.Abs(diff.Y) > 150;
}
else
{
shouldUseScooter = Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition) > threshold * threshold;
}
}
if (HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> equippedScooters, recursive: false, requireEquipped: true))
{
// Currently equipped scooter
scooter = equippedScooters.FirstOrDefault();
}
else if (shouldUseScooter)
{
var leftHandItem = character.GetEquippedItem(slotType: InvSlotType.LeftHand);
var rightHandItem = character.GetEquippedItem(slotType: InvSlotType.RightHand);
bool handsFull =
(leftHandItem != null && !character.Inventory.IsAnySlotAvailable(leftHandItem)) ||
(rightHandItem != null && !character.Inventory.IsAnySlotAvailable(rightHandItem));
if (!handsFull)
{
bool hasBattery = false;
if (HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> nonEquippedScooters, containedTag: batteryTag, conditionPercentage: 1, requireEquipped: false))
{
// Non-equipped scooter with a battery
scooter = nonEquippedScooters.FirstOrDefault();
hasBattery = true;
}
else if (HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> _nonEquippedScooters, requireEquipped: false))
{
// Non-equipped scooter without a battery
scooter = _nonEquippedScooters.FirstOrDefault();
// Non-recursive so that the bots won't take batteries from other items. Also means that they can't find batteries inside containers. Not sure how to solve this.
hasBattery = HumanAIController.HasItem(character, batteryTag, out _, requireEquipped: false, conditionPercentage: 1, recursive: false);
}
if (scooter != null && hasBattery)
{
// Equip only if we have a battery available
HumanAIController.TakeItem(scooter, character.Inventory, equip: true, dropOtherIfCannotMove: false, allowSwapping: true, storeUnequipped: false);
}
}
}
if (scooter != null && character.HasEquippedItem(scooter))
{
if (shouldUseScooter)
{
useScooter = true;
// Check the battery
if (scooter.ContainedItems.None(i => i.Condition > 0))
{
// Try to switch batteries
if (HumanAIController.HasItem(character, batteryTag, out IEnumerable<Item> batteries, conditionPercentage: 1, recursive: false))
{
scooter.ContainedItems.ForEachMod(emptyBattery => character.Inventory.TryPutItem(emptyBattery, character, CharacterInventory.AnySlot));
if (!scooter.Combine(batteries.OrderByDescending(b => b.Condition).First(), character))
{
useScooter = false;
}
}
else
{
useScooter = false;
}
}
}
if (!useScooter)
{
// Unequip
character.Inventory.TryPutItem(scooter, character, CharacterInventory.AnySlot);
}
}
}
else
{
checkScooterTimer -= deltaTime;
}
}
else
{
TargetGap = null;
useScooter = false;
checkScooterTimer = 0;
}
if (SteeringManager == PathSteering)
if (TargetGap != null)
{
Vector2 targetPos = character.GetRelativeSimPosition(Target);
Func<PathNode, bool> nodeFilter = null;
if (isInside && !AllowGoingOutside)
if (TargetGap.FlowTargetHull != null && HumanAIController.SteerThroughGap(TargetGap, IsFollowOrder ? Target.WorldPosition : TargetGap.FlowTargetHull.WorldPosition, deltaTime))
{
nodeFilter = n => n.Waypoint.CurrentHull != null;
}
else if (!isInside)
{
if (HumanAIController.UseOutsideWaypoints)
{
nodeFilter = n => n.Waypoint.Submarine == null;
}
else
{
nodeFilter = n => n.Waypoint.Submarine != null || n.Waypoint.Ruin != null;
}
}
if (!isInside && !UsePathingOutside)
{
character.ReleaseSecondaryItem();
PathSteering.SteeringSeekSimple(character.GetRelativeSimPosition(Target), 10);
if (character.AnimController.InWater)
{
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 15);
}
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 1);
return;
}
else
{
PathSteering.SteeringSeek(targetPos, weight: 1,
startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (character.CurrentHull == null),
endNodeFilter: endNodeFilter,
nodeFilter: nodeFilter,
checkVisiblity: Target is Item || Target is Character);
TargetGap = null;
}
if (!isInside && (PathSteering.CurrentPath == null || PathSteering.IsPathDirty || PathSteering.CurrentPath.Unreachable))
}
if (checkScooterTimer <= 0)
{
useScooter = false;
checkScooterTimer = checkScooterTime * Rand.Range(0.75f, 1.25f);
Identifier scooterTag = "scooter".ToIdentifier();
Identifier batteryTag = "mobilebattery".ToIdentifier();
Item scooter = null;
bool shouldUseScooter = Mimic && targetCharacter != null && targetCharacter.HasEquippedItem(scooterTag, allowBroken: false);
if (!shouldUseScooter)
{
if (useScooter)
float threshold = 500;
if (isInside)
{
UseScooter(Target.WorldPosition);
Vector2 diff = Target.WorldPosition - character.WorldPosition;
shouldUseScooter = Math.Abs(diff.X) > threshold || Math.Abs(diff.Y) > 150;
}
else
{
character.ReleaseSecondaryItem();
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(Target.WorldPosition - character.WorldPosition));
if (character.AnimController.InWater)
shouldUseScooter = Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition) > threshold * threshold;
}
}
if (HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> equippedScooters, recursive: false, requireEquipped: true))
{
// Currently equipped scooter
scooter = equippedScooters.FirstOrDefault();
}
else if (shouldUseScooter)
{
var leftHandItem = character.GetEquippedItem(slotType: InvSlotType.LeftHand);
var rightHandItem = character.GetEquippedItem(slotType: InvSlotType.RightHand);
bool handsFull =
(leftHandItem != null && !character.Inventory.IsAnySlotAvailable(leftHandItem)) ||
(rightHandItem != null && !character.Inventory.IsAnySlotAvailable(rightHandItem));
if (!handsFull)
{
bool hasBattery = false;
if (HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> nonEquippedScooters, containedTag: batteryTag, conditionPercentage: 1, requireEquipped: false))
{
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 2);
// Non-equipped scooter with a battery
scooter = nonEquippedScooters.FirstOrDefault();
hasBattery = true;
}
else if (HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> _nonEquippedScooters, requireEquipped: false))
{
// Non-equipped scooter without a battery
scooter = _nonEquippedScooters.FirstOrDefault();
// Non-recursive so that the bots won't take batteries from other items. Also means that they can't find batteries inside containers. Not sure how to solve this.
hasBattery = HumanAIController.HasItem(character, batteryTag, out _, requireEquipped: false, conditionPercentage: 1, recursive: false);
}
if (scooter != null && hasBattery)
{
// Equip only if we have a battery available
HumanAIController.TakeItem(scooter, character.Inventory, equip: true, dropOtherIfCannotMove: false, allowSwapping: true, storeUnequipped: false);
}
}
}
else if (useScooter && PathSteering.CurrentPath?.CurrentNode != null)
if (scooter != null && character.HasEquippedItem(scooter))
{
UseScooter(PathSteering.CurrentPath.CurrentNode.WorldPosition);
if (shouldUseScooter)
{
useScooter = true;
// Check the battery
if (scooter.ContainedItems.None(i => i.Condition > 0))
{
// Try to switch batteries
if (HumanAIController.HasItem(character, batteryTag, out IEnumerable<Item> batteries, conditionPercentage: 1, recursive: false))
{
scooter.ContainedItems.ForEachMod(emptyBattery => character.Inventory.TryPutItem(emptyBattery, character, CharacterInventory.AnySlot));
if (!scooter.Combine(batteries.OrderByDescending(b => b.Condition).First(), character))
{
useScooter = false;
}
}
else
{
useScooter = false;
}
}
}
if (!useScooter)
{
// Unequip
character.Inventory.TryPutItem(scooter, character, CharacterInventory.AnySlot);
}
}
}
else
{
checkScooterTimer -= deltaTime;
}
}
else
{
TargetGap = null;
useScooter = false;
checkScooterTimer = 0;
}
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)
{
if (HumanAIController.UseOutsideWaypoints)
{
nodeFilter = n => n.Waypoint.Submarine == null;
}
else
{
nodeFilter = n => n.Waypoint.Submarine != null || n.Waypoint.Ruin != null;
}
}
if (!isInside && !UsePathingOutside)
{
character.ReleaseSecondaryItem();
PathSteering.SteeringSeekSimple(character.GetRelativeSimPosition(Target), 10);
if (character.AnimController.InWater)
{
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 15);
}
}
else
{
PathSteering.SteeringSeek(targetPos, weight: 1,
startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (character.CurrentHull == null),
endNodeFilter: endNodeFilter,
nodeFilter: nodeFilter,
checkVisiblity: Target is Item || Target is Character);
}
if (!isInside && (PathSteering.CurrentPath == null || PathSteering.IsPathDirty || PathSteering.CurrentPath.Unreachable))
{
if (useScooter)
{
@@ -574,13 +581,33 @@ namespace Barotrauma
else
{
character.ReleaseSecondaryItem();
SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Target), 10);
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(Target.WorldPosition - character.WorldPosition));
if (character.AnimController.InWater)
{
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 15);
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 2);
}
}
}
else if (useScooter && PathSteering.CurrentPath?.CurrentNode != null)
{
UseScooter(PathSteering.CurrentPath.CurrentNode.WorldPosition);
}
}
else
{
if (useScooter)
{
UseScooter(Target.WorldPosition);
}
else
{
character.ReleaseSecondaryItem();
SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Target), 10);
if (character.AnimController.InWater)
{
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 15);
}
}
}
void UseScooter(Vector2 targetWorldPos)
@@ -595,7 +622,7 @@ namespace Barotrauma
}
Vector2 diff = character.CursorPosition - character.Position;
Vector2 dir = Vector2.Normalize(diff);
if (character.CurrentHull == null && IsFollowOrderObjective)
if (character.CurrentHull == null && IsFollowOrder)
{
float sqrDist = diff.LengthSquared();
if (sqrDist > MathUtils.Pow2(CloseEnough * 1.5f))
@@ -674,7 +701,7 @@ namespace Barotrauma
{
if (gap.Open < 1) { continue; }
if (gap.Submarine == null) { continue; }
if (!IsFollowOrderObjective)
if (!IsFollowOrder)
{
if (gap.FlowTargetHull == null) { continue; }
if (gap.Submarine != Target.Submarine) { continue; }
@@ -792,8 +819,10 @@ namespace Barotrauma
// Release ladders when ordered to wait at a spawnpoint.
// This is a special case specifically meant for NPCs that spawn in outposts with a wait order.
// Otherwise they might keep holding to the ladders when the target is just next to it.
// Releasing too early should be handled inside the IsCloseEnough property.
character.ReleaseSecondaryItem();
if (character.IsClimbing && character.AnimController.IsAboveFloor)
{
character.StopClimbing();
}
}
base.OnCompleted();
}