v1.0.13.1 (first post-1.0 patch)

This commit is contained in:
Regalis11
2023-05-10 15:07:17 +03:00
parent 96fb49ba14
commit ee1db852b1
272 changed files with 5738 additions and 2413 deletions
@@ -506,15 +506,29 @@ namespace Barotrauma
}
}
protected static bool CanEquip(Character character, Item item)
protected static bool CanEquip(Character character, Item item, bool allowWearing)
{
bool canEquip = item != null;
if (canEquip && !item.AllowedSlots.Contains(InvSlotType.Any))
if (item == null) { return false; }
bool canEquip = false;
if (item.AllowedSlots.Contains(InvSlotType.Any))
{
if (character.Inventory.IsAnySlotAvailable(item))
{
canEquip = true;
}
}
if (!canEquip)
{
canEquip = false;
var inv = character.Inventory;
foreach (var allowedSlot in item.AllowedSlots)
{
if (!allowWearing)
{
if (!allowedSlot.HasFlag(InvSlotType.RightHand) && !allowedSlot.HasFlag(InvSlotType.LeftHand))
{
continue;
}
}
foreach (var slotType in inv.SlotTypes)
{
if (!allowedSlot.HasFlag(slotType)) { continue; }
@@ -530,18 +544,9 @@ namespace Barotrauma
}
}
}
return canEquip;
}
protected bool CheckItemIdentifiersOrTags(Item item, ImmutableHashSet<Identifier> identifiersOrTags)
{
if (identifiersOrTags.Contains(item.Prefab.Identifier)) { return true; }
foreach (var identifier in identifiersOrTags)
{
if (item.HasTag(identifier)) { return true; }
}
return false;
return canEquip && character.Inventory.CanBePut(item);
}
protected bool CanEquip(Item item) => CanEquip(character, item);
protected bool CanEquip(Item item, bool allowWearing) => CanEquip(character, item, allowWearing);
}
}
@@ -64,7 +64,6 @@ namespace Barotrauma
if (subObjectives.Any()) { return; }
if (HumanAIController.FindSuitableContainer(character, item, ignoredContainers, ref itemIndex, out Item suitableContainer))
{
itemIndex = 0;
if (suitableContainer != null)
{
bool equip = item.GetComponent<Holdable>() != null ||
@@ -112,10 +111,7 @@ namespace Barotrauma
Abandon = true;
}
}
else
{
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
}
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
}
protected override bool CheckObjectiveSpecific()
@@ -121,7 +121,7 @@ namespace Barotrauma
{
return true;
}
return CanEquip(character, item);
return CanEquip(character, item, allowWearing: false);
}
public override void OnDeselected()
@@ -170,13 +170,33 @@ namespace Barotrauma
return Priority;
}
}
float damageFactor = MathUtils.InverseLerp(0.0f, 5.0f, character.GetDamageDoneByAttacker(Enemy) / 100.0f);
Priority = TargetEliminated ? 0 : Math.Min((95 + damageFactor) * PriorityModifier, 100);
if (Priority > 0)
if (TargetEliminated)
{
if (EnemyAIController.IsLatchedToSomeoneElse(Enemy, character))
Priority = 0;
}
else
{
// 91-100
float minPriority = AIObjectiveManager.EmergencyObjectivePriority + 1;
float maxPriority = AIObjectiveManager.MaxObjectivePriority;
float priorityScale = maxPriority - minPriority;
float xDist = Math.Abs(character.WorldPosition.X - Enemy.WorldPosition.X);
float yDist = Math.Abs(character.WorldPosition.Y - Enemy.WorldPosition.Y);
if (HumanAIController.VisibleHulls.Contains(Enemy.CurrentHull))
{
Priority = 0;
xDist /= 2;
yDist /= 2;
}
float distanceFactor = MathUtils.InverseLerp(3000, 0, xDist + yDist * 5);
float devotion = CumulatedDevotion / 100;
float additionalPriority = MathHelper.Lerp(0, priorityScale, Math.Clamp(devotion + distanceFactor, 0, 1));
Priority = Math.Min((minPriority + additionalPriority) * PriorityModifier, maxPriority);
if (Priority > 0)
{
if (EnemyAIController.IsLatchedToSomeoneElse(Enemy, character))
{
Priority = 0;
}
}
}
return Priority;
@@ -312,12 +332,10 @@ namespace Barotrauma
}
else
{
AskHelp();
Retreat(deltaTime);
}
break;
case CombatMode.Retreat:
AskHelp();
Retreat(deltaTime);
break;
default:
@@ -352,7 +370,7 @@ namespace Barotrauma
Weapon = null;
continue;
}
if (WeaponComponent.IsNotEmpty(character))
if (!WeaponComponent.IsEmpty(character))
{
// All good, the weapon is loaded
break;
@@ -470,7 +488,7 @@ namespace Barotrauma
// Not in the inventory anymore or cannot find the weapon component
return false;
}
if (!WeaponComponent.IsNotEmpty(character))
if (WeaponComponent.IsEmpty(character))
{
// Try reloading (and seek ammo)
if (!Reload(seekAmmo))
@@ -541,7 +559,7 @@ namespace Barotrauma
priority /= 2;
}
}
if (!weapon.IsNotEmpty(character))
if (weapon.IsEmpty(character))
{
if (weapon is RangedWeapon && !isAllowedToSeekWeapons)
{
@@ -554,7 +572,6 @@ namespace Barotrauma
priority /= 2;
}
}
if (Enemy.Params.Health.StunImmunity)
{
if (weapon.Item.HasTag("stunner"))
@@ -750,7 +767,7 @@ namespace Barotrauma
private bool Equip()
{
if (character.LockHands) { return false; }
if (!WeaponComponent.HasRequiredContainedItems(character, addMessage: false))
if (WeaponComponent.IsEmpty(character))
{
return false;
}
@@ -783,6 +800,10 @@ namespace Barotrauma
private void Retreat(float deltaTime)
{
if (!Enemy.IsHuman)
{
SpeakRetreating();
}
RemoveFollowTarget();
RemoveSubObjective(ref seekAmmunitionObjective);
if (retreatObjective != null && retreatObjective.Target != retreatTarget)
@@ -793,6 +814,7 @@ namespace Barotrauma
{
// Swim away
SteeringManager.Reset();
character.ReleaseSecondaryItem();
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.WorldPosition - Enemy.WorldPosition));
SteeringManager.SteeringAvoid(deltaTime, 5, weight: 2);
return;
@@ -819,7 +841,8 @@ namespace Barotrauma
{
TryAddSubObjective(ref retreatObjective, () => new AIObjectiveGoTo(retreatTarget, character, objectiveManager)
{
UsePathingOutside = false
UsePathingOutside = false,
SpeakIfFails = false
},
onAbandon: () =>
{
@@ -861,6 +884,7 @@ namespace Barotrauma
{
if (sqrDistance > MathUtils.Pow2(meleeWeapon.Range))
{
character.ReleaseSecondaryItem();
// Swim towards the target
SteeringManager.Reset();
SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Enemy), weight: 10);
@@ -882,7 +906,8 @@ namespace Barotrauma
UsePathingOutside = false,
IgnoreIfTargetDead = true,
TargetName = Enemy.DisplayName,
AlwaysUseEuclideanDistance = false
AlwaysUseEuclideanDistance = false,
SpeakIfFails = false
},
onAbandon: () =>
{
@@ -966,7 +991,7 @@ namespace Barotrauma
item.GetComponent<RangedWeapon>() != null)
{
item.Drop(character);
character.Inventory.TryPutItem(item, character, CharacterInventory.anySlot);
character.Inventory.TryPutItem(item, character, CharacterInventory.AnySlot);
}
}
}
@@ -1028,54 +1053,43 @@ namespace Barotrauma
if (Weapon.OwnInventory == null) { return true; }
// Eject empty ammo
HumanAIController.UnequipEmptyItems(Weapon);
RelatedItem item = null;
Item ammunition = null;
ImmutableHashSet<Identifier> ammunitionIdentifiers = null;
if (WeaponComponent.requiredItems.ContainsKey(RelatedItem.RelationType.Contained))
{
foreach (RelatedItem requiredItem in WeaponComponent.requiredItems[RelatedItem.RelationType.Contained])
{
ammunition = Weapon.OwnInventory.AllItems.FirstOrDefault(it => it.Condition > 0 && requiredItem.MatchesItem(it));
if (ammunition != null)
{
// Ammunition still remaining
return true;
}
item = requiredItem;
if (Weapon.OwnInventory.AllItems.Any(it => it.Condition > 0 && requiredItem.MatchesItem(it))) { continue; }
ammunitionIdentifiers = requiredItem.Identifiers;
break;
}
}
else if (WeaponComponent is MeleeWeapon meleeWeapon)
{
ammunitionIdentifiers = meleeWeapon.PreferredContainedItems;
}
// No ammo
if (ammunition == null)
if (ammunitionIdentifiers != null)
{
if (ammunitionIdentifiers != null)
// Try reload ammunition from inventory
static bool IsInsideHeadset(Item i) => i.ParentInventory?.Owner is Item ownerItem && ownerItem.HasTag("mobileradio");
Item ammunition = character.Inventory.FindItem(i => i.HasIdentifierOrTags(ammunitionIdentifiers) && i.Condition > 0 && !IsInsideHeadset(i), recursive: true);
if (ammunition != null)
{
// Try reload ammunition from inventory
static bool IsInsideHeadset(Item i) => i.ParentInventory?.Owner is Item ownerItem && ownerItem.HasTag("mobileradio");
ammunition = character.Inventory.FindItem(i => CheckItemIdentifiersOrTags(i, ammunitionIdentifiers) && i.Condition > 0 && !IsInsideHeadset(i), recursive: true);
if (ammunition != null)
var container = Weapon.GetComponent<ItemContainer>();
if (!container.Inventory.TryPutItem(ammunition, user: character))
{
var container = Weapon.GetComponent<ItemContainer>();
if (!container.Inventory.TryPutItem(ammunition, null))
if (ammunition.ParentInventory == character.Inventory)
{
if (ammunition.ParentInventory == character.Inventory)
{
ammunition.Drop(character);
}
ammunition.Drop(character);
}
}
}
}
if (WeaponComponent.HasRequiredContainedItems(character, addMessage: false))
if (!WeaponComponent.IsEmpty(character))
{
return true;
}
else if (ammunition == null && !HoldPosition && IsOffensiveOrArrest && seekAmmo && ammunitionIdentifiers != null)
else if (!HoldPosition && IsOffensiveOrArrest && seekAmmo && ammunitionIdentifiers != null)
{
SeekAmmunition(ammunitionIdentifiers);
}
@@ -1270,7 +1284,7 @@ namespace Barotrauma
}
private void SpeakNoWeapons() => Speak("dialogcombatnoweapons".ToIdentifier(), delay: 0, minDuration: 30);
private void AskHelp() => Speak("dialogcombatretreating".ToIdentifier(), delay: Rand.Range(0f, 1f), minDuration: 20);
private void SpeakRetreating() => Speak("dialogcombatretreating".ToIdentifier(), delay: Rand.Range(0f, 1f), minDuration: 20);
private void Speak(Identifier textIdentifier, float delay, float minDuration)
{
@@ -109,7 +109,7 @@ namespace Barotrauma
private bool CheckItem(Item item)
{
return CheckItemIdentifiersOrTags(item, itemIdentifiers) && item.ConditionPercentage >= ConditionLevel && item.HasAccess(character);
return item.HasIdentifierOrTags(itemIdentifiers) && item.ConditionPercentage >= ConditionLevel && item.HasAccess(character);
}
protected override void Act(float deltaTime)
@@ -156,15 +156,15 @@ namespace Barotrauma
Inventory originalInventory = ItemToContain.ParentInventory;
var slots = originalInventory?.FindIndices(ItemToContain);
static bool TryPutItem(Inventory inventory, int? targetSlot, Item itemToContain)
bool TryPutItem(Inventory inventory, int? targetSlot, Item itemToContain)
{
if (targetSlot.HasValue)
{
return inventory.TryPutItem(itemToContain, targetSlot.Value, allowSwapping: false, allowCombine: false, user: null);
return inventory.TryPutItem(itemToContain, targetSlot.Value, allowSwapping: false, allowCombine: false, user: character);
}
else
{
return inventory.TryPutItem(itemToContain, user: null);
return inventory.TryPutItem(itemToContain, user: character);
}
}
@@ -202,7 +202,7 @@ namespace Barotrauma
ItemToContain == null || ItemToContain.Removed ||
!ItemToContain.IsOwnedBy(character) || container.Item.GetRootInventoryOwner() is Character c && c != character,
SpeakIfFails = !objectiveManager.IsCurrentOrder<AIObjectiveCleanupItems>(),
endNodeFilter = n => Vector2.DistanceSquared(n.Waypoint.WorldPosition, container.Item.WorldPosition) <= MathUtils.Pow2(AIObjectiveGetItem.DefaultReach)
endNodeFilter = n => Vector2.DistanceSquared(n.Waypoint.WorldPosition, container.Item.WorldPosition) <= MathUtils.Pow2(AIObjectiveGetItem.MaxReach)
},
onAbandon: () => Abandon = true,
onCompleted: () => RemoveSubObjective(ref goToObjective));
@@ -66,7 +66,7 @@ namespace Barotrauma
else
{
float devotion = CumulatedDevotion / 100;
Priority = MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
Priority = MathHelper.Lerp(0, AIObjectiveManager.MaxObjectivePriority, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
}
}
return Priority;
@@ -28,7 +28,7 @@ namespace Barotrauma
if (character.IsSecurity) { return 100; }
if (objectiveManager.IsOrder(this)) { return 100; }
// If there's any security officers onboard, leave fighting for them.
return HumanAIController.IsTrueForAnyCrewMember(c => c.Character.IsSecurity && !c.Character.IsIncapacitated && c.Character.Submarine == character.Submarine) ? 0 : 100;
return HumanAIController.IsTrueForAnyCrewMember(c => c.IsSecurity, onlyActive: true, onlyConnectedSubs: true) ? 0 : 100;
}
protected override AIObjective ObjectiveConstructor(Character target)
@@ -37,8 +37,7 @@ namespace Barotrauma
var combatObjective = new AIObjectiveCombat(character, target, combatMode, objectiveManager, PriorityModifier);
if (character.TeamID == CharacterTeamType.FriendlyNPC && target.TeamID == CharacterTeamType.Team1 && GameMain.GameSession?.GameMode is CampaignMode campaign)
{
var reputation = campaign.Map?.CurrentLocation?.Reputation;
if (reputation != null && reputation.NormalizedValue < Reputation.HostileThreshold)
if (campaign.CurrentLocation is { IsFactionHostile: true })
{
combatObjective.holdFireCondition = () =>
{
@@ -66,7 +65,13 @@ namespace Barotrauma
if (target.CurrentHull == null) { return false; }
if (HumanAIController.IsFriendly(character, target)) { return false; }
if (!character.Submarine.IsConnectedTo(target.Submarine)) { return false; }
if (!targetCharactersInOtherSubs && character.Submarine.TeamID != target.Submarine.TeamID && character.Submarine.TeamID != character.OriginalTeamID) { return false; }
if (!targetCharactersInOtherSubs)
{
if (character.Submarine.TeamID != target.Submarine.TeamID && character.OriginalTeamID != target.Submarine.TeamID)
{
return false;
}
}
if (target.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { return false; }
if (target.IsArrested) { return false; }
if (EnemyAIController.IsLatchedToSomeoneElse(target, character)) { return false; }
@@ -79,7 +79,7 @@ namespace Barotrauma
{
if (mask != targetItem)
{
character.Inventory.TryPutItem(mask, character, CharacterInventory.anySlot);
character.Inventory.TryPutItem(mask, character, CharacterInventory.AnySlot);
}
}
}
@@ -52,17 +52,14 @@ namespace Barotrauma
objectiveManager.HasOrder<AIObjectiveReturn>(o => o.Priority > 0) ||
objectiveManager.HasActiveObjective<AIObjectiveRescue>() ||
objectiveManager.Objectives.Any(o => o is AIObjectiveCombat && o.Priority > 0))
&& ((character.IsImmuneToPressure && !character.IsLowInOxygen)|| HumanAIController.HasDivingSuit(character)) ? 0 : 100;
&& ((!character.IsLowInOxygen && character.IsImmuneToPressure)|| HumanAIController.HasDivingSuit(character)) ? 0 : AIObjectiveManager.EmergencyObjectivePriority - 10;
}
else
{
if ((character.IsLowInOxygen && !character.AnimController.HeadInWater && HumanAIController.HasDivingSuit(character, requireOxygenTank: false)) ||
(HumanAIController.NeedsDivingGear(character.CurrentHull, out bool needsSuit) &&
(needsSuit ?
!HumanAIController.HasDivingSuit(character, conditionPercentage: AIObjectiveFindDivingGear.GetMinOxygen(character)) :
!HumanAIController.HasDivingGear(character, conditionPercentage: AIObjectiveFindDivingGear.GetMinOxygen(character)))))
NeedMoreDivingGear(character.CurrentHull, AIObjectiveFindDivingGear.GetMinOxygen(character)))
{
Priority = 100;
Priority = AIObjectiveManager.MaxObjectivePriority;
}
else if ((objectiveManager.IsCurrentOrder<AIObjectiveGoTo>() || objectiveManager.IsCurrentOrder<AIObjectiveReturn>()) &&
character.Submarine != null && !character.IsOnFriendlyTeam(character.Submarine.TeamID))
@@ -75,11 +72,11 @@ namespace Barotrauma
{
Priority = 0;
}
Priority = MathHelper.Clamp(Priority, 0, 100);
Priority = MathHelper.Clamp(Priority, 0, AIObjectiveManager.MaxObjectivePriority);
if (divingGearObjective != null && !divingGearObjective.IsCompleted && divingGearObjective.CanBeCompleted)
{
// Boost the priority while seeking the diving gear
Priority = Math.Max(Priority, Math.Min(AIObjectiveManager.HighestOrderPriority + 20, 100));
Priority = Math.Max(Priority, Math.Min(AIObjectiveManager.EmergencyObjectivePriority - 1, AIObjectiveManager.MaxObjectivePriority));
}
}
return Priority;
@@ -111,7 +108,7 @@ namespace Barotrauma
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
{
Priority -= priorityDecrease * deltaTime;
if (currenthullSafety >= 100)
if (currenthullSafety >= 100 && !character.IsLowInOxygen)
{
// Reduce the priority to zero so that the bot can get switch to other objectives immediately, e.g. when entering the airlock.
Priority = 0;
@@ -122,7 +119,7 @@ namespace Barotrauma
float dangerFactor = (100 - currenthullSafety) / 100;
Priority += dangerFactor * priorityIncrease * deltaTime;
}
Priority = MathHelper.Clamp(Priority, 0, 100);
Priority = MathHelper.Clamp(Priority, 0, AIObjectiveManager.MaxObjectivePriority);
}
}
@@ -138,7 +135,7 @@ namespace Barotrauma
{
if (resetPriority) { return; }
var currentHull = character.CurrentHull;
bool dangerousPressure = !character.IsProtectedFromPressure && (currentHull == null || currentHull.LethalPressure > 0);
bool dangerousPressure = (currentHull == null || currentHull.LethalPressure > 0) && !character.IsProtectedFromPressure;
bool shouldActOnSuffocation = character.IsLowInOxygen && !character.AnimController.HeadInWater && HumanAIController.HasDivingSuit(character, requireOxygenTank: false);
if (!character.LockHands && (!dangerousPressure || shouldActOnSuffocation || cannotFindSafeHull))
{
@@ -200,16 +197,11 @@ namespace Barotrauma
UpdateSimpleEscape(deltaTime);
return;
}
searchHullTimer = SearchHullInterval * Rand.Range(0.9f, 1.1f);
previousSafeHull = currentSafeHull;
currentSafeHull = potentialSafeHull;
cannotFindSafeHull = currentSafeHull == null || HumanAIController.NeedsDivingGear(currentSafeHull, out _);
if (currentSafeHull == null)
{
currentSafeHull = previousSafeHull;
}
cannotFindSafeHull = currentSafeHull == null || NeedMoreDivingGear(currentSafeHull);
currentSafeHull ??= previousSafeHull;
if (currentSafeHull != null && currentSafeHull != currentHull)
{
if (goToObjective?.Target != currentSafeHull)
@@ -219,6 +211,7 @@ namespace Barotrauma
TryAddSubObjective(ref goToObjective,
constructor: () => new AIObjectiveGoTo(currentSafeHull, character, objectiveManager, getDivingGearIfNeeded: true)
{
SpeakIfFails = false,
AllowGoingOutside =
character.IsProtectedFromPressure ||
character.CurrentHull == null ||
@@ -300,6 +293,7 @@ namespace Barotrauma
//only move if we haven't reached the edge of the room
if (escapeVel.X < 0 && character.Position.X > left || escapeVel.X > 0 && character.Position.X < right)
{
character.ReleaseSecondaryItem();
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
}
else
@@ -349,7 +343,6 @@ namespace Barotrauma
if (ignoredHulls != null && ignoredHulls.Contains(hull)) { continue; }
if (HumanAIController.UnreachableHulls.Contains(hull)) { continue; }
if (connectedSubs != null && !connectedSubs.Contains(hull.Submarine)) { continue; }
//sort the hulls based on distance and which sub they're in
//tends to make the method much faster, because we find a potential hull earlier and can discard further-away hulls more easily
//(for instance, an NPC in an outpost might otherwise go through all the hulls in the main sub first and do tons of expensive
@@ -493,5 +486,18 @@ namespace Barotrauma
cannotFindDivingGear = false;
cannotFindSafeHull = false;
}
private bool NeedMoreDivingGear(Hull targetHull, float minOxygen = 0)
{
if (!HumanAIController.NeedsDivingGear(targetHull, out bool needsSuit)) { return false; }
if (needsSuit)
{
return !HumanAIController.HasDivingSuit(character, minOxygen);
}
else
{
return !HumanAIController.HasDivingGear(character, minOxygen);
}
}
}
}
@@ -37,40 +37,56 @@ namespace Barotrauma
{
Priority = 0;
Abandon = true;
return Priority;
}
else if (HumanAIController.IsTrueForAnyCrewMember(
other => other != HumanAIController &&
other.Character.IsBot &&
other.ObjectiveManager.GetActiveObjective<AIObjectiveFixLeaks>() is AIObjectiveFixLeaks fixLeaks &&
fixLeaks.SubObjectives.Any(so => so is AIObjectiveFixLeak fixObjective && fixObjective.Leak == Leak)))
float coopMultiplier = 1;
foreach (var c in Character.CharacterList)
{
Priority = 0;
if (!HumanAIController.IsActive(c)) { continue; }
if (c.TeamID != character.TeamID) { continue; }
if (c == character) { continue; }
if (c.IsPlayer) { continue; }
if (c.AIController is HumanAIController otherAI )
{
if (otherAI.ObjectiveManager.GetFirstActiveObjective<AIObjectiveFixLeak>() is AIObjectiveFixLeak fixLeak)
{
if (fixLeak.Leak == Leak)
{
// Ignore leaks that others are already targeting
Priority = 0;
return Priority;
}
if (fixLeak.Leak.FlowTargetHull == Leak.FlowTargetHull)
{
// Reduce the priority of leaks that others should be targeting
coopMultiplier = 0.1f;
break;
}
}
}
}
float reduction = isPriority ? 1 : 2;
float maxPriority = AIObjectiveManager.LowestOrderPriority - reduction;
if (operateObjective != null && objectiveManager.GetFirstActiveObjective<AIObjectiveFixLeaks>() is AIObjectiveFixLeaks fixLeaks && fixLeaks.CurrentSubObjective == this)
{
// Prioritize leaks that we are already fixing
Priority = maxPriority;
}
else
{
float reduction = isPriority ? 1 : 2;
float maxPriority = AIObjectiveManager.LowestOrderPriority - reduction;
if (operateObjective != null && objectiveManager.GetActiveObjective<AIObjectiveFixLeaks>() is AIObjectiveFixLeaks fixLeaks && fixLeaks.CurrentSubObjective == this)
float xDist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X);
float yDist = Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y);
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally).
// If the target is close, ignore the distance factor alltogether so that we keep fixing the leaks that are nearby.
float distanceFactor = isPriority || xDist < 200 && yDist < 100 ? 1 : MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 3000, xDist + yDist * 3.0f));
if (Leak.linkedTo.Any(e => e is Hull h && h == character.CurrentHull))
{
// Prioritize leaks that we are already fixing
Priority = maxPriority;
}
else
{
float xDist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X);
float yDist = Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y);
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally).
// If the target is close, ignore the distance factor alltogether so that we keep fixing the leaks that are nearby.
float distanceFactor = isPriority || xDist < 200 && yDist < 100 ? 1 : MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 3000, xDist + yDist * 3.0f));
if (Leak.linkedTo.Any(e => e is Hull h && h == character.CurrentHull))
{
// Double the distance when the leak can be accessed from the current hull.
distanceFactor *= 2;
}
float severity = isPriority ? 1 : AIObjectiveFixLeaks.GetLeakSeverity(Leak) / 100;
float devotion = CumulatedDevotion / 100;
Priority = MathHelper.Lerp(0, maxPriority, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
// Double the distance when the leak can be accessed from the current hull.
distanceFactor *= 2;
}
float severity = isPriority ? 1 : AIObjectiveFixLeaks.GetLeakSeverity(Leak) / 100;
float devotion = CumulatedDevotion / 100;
Priority = MathHelper.Lerp(0, maxPriority, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier * coopMultiplier), 0, 1));
}
return Priority;
}
@@ -38,9 +38,9 @@ namespace Barotrauma
protected override float TargetEvaluation()
{
int totalLeaks = Targets.Count();
int totalLeaks = Targets.Count;
if (totalLeaks == 0) { return 0; }
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveFixLeaks>() && !c.Character.IsIncapacitated && c.Character.Submarine == character.Submarine, onlyBots: true);
int otherFixers = HumanAIController.CountBotsInTheCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveFixLeaks>() && c.Character.Submarine == character.Submarine);
bool anyFixers = otherFixers > 0;
if (objectiveManager.IsOrder(this))
{
@@ -52,7 +52,7 @@ namespace Barotrauma
int secondaryLeaks = Targets.Count(l => l.IsRoomToRoom);
int leaks = totalLeaks - secondaryLeaks;
float ratio = leaks == 0 ? 1 : anyFixers ? leaks / (float)otherFixers : 1;
if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / (float)HumanAIController.CountCrew(onlyBots: true) > 0.75f))
if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / (float)HumanAIController.CountBotsInTheCrew() > 0.75f))
{
// Enough fixers
return 0;
@@ -1,9 +1,11 @@
using Barotrauma.Items.Components;
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Immutable;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
namespace Barotrauma
{
@@ -31,14 +33,15 @@ namespace Barotrauma
private ISpatialEntity moveToTarget;
private bool isDoneSeeking;
public Item TargetItem => targetItem;
private int currSearchIndex;
private int currentSearchIndex;
public ImmutableHashSet<Identifier> ignoredContainerIdentifiers;
public ImmutableHashSet<Identifier> ignoredIdentifiersOrTags;
private AIObjectiveGoTo goToObjective;
private float currItemPriority;
private readonly bool checkInventory;
public static float DefaultReach = 100;
public const float DefaultReach = 100;
public const float MaxReach = 150;
public bool AllowToFindDivingGear { get; set; } = true;
public bool MustBeSpecificItem { get; set; }
@@ -76,7 +79,7 @@ namespace Barotrauma
public AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip = true, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier)
{
currSearchIndex = -1;
currentSearchIndex = 0;
Equip = equip;
originalTarget = targetItem;
this.targetItem = targetItem;
@@ -89,7 +92,7 @@ namespace Barotrauma
public AIObjectiveGetItem(Character character, IEnumerable<Identifier> identifiersOrTags, AIObjectiveManager objectiveManager, bool equip = true, bool checkInventory = true, float priorityModifier = 1, bool spawnItemIfNotFound = false)
: base(character, objectiveManager, priorityModifier)
{
currSearchIndex = -1;
currentSearchIndex = 0;
Equip = equip;
this.spawnItemIfNotFound = spawnItemIfNotFound;
this.checkInventory = checkInventory;
@@ -125,7 +128,7 @@ namespace Barotrauma
public static Func<PathNode, bool> CreateEndNodeFilter(ISpatialEntity targetEntity)
{
return n => (n.Waypoint.Ladders == null || n.Waypoint.IsInWater) && Vector2.DistanceSquared(n.Waypoint.WorldPosition, targetEntity.WorldPosition) <= MathUtils.Pow2(DefaultReach);
return n => (n.Waypoint.Ladders == null || n.Waypoint.IsInWater) && Vector2.DistanceSquared(n.Waypoint.WorldPosition, targetEntity.WorldPosition) <= MathUtils.Pow2(MaxReach);
}
private bool CheckInventory()
@@ -246,7 +249,7 @@ namespace Barotrauma
else
{
character.SelectCharacter(c);
canInteract = character.CanInteractWith(c, maxDist: DefaultReach);
canInteract = character.CanInteractWith(c);
character.DeselectCharacter();
}
}
@@ -268,7 +271,7 @@ namespace Barotrauma
Inventory itemInventory = targetItem.ParentInventory;
var slots = itemInventory?.FindIndices(targetItem);
if (HumanAIController.TakeItem(targetItem, character.Inventory, Equip, Wear, storeUnequipped: true))
if (HumanAIController.TakeItem(targetItem, character.Inventory, Equip, Wear, storeUnequipped: true, targetTags: IdentifiersOrTags))
{
if (TakeWholeStack && slots != null)
{
@@ -298,8 +301,11 @@ namespace Barotrauma
if (!Equip)
{
// Try equipping and wearing the item
Wear = true;
Equip = true;
if (!objectiveManager.HasActiveObjective<AIObjectiveCleanupItem>() && !objectiveManager.HasActiveObjective<AIObjectiveLoadItem>())
{
Wear = true;
}
return;
}
#if DEBUG
@@ -342,6 +348,10 @@ namespace Barotrauma
}
}
private Stopwatch sw;
private Stopwatch StopWatch => sw ??= new Stopwatch();
private readonly List<(Item item, float priority)> itemCandidates = new List<(Item, float)>();
private List<Item> itemList;
private void FindTargetItem()
{
if (IdentifiersOrTags == null)
@@ -349,13 +359,16 @@ namespace Barotrauma
if (targetItem == null)
{
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot find an item, because neither identifiers nor item was defined.", Color.Red);
DebugConsole.AddWarning($"{character.Name}: Cannot find an item, because neither identifiers nor item was defined.");
#endif
Abandon = true;
}
return;
}
if (HumanAIController.DebugAI)
{
StopWatch.Restart();
}
float priority = Math.Clamp(objectiveManager.GetCurrentPriority(), 10, 100);
if (!CheckPathForEachItem)
{
@@ -366,12 +379,22 @@ namespace Barotrauma
CheckPathForEachItem = priority >= AIObjectiveManager.LowestOrderPriority && (objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>() || objectiveManager.CurrentOrder is AIObjectiveGoTo gotoOrder && gotoOrder.IsFollowOrderObjective);
}
bool checkPath = CheckPathForEachItem;
bool hasCalledPathFinder = false;
int itemsPerFrame = (int)priority;
for (int i = 0; i < itemsPerFrame && currSearchIndex < Item.ItemList.Count - 1; i++)
// Reset if the character has switched subs.
if (itemList != null && !character.Submarine.IsEntityFoundOnThisSub(itemList.FirstOrDefault(), includingConnectedSubs: true))
{
currSearchIndex++;
var item = Item.ItemList[currSearchIndex];
currentSearchIndex = 0;
}
if (currentSearchIndex == 0)
{
itemCandidates.Clear();
itemList = character.Submarine.GetItems(alsoFromConnectedSubs: true);
}
int itemsPerFrame = (int)MathHelper.Lerp(30, 300, MathUtils.InverseLerp(10, 100, priority));
int checkedItems = 0;
for (int i = 0; i < itemsPerFrame && currentSearchIndex < itemList.Count; i++, currentSearchIndex++)
{
checkedItems++;
var item = itemList[currentSearchIndex];
Submarine itemSub = item.Submarine ?? item.ParentInventory?.Owner?.Submarine;
if (itemSub == null) { continue; }
Submarine mySub = character.Submarine;
@@ -395,8 +418,6 @@ namespace Barotrauma
if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; }
}
}
// Don't allow going into another sub, unless it's connected and of the same team and type.
if (!character.Submarine.IsEntityFoundOnThisSub(item, includingConnectedSubs: true)) { continue; }
if (character.IsItemTakenBySomeoneElse(item)) { continue; }
if (item.ParentInventory is ItemInventory itemInventory)
{
@@ -411,11 +432,14 @@ namespace Barotrauma
if (rootInventoryOwner is Item ownerItem)
{
if (!ownerItem.IsInteractable(character)) { continue; }
if (!(ownerItem.GetComponent<ItemContainer>()?.HasRequiredItems(character, addMessage: false) ?? true)) { continue; }
//the item is inside an item inside an item (e.g. fuel tank in a welding tool in a cabinet -> reduce priority to prefer items that aren't inside a tool)
if (ownerItem != item.Container)
if (ownerItem != item)
{
itemPriority *= 0.1f;
if (!(ownerItem.GetComponent<ItemContainer>()?.HasRequiredItems(character, addMessage: false) ?? true)) { continue; }
//the item is inside an item inside an item (e.g. fuel tank in a welding tool in a cabinet -> reduce priority to prefer items that aren't inside a tool)
if (ownerItem != item.Container)
{
itemPriority *= 0.1f;
}
}
}
Vector2 itemPos = (rootInventoryOwner ?? item).WorldPosition;
@@ -463,22 +487,69 @@ namespace Barotrauma
{
itemPriority *= item.Condition / item.MaxCondition;
}
if (checkPath)
{
itemCandidates.Add((item, itemPriority));
}
// Ignore if the item has a lower priority than the currently selected one
if (itemPriority < currItemPriority) { continue; }
if (!hasCalledPathFinder && PathSteering != null && checkPath)
if (EvaluateCombatPriority && itemPriority <= 0)
{
hasCalledPathFinder = true;
var path = PathSteering.PathFinder.FindPath(character.SimPosition, item.SimPosition, character.Submarine, errorMsgStr: $"AIObjectiveGetItem {character.DisplayName}", nodeFilter: node => node.Waypoint.CurrentHull != null);
if (path.Unreachable) { continue; }
// Not good enough
continue;
}
currItemPriority = itemPriority;
targetItem = item;
moveToTarget = rootInventoryOwner ?? item;
}
if (currSearchIndex >= Item.ItemList.Count - 1)
if (currentSearchIndex >= itemList.Count - 1)
{
isDoneSeeking = true;
if (targetItem == null)
}
if (checkedItems > 0)
{
if (isDoneSeeking && itemCandidates.Any())
{
itemCandidates.Sort((x, y) => y.priority.CompareTo(x.priority));
}
if (HumanAIController.DebugAI && targetItem != null && StopWatch.ElapsedMilliseconds > 2)
{
var msg = $"Went through {checkedItems} of total {itemList.Count} items. Found item {targetItem.Name} in {StopWatch.ElapsedMilliseconds} ms. Completed: {isDoneSeeking}";
if (StopWatch.ElapsedMilliseconds > 5)
{
DebugConsole.ThrowError(msg);
}
else
{
// An occasional warning now and then can be ignored, but multiple at the same time might indicate a performance issue.
DebugConsole.AddWarning(msg);
}
}
}
if (isDoneSeeking)
{
if (PathSteering == null)
{
itemCandidates.Clear();
}
if (itemCandidates.Any())
{
if (itemCandidates.FirstOrDefault() is { } itemCandidate)
{
var path = PathSteering.PathFinder.FindPath(character.SimPosition, itemCandidate.item.SimPosition, character.Submarine, errorMsgStr: $"AIObjectiveGetItem {character.DisplayName}", nodeFilter: node => node.Waypoint.CurrentHull != null);
if (path.Unreachable)
{
// Remove the invalid candidates and continue on the next frame.
itemCandidates.Remove(itemCandidate);
}
else
{
// The path was valid -> we are done.
itemCandidates.Clear();
}
}
}
if (targetItem == null && itemCandidates.None())
{
if (spawnItemIfNotFound)
{
@@ -569,11 +640,11 @@ namespace Barotrauma
{
if (!item.HasAccess(character)) { return false; }
if (ignoredItems.Contains(item)) { return false; };
if (ignoredIdentifiersOrTags != null && CheckItemIdentifiersOrTags(item, ignoredIdentifiersOrTags)) { return false; }
if (ignoredIdentifiersOrTags != null && item.HasIdentifierOrTags(ignoredIdentifiersOrTags)) { return false; }
if (item.Condition < TargetCondition) { return false; }
if (ItemFilter != null && !ItemFilter(item)) { return false; }
if (RequireNonEmpty && item.Components.Any(i => !i.IsNotEmpty(character))) { return false; }
return CheckItemIdentifiersOrTags(item, IdentifiersOrTags) || (AllowVariants && !item.Prefab.VariantOf.IsEmpty && IdentifiersOrTags.Contains(item.Prefab.VariantOf));
if (RequireNonEmpty && item.Components.Any(i => i.IsEmpty(character))) { return false; }
return item.HasIdentifierOrTags(IdentifiersOrTags) || (AllowVariants && !item.Prefab.VariantOf.IsEmpty && IdentifiersOrTags.Contains(item.Prefab.VariantOf));
}
public override void Reset()
@@ -591,7 +662,7 @@ namespace Barotrauma
targetItem = originalTarget;
moveToTarget = targetItem?.GetRootInventoryOwner();
isDoneSeeking = false;
currSearchIndex = 0;
currentSearchIndex = 0;
currItemPriority = 0;
}
@@ -257,7 +257,7 @@ namespace Barotrauma
}
}
}
else if (HumanAIController.HasValidPath(requireNonDirty: true, requireUnfinished: false))
else if (HumanAIController.HasValidPath(requireUnfinished: false))
{
waitUntilPathUnreachable = pathWaitingTime;
}
@@ -364,7 +364,8 @@ namespace Barotrauma
else
{
bool isRuins = character.Submarine?.Info.IsRuin != null || Target.Submarine?.Info.IsRuin != null;
if (!isRuins || !HumanAIController.HasValidPath(requireNonDirty: true, requireUnfinished: true))
bool isEitherOneInside = isInside || Target.Submarine != null;
if (isEitherOneInside && (!isRuins || !HumanAIController.HasValidPath()))
{
SeekGaps(maxGapDistance);
seekGapsTimer = seekGapsInterval * Rand.Range(0.1f, 1.1f);
@@ -388,6 +389,10 @@ namespace Barotrauma
}
}
}
else
{
TargetGap = null;
}
}
}
else
@@ -436,9 +441,9 @@ namespace Barotrauma
{
var leftHandItem = character.GetEquippedItem(slotType: InvSlotType.LeftHand);
var rightHandItem = character.GetEquippedItem(slotType: InvSlotType.RightHand);
bool handsFull =
(leftHandItem != null && character.Inventory.CheckIfAnySlotAvailable(leftHandItem, inWrongSlot: false) == -1) ||
(rightHandItem != null && character.Inventory.CheckIfAnySlotAvailable(rightHandItem, inWrongSlot: false) == -1);
bool handsFull =
(leftHandItem != null && !character.Inventory.IsAnySlotAvailable(leftHandItem)) ||
(rightHandItem != null && !character.Inventory.IsAnySlotAvailable(rightHandItem));
if (!handsFull)
{
bool hasBattery = false;
@@ -473,7 +478,7 @@ namespace Barotrauma
// 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));
scooter.ContainedItems.ForEachMod(emptyBattery => character.Inventory.TryPutItem(emptyBattery, character, CharacterInventory.AnySlot));
if (!scooter.Combine(batteries.OrderByDescending(b => b.Condition).First(), character))
{
useScooter = false;
@@ -488,7 +493,7 @@ namespace Barotrauma
if (!useScooter)
{
// Unequip
character.Inventory.TryPutItem(scooter, character, CharacterInventory.anySlot);
character.Inventory.TryPutItem(scooter, character, CharacterInventory.AnySlot);
}
}
}
@@ -511,13 +516,20 @@ namespace Barotrauma
{
nodeFilter = n => n.Waypoint.CurrentHull != null;
}
else if (!isInside && HumanAIController.UseIndoorSteeringOutside)
else if (!isInside)
{
nodeFilter = n => n.Waypoint.Submarine == null;
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)
{
@@ -540,6 +552,7 @@ namespace Barotrauma
}
else
{
character.ReleaseSecondaryItem();
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(Target.WorldPosition - character.WorldPosition));
if (character.AnimController.InWater)
{
@@ -560,6 +573,7 @@ namespace Barotrauma
}
else
{
character.ReleaseSecondaryItem();
SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Target), 10);
if (character.AnimController.InWater)
{
@@ -573,6 +587,7 @@ namespace Barotrauma
{
if (!character.HasEquippedItem("scooter".ToIdentifier())) { return; }
SteeringManager.Reset();
character.ReleaseSecondaryItem();
character.CursorPosition = targetWorldPos;
if (character.Submarine != null)
{
@@ -624,7 +639,7 @@ namespace Barotrauma
}
else if (target is Character c)
{
return c.CurrentHull;
return c.CurrentHull ?? c.AnimController.CurrentHull;
}
else if (target is Structure structure)
{
@@ -772,6 +787,14 @@ namespace Barotrauma
{
StopMovement();
HumanAIController.FaceTarget(Target);
if (Target is WayPoint { Ladders: null })
{
// 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();
}
base.OnCompleted();
}
@@ -781,6 +804,10 @@ namespace Barotrauma
findDivingGear = null;
seekGapsTimer = 0;
TargetGap = null;
if (SteeringManager is IndoorsSteeringManager pathSteering)
{
pathSteering.ResetPath();
}
}
}
}
@@ -163,15 +163,22 @@ namespace Barotrauma
character.SelectedItem = null;
CleanupItems(deltaTime);
if (!character.IsClimbing)
{
CleanupItems(deltaTime);
}
if (behavior == BehaviorType.StayInHull && TargetHull == null && character.CurrentHull != null)
{
TargetHull = character.CurrentHull;
}
bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) || (PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)));
if (behavior == BehaviorType.StayInHull && !currentTargetIsInvalid)
bool currentTargetIsInvalid =
currentTarget == null ||
IsForbidden(currentTarget) ||
(PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)));
if (behavior == BehaviorType.StayInHull && !currentTargetIsInvalid && !HumanAIController.UnsafeHulls.Contains(TargetHull))
{
currentTarget = TargetHull;
bool stayInHull = character.CurrentHull == currentTarget && IsSteeringFinished() && !character.IsClimbing;
@@ -305,12 +312,8 @@ namespace Barotrauma
{
if (character.IsClimbing)
{
if (character.AnimController.GetHeightFromFloor() < 0.1f)
{
character.AnimController.Anim = AnimController.Animation.None;
character.SelectedSecondaryItem = null;
}
return;
PathSteering.Reset();
character.StopClimbing();
}
var currentHull = character.CurrentHull;
if (!character.AnimController.InWater && currentHull != null)
@@ -362,6 +365,7 @@ namespace Barotrauma
}
else
{
character.ReleaseSecondaryItem();
PathSteering.SteeringManual(deltaTime, Vector2.Normalize(diff));
}
return;
@@ -24,7 +24,7 @@ namespace Barotrauma
private ImmutableHashSet<Identifier> ValidContainableItemIdentifiers { get; }
private static Dictionary<ItemPrefab, ImmutableHashSet<Identifier>> AllValidContainableItemIdentifiers { get; } = new Dictionary<ItemPrefab, ImmutableHashSet<Identifier>>();
private int itemIndex = 0;
private int itemIndex;
private AIObjectiveDecontainItem decontainObjective;
private readonly HashSet<Item> ignoredItems = new HashSet<Item>();
private Item targetItem;
@@ -219,9 +219,8 @@ namespace Barotrauma
return Priority;
}
public override void Update(float deltaTime)
protected override void Act(float deltaTime)
{
base.Update(deltaTime);
if (targetItem == null)
{
if (character.FindItem(ref itemIndex, out Item item, identifiers: ValidContainableItemIdentifiers, ignoreBroken: false, customPredicate: IsValidContainable, customPriorityFunction: GetPriority))
@@ -233,6 +232,7 @@ namespace Barotrauma
}
targetItem = item;
}
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
float GetPriority(Item item)
{
try
@@ -256,11 +256,7 @@ namespace Barotrauma
}
}
}
}
protected override void Act(float deltaTime)
{
if (targetItem != null)
else
{
if(decontainObjective == null && !IsValidContainable(targetItem))
{
@@ -290,10 +286,6 @@ namespace Barotrauma
Reset();
});
}
else
{
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
}
}
private bool IsValidContainable(Item item)
@@ -310,7 +302,7 @@ namespace Barotrauma
if (parentItem.HasTag("donttakeitems")) { return false; }
}
if (!item.HasAccess(character)) { return false; }
if (!character.HasItem(item) && !CanEquip(item)) { return false; }
if (!character.HasItem(item) && !CanEquip(item, allowWearing: false)) { return false; }
if (!ItemContainer.CanBeContained(item)) { return false; }
if (AIObjectiveLoadItems.ItemMatchesTargetCondition(item, TargetItemCondition)) { return false; }
if (TargetItemCondition == AIObjectiveLoadItems.ItemCondition.Full)
@@ -63,7 +63,7 @@ namespace Barotrauma
{
if (item == null || item.Removed) { return false; }
if (targetContainerTags.HasValue && !OrderPrefab.TargetItemsMatchItem(targetContainerTags.Value, item)) { return false; }
if (!(item.GetComponent<ItemContainer>() is ItemContainer container)) { return false; }
if ((item.GetComponent<ItemContainer>() is not ItemContainer container)) { return false; }
if (container.Inventory == null) { return false; }
if (targetCondition.HasValue && container.Inventory.IsFull() && container.Inventory.AllItems.None(i => ItemMatchesTargetCondition(i, targetCondition.Value))) { return false; }
if (!AIObjectiveCleanupItems.IsItemInsideValidSubmarine(item, character)) { return false; }
@@ -89,12 +89,7 @@ namespace Barotrauma
var target = objective.Key;
if (!Targets.Contains(target))
{
var subObjective = objective.Value;
if (CurrentSubObjective == subObjective)
{
CurrentSubObjective.Abandon = !CurrentSubObjective.IsCompleted;
}
subObjectives.Remove(subObjective);
subObjectives.Remove(objective.Value);
}
}
SyncRemovedObjectives(Objectives, GetList());
@@ -157,6 +152,11 @@ namespace Barotrauma
else
{
float max = AIObjectiveManager.LowestOrderPriority - 1;
if (this is AIObjectiveRescueAll rescueObjective && rescueObjective.Targets.Contains(character))
{
// Allow higher prio
max = AIObjectiveManager.EmergencyObjectivePriority;
}
float value = MathHelper.Clamp((CumulatedDevotion + (targetValue * PriorityModifier)) / 100, 0, 1);
Priority = MathHelper.Lerp(0, max, value);
}
@@ -20,6 +20,8 @@ namespace Barotrauma
MaxValue = 2
}
public const float MaxObjectivePriority = 100;
public const float EmergencyObjectivePriority = 90;
public const float HighestOrderPriority = 70;
public const float LowestOrderPriority = 60;
public const float RunPriority = 50;
@@ -125,11 +127,21 @@ namespace Barotrauma
{
CoroutineManager.StopCoroutines(delayedObjective.Value);
}
var prevIdleObjective = GetObjective<AIObjectiveIdle>();
DelayedObjectives.Clear();
Objectives.Clear();
FailedAutonomousObjectives = false;
AddObjective(new AIObjectiveFindSafety(character, this));
AddObjective(new AIObjectiveIdle(character, this));
var newIdleObjective = new AIObjectiveIdle(character, this);
if (prevIdleObjective != null)
{
newIdleObjective.TargetHull = prevIdleObjective.TargetHull;
newIdleObjective.Behavior = prevIdleObjective.Behavior;
prevIdleObjective.PreferredOutpostModuleTypes.ForEach(t => newIdleObjective.PreferredOutpostModuleTypes.Add(t));
}
AddObjective(newIdleObjective);
int objectiveCount = Objectives.Count;
foreach (var autonomousObjective in character.Info.Job.Prefab.AutonomousObjectives)
{
@@ -155,7 +167,7 @@ namespace Barotrauma
AddObjective(objective, delay: Rand.Value() / 2);
objectiveCount++;
}
}
}
_waitTimer = Math.Max(_waitTimer, Rand.Range(0.5f, 1f) * objectiveCount);
}
@@ -410,7 +422,7 @@ namespace Barotrauma
newObjective = new AIObjectiveGoTo(order.OrderGiver, character, this, repeat: true, priorityModifier: priorityModifier)
{
CloseEnough = Rand.Range(80f, 100f),
CloseEnoughMultiplier = Math.Min(1 + HumanAIController.CountCrew(c => c.ObjectiveManager.HasOrder<AIObjectiveGoTo>(o => o.Target == order.OrderGiver), onlyBots: true) * Rand.Range(0.8f, 1f), 4),
CloseEnoughMultiplier = Math.Min(1 + HumanAIController.CountBotsInTheCrew(c => c.ObjectiveManager.HasOrder<AIObjectiveGoTo>(o => o.Target == order.OrderGiver)) * Rand.Range(0.8f, 1f), 4),
ExtraDistanceOutsideSub = 100,
ExtraDistanceWhileSwimming = 100,
AllowGoingOutside = true,
@@ -633,10 +645,11 @@ namespace Barotrauma
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>
public T GetActiveObjective<T>() where T : AIObjective => CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).LastOrDefault(so => so is T) as T;
public T GetLastActiveObjective<T>() where T : AIObjective
=> CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).LastOrDefault(so => so is T) as T;
public T GetFirstActiveObjective<T>() where T : AIObjective
=> CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).FirstOrDefault(so => so is T) as T;
/// <summary>
/// Returns all active objectives of the specific type. Creates a new collection -> don't use too frequently.
@@ -122,7 +122,7 @@ namespace Barotrauma
else if (!isOrder)
{
var steering = component?.Item.GetComponent<Steering>();
if (steering != null && (steering.AutoPilot || HumanAIController.IsTrueForAnyCrewMember(c => c != HumanAIController && c.Character.IsCaptain)))
if (steering != null && (steering.AutoPilot || HumanAIController.IsTrueForAnyCrewMember(c => c != character && c.IsCaptain, onlyActive: true, onlyConnectedSubs: true)))
{
// Ignore if already set to autopilot or if there's a captain onboard
Priority = 0;
@@ -204,7 +204,7 @@ namespace Barotrauma
}
if (operateTarget != null)
{
if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.Character.IsBot && other.ObjectiveManager.GetActiveObjective() is AIObjectiveOperateItem operateObjective && operateObjective.operateTarget == operateTarget))
if (HumanAIController.IsTrueForAnyBotInTheCrew(other => other != HumanAIController && other.ObjectiveManager.GetActiveObjective() is AIObjectiveOperateItem operateObjective && operateObjective.operateTarget == operateTarget))
{
// Another crew member is already targeting this entity (leak).
Abandon = true;
@@ -69,16 +69,6 @@ namespace Barotrauma
if (subObjective != null && subObjective.IsCompleted)
{
Priority = 0;
items.RemoveWhere(i => i == null || i.Removed || !i.IsOwnedBy(character));
if (items.None())
{
Abandon = true;
}
else if (items.Any(i => i.Components.Any(i => !i.IsNotEmpty(character))))
{
Reset();
}
}
return Priority;
}
@@ -162,25 +152,25 @@ namespace Barotrauma
};
}
if (!TryAddSubObjective(ref getSingleItemObjective, getItemConstructor,
onCompleted: () =>
{
if (KeepActiveWhenReady)
onCompleted: () =>
{
if (getSingleItemObjective != null)
if (KeepActiveWhenReady)
{
var item = getSingleItemObjective?.TargetItem;
if (item?.IsOwnedBy(character) != null)
if (getSingleItemObjective != null)
{
items.Add(item);
var item = getSingleItemObjective?.TargetItem;
if (item?.IsOwnedBy(character) != null)
{
items.Add(item);
}
}
}
}
else
{
IsCompleted = true;
}
},
onAbandon: () => Abandon = true))
else
{
IsCompleted = true;
}
},
onAbandon: () => Abandon = true))
{
Abandon = true;
}
@@ -102,7 +102,7 @@ namespace Barotrauma
// Don't stop fixing until completely done
return 100;
}
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveRepairItems>() && !c.Character.IsIncapacitated, onlyBots: true);
int otherFixers = HumanAIController.CountBotsInTheCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveRepairItems>());
int items = Targets.Count;
if (items == 0)
{
@@ -116,7 +116,7 @@ namespace Barotrauma
}
else
{
if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / (float)HumanAIController.CountCrew(onlyBots: true) > 0.75f))
if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / (float)HumanAIController.CountBotsInTheCrew() > 0.75f))
{
// Enough fixers
return 0;
@@ -139,14 +139,14 @@ namespace Barotrauma
recursive: true);
}
}
if (character.Submarine != null)
if (character.Submarine != null && targetCharacter.CurrentHull != null)
{
if (HumanAIController.GetHullSafety(targetCharacter.CurrentHull, targetCharacter) < HumanAIController.HULL_SAFETY_THRESHOLD)
{
// Incapacitated target is not in a safe place -> Move to a safe place first
if (character.SelectedCharacter != targetCharacter)
{
if (targetCharacter.CurrentHull != null && HumanAIController.VisibleHulls.Contains(targetCharacter.CurrentHull) && targetCharacter.CurrentHull.DisplayName != null)
if (HumanAIController.VisibleHulls.Contains(targetCharacter.CurrentHull) && targetCharacter.CurrentHull.DisplayName != null)
{
character.Speak(TextManager.GetWithVariables("DialogFoundUnconsciousTarget",
("[targetname]", targetCharacter.Name, FormatCapitals.No),
@@ -293,6 +293,11 @@ namespace Barotrauma
currentTreatmentSuitabilities[treatmentSuitability.Key] > bestSuitability)
{
Item matchingItem = character.Inventory.FindItemByIdentifier(treatmentSuitability.Key, true);
//allow taking items from the target's inventory too if the target is unconscious
if (matchingItem == null && targetCharacter.IsIncapacitated)
{
matchingItem ??= targetCharacter.Inventory?.FindItemByIdentifier(treatmentSuitability.Key, true);
}
if (matchingItem != null)
{
bestItem = matchingItem;
@@ -379,24 +384,24 @@ namespace Barotrauma
onAbandon: () =>
{
Abandon = true;
if (character != targetCharacter && character.IsOnPlayerTeam)
if (character.IsOnPlayerTeam)
{
character.Speak(TextManager.GetWithVariable("dialogcannottreatpatient", "[name]", targetCharacter.DisplayName, FormatCapitals.No).Value, identifier: "cannottreatpatient".ToIdentifier(), minDurationBetweenSimilar: 20.0f);
SpeakCannotTreat();
}
});
}
else if (cprSuitability <= 0)
{
character.Speak(TextManager.GetWithVariable("dialogcannottreatpatient", "[name]", targetCharacter.DisplayName, formatCapitals: FormatCapitals.No).Value, identifier: "cannottreatpatient".ToIdentifier(), minDurationBetweenSimilar: 20.0f);
Abandon = true;
SpeakCannotTreat();
}
}
}
else if (!targetCharacter.IsUnconscious)
{
//no suitable treatments found, not inside our own sub (= can't search for more treatments), the target isn't unconscious (= can't give CPR)
character.Speak(TextManager.GetWithVariable("dialogcannottreatpatient", "[name]", targetCharacter.DisplayName, formatCapitals: FormatCapitals.No).Value, identifier: "cannottreatpatient".ToIdentifier(), minDurationBetweenSimilar: 20.0f);
Abandon = true;
//no suitable treatments found, not inside our own sub (= can't search for more treatments), the target isn't unconscious (= can't give CPR)
SpeakCannotTreat();
return;
}
if (character != targetCharacter)
@@ -414,6 +419,14 @@ namespace Barotrauma
}
}
private void SpeakCannotTreat()
{
LocalizedString msg = character == targetCharacter ?
TextManager.Get("dialogcannottreatself") :
TextManager.GetWithVariable("dialogcannottreatpatient", "[name]", targetCharacter.DisplayName, FormatCapitals.No);
character.Speak(msg.Value, identifier: "cannottreatpatient".ToIdentifier(), minDurationBetweenSimilar: 20.0f);
}
private void ApplyTreatment(Affliction affliction, Item item)
{
item.ApplyTreatment(character, targetCharacter, targetCharacter.CharacterHealth.GetAfflictionLimb(affliction));
@@ -433,50 +446,36 @@ namespace Barotrauma
protected override float GetPriority()
{
if (!IsAllowed)
if (!IsAllowed || targetCharacter == null)
{
Priority = 0;
Abandon = true;
return Priority;
}
if (character.CurrentHull == null)
if (character.CurrentHull != null)
{
if (!objectiveManager.HasOrder<AIObjectiveRescueAll>())
if (Character.CharacterList.Any(c => c.CurrentHull == targetCharacter.CurrentHull && !HumanAIController.IsFriendly(character, c) && HumanAIController.IsActive(c)))
{
// Don't go into rooms that have enemies
Priority = 0;
Abandon = true;
return Priority;
}
}
else if (Character.CharacterList.Any(c => c.CurrentHull == targetCharacter.CurrentHull && !HumanAIController.IsFriendly(character, c) && HumanAIController.IsActive(c)))
float horizontalDistance = Math.Abs(character.WorldPosition.X - targetCharacter.WorldPosition.X);
float verticalDistance = Math.Abs(character.WorldPosition.Y - targetCharacter.WorldPosition.Y);
if (character.Submarine?.Info is { IsRuin: false })
{
// Don't go into rooms that have enemies
Priority = 0;
Abandon = true;
return Priority;
verticalDistance *= 2;
}
if (targetCharacter == null)
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, horizontalDistance + verticalDistance));
if (character.CurrentHull != null && targetCharacter.CurrentHull == character.CurrentHull)
{
Priority = 0;
Abandon = true;
}
else
{
float horizontalDistance = Math.Abs(character.WorldPosition.X - targetCharacter.WorldPosition.X);
float verticalDistance = Math.Abs(character.WorldPosition.Y - targetCharacter.WorldPosition.Y);
if (character.Submarine?.Info is { IsRuin: false })
{
verticalDistance *= 2;
}
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, horizontalDistance + verticalDistance));
if (targetCharacter.CurrentHull == character.CurrentHull)
{
distanceFactor = 1;
}
float vitalityFactor = 1 - AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) / 100;
float devotion = CumulatedDevotion / 100;
Priority = MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + (vitalityFactor * distanceFactor * PriorityModifier), 0, 1));
distanceFactor = 1;
}
float vitalityFactor = 1 - AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) / 100;
float devotion = CumulatedDevotion / 100;
Priority = MathHelper.Lerp(0, AIObjectiveManager.EmergencyObjectivePriority, MathHelper.Clamp(devotion + (vitalityFactor * distanceFactor * PriorityModifier), 0, 1));
return Priority;
}
@@ -42,15 +42,16 @@ namespace Barotrauma
if (Targets.None()) { return 100; }
if (!objectiveManager.IsOrder(this))
{
if (!character.IsMedic && HumanAIController.IsTrueForAnyCrewMember(c => c != HumanAIController && c.Character.IsMedic && !c.Character.IsUnconscious))
if (!character.IsMedic && HumanAIController.IsTrueForAnyCrewMember(c => c != character && c.IsMedic, onlyActive: true, onlyConnectedSubs: true))
{
// Don't do anything if there's a medic on board and we are not a medic
// Don't do anything if there's a medic on board actively treating and we are not a medic
return 100;
}
}
float worstCondition = Targets.Min(t => GetVitalityFactor(t));
if (Targets.Contains(character))
{
// Targeting self -> higher prio
if (character.Bleeding > 10)
{
// Enforce the highest priority when bleeding out.
@@ -117,7 +118,7 @@ namespace Barotrauma
{
if (!character.IsMedic && target != character)
{
// Don't allow to treat others autonomously
// Don't allow to treat others autonomously, unless we are a medic
return false;
}
// Ignore unsafe hulls, unless ordered
@@ -136,17 +137,17 @@ namespace Barotrauma
// Don't allow going into another sub, unless it's connected and of the same team and type.
if (!character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, includingConnectedSubs: true)) { return false; }
}
else
else if (target.Submarine != null)
{
return target.Submarine == null;
// We are outside, but the target is inside.
return false;
}
if (target != character && target.IsBot && HumanAIController.IsActive(target) && target.AIController is HumanAIController targetAI)
{
// Ignore all concious targets that are currently fighting, fleeing, fixing, or treating characters
// Ignore all concious targets that are currently fighting, fleeing, or treating characters
if (targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveCombat>() ||
targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveFindSafety>() ||
targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveRescue>() ||
targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveFixLeak>())
targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveRescue>())
{
return false;
}