Unstable v0.15.17.0 (Hex is out of town edition)

This commit is contained in:
Juan Pablo Arce
2021-12-03 13:31:10 -03:00
parent cd5c8f3e13
commit 617d9ede88
245 changed files with 8088 additions and 5842 deletions
@@ -283,9 +283,9 @@ namespace Barotrauma
/// </summary>
public float CalculatePriority()
{
ForceWalk = false;
Priority = GetPriority();
ForceHighestPriority = false;
ForceWalk = false;
return Priority;
}
@@ -501,5 +501,34 @@ namespace Barotrauma
}
}
}
protected static bool CanEquip(Character character, Item item)
{
bool canEquip = item != null;
if (canEquip && !item.AllowedSlots.Contains(InvSlotType.Any))
{
canEquip = false;
var inv = character.Inventory;
foreach (var allowedSlot in item.AllowedSlots)
{
foreach (var slotType in inv.SlotTypes)
{
if (!allowedSlot.HasFlag(slotType)) { continue; }
for (int i = 0; i < inv.Capacity; i++)
{
canEquip = true;
if (allowedSlot.HasFlag(inv.SlotTypes[i]) && inv.GetItemAt(i) != null)
{
canEquip = false;
break;
}
}
}
}
}
return canEquip;
}
protected bool CanEquip(Item item) => CanEquip(character, item);
}
}
@@ -68,15 +68,12 @@ namespace Barotrauma
protected override void OnObjectiveCompleted(AIObjective objective, Item target)
=> HumanAIController.RemoveTargets<AIObjectiveCleanupItems, Item>(character, target);
private static bool IsItemInsideValidSubmarine(Item item, Character character)
public static bool IsItemInsideValidSubmarine(Item item, Character character)
{
if (item.CurrentHull == null) { return false; }
if (item.Submarine == null) { return false; }
if (item.Submarine.TeamID != character.TeamID) { return false; }
if (character.Submarine != null)
{
if (!character.Submarine.IsConnectedTo(item.Submarine)) { return false; }
}
if (character.Submarine != null && !character.Submarine.IsConnectedTo(item.Submarine)) { return false; }
return true;
}
@@ -129,29 +126,7 @@ namespace Barotrauma
{
return true;
}
bool canEquip = true;
if (!item.AllowedSlots.Contains(InvSlotType.Any))
{
canEquip = false;
var inv = character.Inventory;
foreach (var allowedSlot in item.AllowedSlots)
{
foreach (var slotType in inv.SlotTypes)
{
if (!allowedSlot.HasFlag(slotType)) { continue; }
for (int i = 0; i < inv.Capacity; i++)
{
canEquip = true;
if (allowedSlot.HasFlag(inv.SlotTypes[i]) && inv.GetItemAt(i) != null)
{
canEquip = false;
break;
}
}
}
}
}
return canEquip;
return CanEquip(character, item);
}
public override void OnDeselected()
@@ -111,7 +111,7 @@ namespace Barotrauma
public CombatMode Mode { get; private set; }
private bool IsOffensiveOrArrest => initialMode == CombatMode.Offensive || initialMode == CombatMode.Arrest;
private bool TargetEliminated => IsEnemyDisabled || (Enemy.IsUnconscious && Enemy.Params.Health.ConstantHealthRegeneration <= 0.0f);
private bool TargetEliminated => IsEnemyDisabled || Enemy.IsUnconscious && Enemy.Params.Health.ConstantHealthRegeneration <= 0.0f || Enemy.IsHuman && Enemy.HasEquippedItem("handlocker") && !character.IsInstigator;
private bool IsEnemyDisabled => Enemy == null || Enemy.Removed || Enemy.IsDead;
private float AimSpeed => HumanAIController.AimSpeed;
@@ -158,7 +158,7 @@ namespace Barotrauma
return Priority;
}
}
float damageFactor = MathUtils.InverseLerp(0.0f, 5.0f, HumanAIController.GetDamageDoneByAttacker(Enemy) / 100.0f);
float damageFactor = MathUtils.InverseLerp(0.0f, 5.0f, character.GetDamageDoneByAttacker(Enemy) / 100.0f);
Priority = TargetEliminated ? 0 : Math.Min((95 + damageFactor) * PriorityModifier, 100);
return Priority;
}
@@ -177,11 +177,12 @@ namespace Barotrauma
ignoredWeapons.Clear();
ignoreWeaponTimer = ignoredWeaponsClearTime;
}
if (findSafety != null)
bool isCurrentObjective = objectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>();
if (findSafety != null && isCurrentObjective)
{
findSafety.Priority = 0;
}
if (!AllowCoolDown && !character.IsOnPlayerTeam && !objectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>())
if (!AllowCoolDown && !character.IsOnPlayerTeam && !isCurrentObjective)
{
distanceTimer -= deltaTime;
if (distanceTimer < 0)
@@ -204,13 +205,18 @@ namespace Barotrauma
protected override void Act(float deltaTime)
{
if (IsEnemyDisabled)
{
IsCompleted = true;
return;
}
if (AllowCoolDown)
{
coolDownTimer -= deltaTime;
}
if (seekAmmunitionObjective == null && seekWeaponObjective == null)
{
if (Mode != CombatMode.Retreat && TryArm() && !IsEnemyDisabled)
if (Mode != CombatMode.Retreat && TryArm())
{
OperateWeapon(deltaTime);
}
@@ -283,10 +289,12 @@ namespace Barotrauma
}
else
{
AskHelp();
Retreat(deltaTime);
}
break;
case CombatMode.Retreat:
AskHelp();
Retreat(deltaTime);
break;
default:
@@ -363,6 +371,7 @@ namespace Barotrauma
{
if (WeaponComponent == null)
{
SpeakNoWeapons();
Mode = CombatMode.Retreat;
}
}
@@ -409,6 +418,7 @@ namespace Barotrauma
onCompleted: () => RemoveSubObjective(ref seekWeaponObjective),
onAbandon: () =>
{
SpeakNoWeapons();
RemoveSubObjective(ref seekWeaponObjective);
Mode = CombatMode.Retreat;
});
@@ -680,6 +690,7 @@ namespace Barotrauma
}
else
{
SpeakNoWeapons();
Weapon = null;
Mode = CombatMode.Retreat;
return false;
@@ -892,7 +903,7 @@ namespace Barotrauma
TryAddSubObjective(ref seekAmmunitionObjective,
constructor: () => new AIObjectiveContainItem(character, ammunitionIdentifiers, Weapon.GetComponent<ItemContainer>(), objectiveManager)
{
targetItemCount = Weapon.GetComponent<ItemContainer>().Capacity,
ItemCount = Weapon.GetComponent<ItemContainer>().Capacity,
checkInventory = false
},
onCompleted: () => RemoveSubObjective(ref seekAmmunitionObjective),
@@ -1099,7 +1110,7 @@ namespace Barotrauma
if (WeaponComponent is RangedWeapon rangedWeapon)
{
// If the weapon is just equipped, we can't shoot just yet.
if (rangedWeapon.ReloadTimer <= 0)
if (rangedWeapon.ReloadTimer <= 0 && !rangedWeapon.HoldTrigger)
{
reloadTime = rangedWeapon.Reload;
}
@@ -1155,6 +1166,21 @@ namespace Barotrauma
retreatTarget = null;
}
private void SpeakNoWeapons() => Speak("dialogcombatnoweapons", delay: 0, minDuration: 30);
private void AskHelp() => Speak("dialogcombatretreating", delay: Rand.Range(0, 1), minDuration: 20);
private void Speak(string textIdentifier, float delay, float minDuration)
{
if (character.IsOnPlayerTeam && !character.IsInFriendlySub)
{
string msg = TextManager.Get(textIdentifier, true);
if (msg != null)
{
character.Speak(msg, identifier: textIdentifier, delay: delay, minDurationBetweenSimilar: minDuration);
}
}
}
//private float CalculateEnemyStrength()
//{
// float enemyStrength = 0;
@@ -11,12 +11,11 @@ namespace Barotrauma
public Func<Item, float> GetItemPriority;
public int targetItemCount = 1;
public string[] ignoredContainerIdentifiers;
public bool checkInventory = true;
//if the item can't be found, spawn it in the character's inventory (used by outpost NPCs)
private bool spawnItemIfNotFound = false;
//if the item can't be found, spawn it in the character's inventory (used by outpost NPCs and in some cases also enemy NPCs, like pirates)
private readonly bool spawnItemIfNotFound;
//can either be a tag or an identifier
public readonly string[] itemIdentifiers;
@@ -35,9 +34,24 @@ namespace Barotrauma
public bool Equip { get; set; }
public bool RemoveEmpty { get; set; } = true;
public bool RemoveExisting { get; set; }
/// <summary>
/// Only remove existing items when the contain target can't be put in the inventory
/// </summary>
public bool RemoveExistingWhenNecessary { get; set; }
public Func<Item, bool> RemoveExistingPredicate { get; set; }
public int? RemoveMax { get; set; }
public bool MoveWholeStack { get; set; }
private int _itemCount = 1;
public int ItemCount
{
get { return _itemCount; }
set
{
_itemCount = Math.Max(value, 1);
}
}
public AIObjectiveContainItem(Character character, Item item, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier)
@@ -83,7 +97,7 @@ namespace Barotrauma
containedItemCount++;
}
}
return containedItemCount >= targetItemCount;
return containedItemCount >= ItemCount;
}
}
@@ -106,9 +120,9 @@ namespace Barotrauma
}
if (character.CanInteractWith(container.Item, checkLinked: false))
{
if (RemoveExisting)
if (RemoveExisting || (RemoveExistingWhenNecessary && !container.Inventory.CanBePut(item)))
{
HumanAIController.UnequipContainedItems(container.Item);
HumanAIController.UnequipContainedItems(container.Item, predicate: RemoveExistingPredicate, unequipMax: RemoveMax);
}
else if (RemoveEmpty)
{
@@ -127,7 +141,6 @@ namespace Barotrauma
container.Inventory.TryPutItem(item, null);
}
}
IsCompleted = true;
}
}
@@ -36,6 +36,11 @@ namespace Barotrauma
/// </summary>
public bool DropIfFails { get; set; } = true;
public bool RemoveExistingWhenNecessary { get; set; }
public Func<Item, bool> RemoveExistingPredicate { get; set; }
public int? RemoveExistingMax { get; set; }
public string AbandonGetItemDialogueIdentifier { get; set; }
public AIObjectiveDecontainItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, ItemContainer sourceContainer = null, ItemContainer targetContainer = null, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier)
{
@@ -86,6 +91,7 @@ namespace Barotrauma
}
if (itemToDecontain.Container != sourceContainer.Item)
{
itemToDecontain.Drop(character);
IsCompleted = true;
return;
}
@@ -98,7 +104,12 @@ namespace Barotrauma
if (getItemObjective == null && !itemToDecontain.IsOwnedBy(character))
{
TryAddSubObjective(ref getItemObjective,
constructor: () => new AIObjectiveGetItem(character, targetItem, objectiveManager, Equip) { TakeWholeStack = this.TakeWholeStack },
constructor: () => new AIObjectiveGetItem(character, targetItem, objectiveManager, Equip)
{
CannotFindDialogueIdentifierOverride = AbandonGetItemDialogueIdentifier,
SpeakIfFails = AbandonGetItemDialogueIdentifier != null,
TakeWholeStack = this.TakeWholeStack
},
onAbandon: () => Abandon = true);
return;
}
@@ -110,6 +121,9 @@ namespace Barotrauma
MoveWholeStack = TakeWholeStack,
Equip = Equip,
RemoveEmpty = false,
RemoveExistingWhenNecessary = RemoveExistingWhenNecessary,
RemoveExistingPredicate = RemoveExistingPredicate,
RemoveMax = RemoveExistingMax,
GetItemPriority = GetItemPriority,
ignoredContainerIdentifiers = sourceContainer != null ? new string[] { sourceContainer.Item.Prefab.Identifier } : null
},
@@ -31,7 +31,7 @@ namespace Barotrauma
protected override AIObjective ObjectiveConstructor(Character target)
{
AIObjectiveCombat.CombatMode combatMode = target.IsEscorted && character.TeamID == CharacterTeamType.Team1 ? AIObjectiveCombat.CombatMode.Arrest : AIObjectiveCombat.CombatMode.Offensive;
AIObjectiveCombat.CombatMode combatMode = ShouldArrest(target, character) ? AIObjectiveCombat.CombatMode.Arrest : AIObjectiveCombat.CombatMode.Offensive;
var combatObjective = new AIObjectiveCombat(character, target, combatMode, objectiveManager, PriorityModifier);
if (character.TeamID == CharacterTeamType.FriendlyNPC && target.TeamID == CharacterTeamType.Team1 && GameMain.GameSession?.GameMode is CampaignMode campaign)
{
@@ -41,7 +41,7 @@ namespace Barotrauma
combatObjective.holdFireCondition = () =>
{
//hold fire while the enemy is in the airlock (except if they've attacked us)
if (HumanAIController.GetDamageDoneByAttacker(target) > 0.0f) { return false; }
if (character.GetDamageDoneByAttacker(target) > 0.0f) { return false; }
return target.CurrentHull == null || target.CurrentHull.OutpostModuleTags.Any(t => t.Equals("airlock", System.StringComparison.OrdinalIgnoreCase));
};
character.Speak(TextManager.Get("dialogenteroutpostwarning"), null, Rand.Range(0.5f, 1.0f), "leaveoutpostwarning", 30.0f);
@@ -65,7 +65,13 @@ namespace Barotrauma
if (HumanAIController.IsFriendly(character, target)) { return false; }
if (!character.Submarine.IsConnectedTo(target.Submarine)) { return false; }
if (target.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { return false; }
if (ShouldArrest(target, character) && target.HasEquippedItem("handlocker")) { return false; }
return true;
}
public static bool ShouldArrest(Character target, Character character)
{
return target != null && target.IsEscorted && character.TeamID == CharacterTeamType.Team1;
}
}
}
@@ -52,9 +52,11 @@ namespace Barotrauma
}
else
{
Priority = (objectiveManager.IsCurrentOrder<AIObjectiveGoTo>() ||
objectiveManager.IsCurrentOrder<AIObjectiveReturn>() ||
objectiveManager.Objectives.Any(o => o.Priority > 0 && o is AIObjectiveCombat))
Priority = (
objectiveManager.HasOrder<AIObjectiveGoTo>(o => o.Priority > 0) ||
objectiveManager.HasOrder<AIObjectiveReturn>(o => o.Priority > 0) ||
objectiveManager.HasActiveObjective<AIObjectiveRescue>() ||
objectiveManager.Objectives.Any(o => o is AIObjectiveCombat && o.Priority > 0))
&& HumanAIController.HasDivingSuit(character) ? 0 : 100;
}
}
@@ -21,7 +21,7 @@ namespace Barotrauma
public float TargetCondition { get; set; } = 1;
public bool AllowDangerousPressure { get; set; }
private readonly ImmutableArray<string> identifiersOrTags;
public readonly ImmutableArray<string> IdentifiersOrTags;
//if the item can't be found, spawn it in the character's inventory (used by outpost NPCs)
private bool spawnItemIfNotFound = false;
@@ -58,6 +58,17 @@ namespace Barotrauma
public bool EvaluateCombatPriority { get; set; }
public bool CheckPathForEachItem { get; set; }
public bool SpeakIfFails { get; set; }
public string CannotFindDialogueIdentifierOverride { get; set; }
private int _itemCount = 1;
public int ItemCount
{
get { return _itemCount; }
set
{
_itemCount = Math.Max(value, 1);
}
}
public InvSlotType? EquipSlotType { get; set; }
@@ -81,7 +92,7 @@ namespace Barotrauma
Equip = equip;
this.spawnItemIfNotFound = spawnItemIfNotFound;
this.checkInventory = checkInventory;
this.identifiersOrTags = ParseGearTags(identifiersOrTags).ToImmutableArray();
IdentifiersOrTags = ParseGearTags(identifiersOrTags).ToImmutableArray();
ignoredIdentifiersOrTags = ParseIgnoredTags(identifiersOrTags).ToArray();
}
@@ -113,7 +124,7 @@ namespace Barotrauma
private bool CheckInventory()
{
if (identifiersOrTags == null) { return false; }
if (IdentifiersOrTags == null) { return false; }
var item = character.Inventory.FindItem(i => CheckItem(i), recursive: true);
if (item != null)
{
@@ -123,6 +134,19 @@ namespace Barotrauma
return item != null;
}
private bool CountItems()
{
int itemCount = 0;
foreach (Item it in character.Inventory.AllItems)
{
if (CheckItem(it))
{
itemCount++;
}
}
return itemCount >= ItemCount;
}
protected override void Act(float deltaTime)
{
if (character.LockHands)
@@ -135,7 +159,7 @@ namespace Barotrauma
Abandon = true;
return;
}
if (identifiersOrTags != null && !isDoneSeeking)
if (IdentifiersOrTags != null && !isDoneSeeking)
{
if (checkInventory)
{
@@ -152,7 +176,7 @@ namespace Barotrauma
if (dangerousPressure)
{
#if DEBUG
string itemName = targetItem != null ? targetItem.Name : identifiersOrTags.FirstOrDefault();
string itemName = targetItem != null ? targetItem.Name : IdentifiersOrTags.FirstOrDefault();
DebugConsole.NewMessage($"{character.Name}: Seeking item ({itemName}) aborted, because the pressure is dangerous.", Color.Yellow);
#endif
Abandon = true;
@@ -245,7 +269,18 @@ namespace Barotrauma
}
}
}
IsCompleted = true;
if (IdentifiersOrTags == null)
{
IsCompleted = true;
}
else
{
IsCompleted = CountItems();
if (!IsCompleted)
{
ResetInternal();
}
}
}
else
{
@@ -297,7 +332,7 @@ namespace Barotrauma
private void FindTargetItem()
{
if (identifiersOrTags == null)
if (IdentifiersOrTags == null)
{
if (targetItem == null)
{
@@ -316,7 +351,7 @@ namespace Barotrauma
// Otherwise it will take some time for us to find a valid item when there are multiple items that we can't reach and some that we can.
// This is relatively expensive, so let's do this only when it significantly improves the behavior.
// Only allow one path find call per frame.
CheckPathForEachItem = priority >= AIObjectiveManager.LowestOrderPriority && (objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>() || objectiveManager.CurrentOrder is AIObjectiveGoTo gotoOrder && gotoOrder.isFollowOrderObjective);
CheckPathForEachItem = priority >= AIObjectiveManager.LowestOrderPriority && (objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>() || objectiveManager.CurrentOrder is AIObjectiveGoTo gotoOrder && gotoOrder.IsFollowOrderObjective);
}
bool checkPath = CheckPathForEachItem;
bool hasCalledPathFinder = false;
@@ -430,10 +465,10 @@ namespace Barotrauma
{
if (spawnItemIfNotFound)
{
if (!(MapEntityPrefab.List.FirstOrDefault(me => me is ItemPrefab ip && identifiersOrTags.Any(id => id == ip.Identifier || ip.Tags.Contains(id))) is ItemPrefab prefab))
if (!(MapEntityPrefab.List.FirstOrDefault(me => me is ItemPrefab ip && IdentifiersOrTags.Any(id => id == ip.Identifier || ip.Tags.Contains(id))) is ItemPrefab prefab))
{
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}, tried to spawn the item but no matching item prefabs were found.", Color.Yellow);
DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", IdentifiersOrTags)}, tried to spawn the item but no matching item prefabs were found.", Color.Yellow);
#endif
Abandon = true;
}
@@ -452,7 +487,7 @@ namespace Barotrauma
else
{
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}", Color.Yellow);
DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", IdentifiersOrTags)}", Color.Yellow);
#endif
Abandon = true;
}
@@ -468,13 +503,20 @@ namespace Barotrauma
// Not yet ready
return false;
}
if (Equip && EquipSlotType.HasValue)
if (IdentifiersOrTags != null && ItemCount > 1)
{
return character.HasEquippedItem(targetItem, EquipSlotType.Value);
return CountItems();
}
else
{
return character.HasItem(targetItem, Equip);
if (Equip && EquipSlotType.HasValue)
{
return character.HasEquippedItem(targetItem, EquipSlotType.Value);
}
else
{
return character.HasItem(targetItem, Equip);
}
}
}
@@ -487,7 +529,7 @@ namespace Barotrauma
if (item.Condition < TargetCondition) { return false; }
if (ItemFilter != null && !ItemFilter(item)) { return false; }
if (RequireLoaded && item.Components.Any(i => !i.IsLoaded(character))) { return false; }
return identifiersOrTags.Any(id => id == item.Prefab.Identifier || item.HasTag(id) || (AllowVariants && item.Prefab.VariantOf?.Identifier == id));
return IdentifiersOrTags.Any(id => id == item.Prefab.Identifier || item.HasTag(id) || (AllowVariants && item.Prefab.VariantOf?.Identifier == id));
}
public override void Reset()
@@ -528,7 +570,7 @@ namespace Barotrauma
{
if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder == objectiveManager.CurrentObjective)
{
string msg = TextManager.Get("dialogcannotfinditem", true);
string msg = TextManager.Get(CannotFindDialogueIdentifierOverride, returnNull: true) ?? TextManager.Get("dialogcannotfinditem", returnNull: true);
if (msg != null)
{
character.Speak(msg, identifier: "dialogcannotfinditem", minDurationBetweenSimilar: 20.0f);
@@ -11,6 +11,7 @@ namespace Barotrauma
public override string Identifier { get; set; } = "get items";
public override string DebugTag => $"{Identifier}";
public override bool KeepDivingGearOn => true;
public override bool AllowMultipleInstances => true;
public bool AllowStealing { get; set; }
public bool TakeWholeStack { get; set; }
@@ -21,6 +22,7 @@ namespace Barotrauma
public bool EvaluateCombatPriority { get; set; }
public bool CheckPathForEachItem { get; set; }
public bool RequireLoaded { get; set; }
public bool RequireAllItems { get; set; }
private readonly ImmutableArray<string> gearTags;
private readonly string[] ignoredTags;
@@ -47,9 +49,11 @@ namespace Barotrauma
{
foreach (string tag in gearTags)
{
if (subObjectives.Any(so => so is AIObjectiveGetItem getItem && getItem.IdentifiersOrTags.Contains(tag))) { continue; }
int count = gearTags.Count(t => t == tag);
AIObjectiveGetItem? getItem = null;
TryAddSubObjective(ref getItem, () =>
new AIObjectiveGetItem(character, tag, objectiveManager, Equip, CheckInventory)
TryAddSubObjective(ref getItem, () =>
new AIObjectiveGetItem(character, tag, objectiveManager, Equip, CheckInventory && count <= 1)
{
AllowVariants = AllowVariants,
Wear = Wear,
@@ -57,7 +61,9 @@ namespace Barotrauma
AllowStealing = AllowStealing,
ignoredIdentifiersOrTags = ignoredTags,
CheckPathForEachItem = CheckPathForEachItem,
RequireLoaded = RequireLoaded
RequireLoaded = RequireLoaded,
ItemCount = count,
SpeakIfFails = RequireAllItems
},
onCompleted: () =>
{
@@ -75,6 +81,10 @@ namespace Barotrauma
achievedItems.Remove(item);
}
RemoveSubObjective(ref getItem);
if (RequireAllItems)
{
Abandon = true;
}
});
}
subObjectivesCreated = true;
@@ -22,16 +22,23 @@ namespace Barotrauma
public Func<bool> requiredCondition;
public Func<PathNode, bool> endNodeFilter;
public Func<float> priorityGetter;
public Func<float> PriorityGetter;
public bool IsFollowOrderObjective;
public bool Mimic;
public bool isFollowOrderObjective;
public bool mimic;
public bool SpeakIfFails { get; set; } = true;
public bool DebugLogWhenFails { get; set; } = true;
public bool UsePathingOutside { get; set; } = true;
public float extraDistanceWhileSwimming;
public float extraDistanceOutsideSub;
public float ExtraDistanceWhileSwimming;
public float ExtraDistanceOutsideSub;
private float _closeEnoughMultiplier = 1;
public float CloseEnoughMultiplier
{
get { return _closeEnoughMultiplier; }
set { _closeEnoughMultiplier = Math.Max(value, 1); }
}
private float _closeEnough = 50;
private readonly float minDistance = 50;
private readonly float seekGapsInterval = 1;
@@ -45,14 +52,15 @@ namespace Barotrauma
{
get
{
float dist = _closeEnough;
float dist = _closeEnough * CloseEnoughMultiplier;
float extraMultiplier = Math.Clamp(CloseEnoughMultiplier * 0.6f, 1, 3);
if (character.AnimController.InWater)
{
dist += extraDistanceWhileSwimming;
dist += ExtraDistanceWhileSwimming * extraMultiplier;
}
if (character.CurrentHull == null)
{
dist += extraDistanceOutsideSub;
dist += ExtraDistanceOutsideSub * extraMultiplier;
}
return dist;
}
@@ -109,9 +117,9 @@ namespace Barotrauma
}
else
{
if (priorityGetter != null)
if (PriorityGetter != null)
{
Priority = priorityGetter();
Priority = PriorityGetter();
}
else if (OverridePriority.HasValue)
{
@@ -177,7 +185,7 @@ namespace Barotrauma
Abandon = true;
return;
}
if (cannotFollow || Target == character || character.SelectedBy != null && HumanAIController.IsFriendly(character.SelectedBy))
if (Target == character || character.SelectedBy != null && HumanAIController.IsFriendly(character.SelectedBy))
{
// Wait
character.AIController.SteeringManager.Reset();
@@ -200,7 +208,7 @@ namespace Barotrauma
}
}
Hull targetHull = GetTargetHull();
if (!isFollowOrderObjective)
if (!IsFollowOrderObjective)
{
// 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
@@ -249,7 +257,7 @@ namespace Barotrauma
Character followTarget = Target as Character;
bool needsDivingSuit = (!isInside || hasOutdoorNodes) && character.NeedsAir && !character.HasAbilityFlag(AbilityFlags.ImmuneToPressure);
bool needsDivingGear = (needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit)) && character.NeedsAir;
if (mimic)
if (Mimic)
{
if (HumanAIController.HasDivingSuit(followTarget) && character.NeedsAir)
{
@@ -277,7 +285,7 @@ namespace Barotrauma
if (findDivingGear != null && !findDivingGear.CanBeCompleted)
{
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: false, objectiveManager),
onAbandon: () => Abort(),
onAbandon: () => Abandon = true,
onCompleted: () =>
{
cannotFollow = false;
@@ -287,7 +295,7 @@ namespace Barotrauma
else
{
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager),
onAbandon: () => Abort(),
onAbandon: () => Abandon = true,
onCompleted: () =>
{
cannotFollow = false;
@@ -320,7 +328,7 @@ namespace Barotrauma
if (character.AnimController.InWater)
{
if (character.CurrentHull == null ||
isFollowOrderObjective &&
IsFollowOrderObjective &&
targetCharacter != null && (targetCharacter.CurrentHull == null) != (character.CurrentHull == null) &&
Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition) < maxGapDistance * maxGapDistance)
{
@@ -363,7 +371,7 @@ namespace Barotrauma
}
if (TargetGap != null)
{
if (TargetGap.FlowTargetHull != null && HumanAIController.SteerThroughGap(TargetGap, isFollowOrderObjective ? Target.WorldPosition : TargetGap.FlowTargetHull.WorldPosition, deltaTime))
if (TargetGap.FlowTargetHull != null && HumanAIController.SteerThroughGap(TargetGap, IsFollowOrderObjective ? Target.WorldPosition : TargetGap.FlowTargetHull.WorldPosition, deltaTime))
{
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 1);
return;
@@ -382,7 +390,7 @@ namespace Barotrauma
Item scooter = null;
float closeEnough = 250;
float squaredDistance = Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition);
bool shouldUseScooter = squaredDistance > closeEnough * closeEnough && (!mimic ||
bool shouldUseScooter = squaredDistance > closeEnough * closeEnough && (!Mimic ||
(targetCharacter != null && targetCharacter.HasEquippedItem(scooterTag, allowBroken: false)) || squaredDistance > Math.Pow(closeEnough * 2, 2));
if (HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> equippedScooters, recursive: false, requireEquipped: true))
{
@@ -391,24 +399,32 @@ namespace Barotrauma
}
else if (shouldUseScooter)
{
bool hasBattery = false;
if (HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> nonEquippedScooters, containedTag: batteryTag, conditionPercentage: 1, requireEquipped: false))
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);
if (!handsFull)
{
// 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);
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);
}
}
}
bool isScooterEquipped = scooter != null && character.HasEquippedItem(scooter);
@@ -597,7 +613,7 @@ namespace Barotrauma
{
if (gap.Open < 1) { continue; }
if (gap.Submarine == null) { continue; }
if (!isFollowOrderObjective)
if (!IsFollowOrderObjective)
{
if (gap.FlowTargetHull == null) { continue; }
if (gap.Submarine != Target.Submarine) { continue; }
@@ -678,18 +694,6 @@ namespace Barotrauma
return IsCompleted;
}
private void Abort()
{
if (!objectiveManager.IsOrder(this))
{
Abandon = true;
}
else
{
cannotFollow = true;
}
}
protected override void OnAbandon()
{
StopMovement();
@@ -0,0 +1,235 @@
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
namespace Barotrauma
{
class AIObjectiveLoadItem : AIObjective
{
public override string Identifier { get; set; } = "load item";
public override bool IsLoop
{
get => true;
set => throw new Exception("Trying to set the value for AIObjectiveLoadItem.IsLoop from: " + Environment.StackTrace.CleanupStackTrace());
}
private AIObjectiveLoadItems.ItemCondition TargetItemCondition { get; }
private Item Container { get; }
private ItemContainer ItemContainer { get; }
private ImmutableArray<string> TargetContainerTags { get; }
private int itemIndex = 0;
private AIObjectiveDecontainItem decontainObjective;
private readonly HashSet<Item> ignoredItems = new HashSet<Item>();
private Item targetItem;
private readonly string abandonGetItemDialogueIdentifier = "dialogcannotfindloadable";
public AIObjectiveLoadItem(Item container, ImmutableArray<string> targetTags, AIObjectiveLoadItems.ItemCondition targetCondition, string option, Character character, AIObjectiveManager objectiveManager, float priorityModifier)
: base(character, objectiveManager, priorityModifier)
{
Container = container;
ItemContainer = container?.GetComponent<ItemContainer>();
if (ItemContainer?.Inventory == null)
{
Abandon = true;
return;
}
TargetContainerTags = targetTags;
TargetItemCondition = targetCondition;
if (!string.IsNullOrEmpty(option))
{
string optionSpecificDialogueIdentifier = $"{abandonGetItemDialogueIdentifier}.{option}";
if (TextManager.ContainsTag(optionSpecificDialogueIdentifier))
{
abandonGetItemDialogueIdentifier = optionSpecificDialogueIdentifier;
}
}
}
protected override float GetPriority()
{
if (!IsAllowed)
{
Priority = 0;
Abandon = true;
return Priority;
}
else if (!AIObjectiveLoadItems.IsValidTarget(Container, character, targetCondition: TargetItemCondition))
{
// Reduce priority to 0 if the this isn't a valid container right now
Priority = 0;
}
else if (targetItem == null)
{
Priority = 0;
}
else
{
float dist = 0.0f;
if (character.CurrentHull != targetItem.CurrentHull)
{
AddDistance(character.WorldPosition, targetItem.WorldPosition);
}
if (targetItem.CurrentHull != Container.CurrentHull)
{
AddDistance(targetItem.WorldPosition, Container.WorldPosition);
}
void AddDistance(Vector2 startPos, Vector2 targetPos)
{
float yDist = Math.Abs(startPos.Y - targetPos.Y);
// If we're on the same level with the target, we'll disregard the vertical distance
if (yDist > 100) { dist += yDist * 5; }
dist += Math.Abs(character.WorldPosition.X - targetPos.X);
}
float distanceFactor = dist > 0.0f ? MathHelper.Lerp(0.9f, 0, MathUtils.InverseLerp(0, 5000, dist)) : 0.9f;
bool hasContainable = character.HasItem(targetItem);
float devotion = (CumulatedDevotion + (hasContainable ? 100 - MaxDevotion : 0)) / 100;
float max = AIObjectiveManager.LowestOrderPriority - (hasContainable ? 1 : 2);
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (distanceFactor * PriorityModifier), 0, 1));
if (decontainObjective != null && targetItem.Container != Container)
{
if (!IsValidContainable(targetItem))
{
// Target is not valid anymore, abandon the objective
decontainObjective.Abandon = true;
}
else if (!ItemContainer.Inventory.CanBePut(targetItem) && ItemContainer.Inventory.AllItems.None(i => AIObjectiveLoadItems.ItemMatchesTargetCondition(i, TargetItemCondition)))
{
// The container is full and there's no item that should be removed, abandon the objective
decontainObjective.Abandon = true;
}
}
if (ItemContainer.Inventory.IsFull())
{
// Prioritize containers that still have empty space by lowering the priority of objectives with a full target container
Priority /= 4;
}
}
return Priority;
}
public override void Update(float deltaTime)
{
base.Update(deltaTime);
if (targetItem == null)
{
if (character.FindItem(ref itemIndex, out Item item, identifiers: ItemContainer.ContainableItemIdentifiers, ignoreBroken: false, customPredicate: IsValidContainable, customPriorityFunction: GetConditionBasedPriority))
{
if (item == null)
{
// No possible containables found, abandon the objective
Abandon = true;
}
targetItem = item;
}
// Prefer items closer to full condition when target condition is Empty, and vice versa
float GetConditionBasedPriority(Item item)
{
try
{
return TargetItemCondition switch
{
AIObjectiveLoadItems.ItemCondition.Full => MathUtils.InverseLerp(100.0f, 0.0f, item.ConditionPercentage),
AIObjectiveLoadItems.ItemCondition.Empty => MathUtils.InverseLerp(0.0f, 100.0f, item.ConditionPercentage),
_ => throw new NotImplementedException()
};
}
catch (NotImplementedException)
{
#if DEBUG
DebugConsole.ShowError($"Unexpected target condition \"{TargetItemCondition}\" in local function GetConditionBasedProperty");
#endif
return 0.0f;
}
}
}
}
protected override void Act(float deltaTime)
{
if (targetItem != null)
{
if(decontainObjective == null && !IsValidContainable(targetItem))
{
IgnoreTargetItem();
Reset();
return;
}
TryAddSubObjective(ref decontainObjective,
constructor: () => new AIObjectiveDecontainItem(character, targetItem, objectiveManager, targetContainer: ItemContainer, priorityModifier: PriorityModifier)
{
AbandonGetItemDialogueIdentifier = abandonGetItemDialogueIdentifier,
Equip = true,
RemoveExistingWhenNecessary = true,
RemoveExistingPredicate = (i) => AIObjectiveLoadItems.ItemMatchesTargetCondition(i, TargetItemCondition),
RemoveExistingMax = 1
},
onCompleted: () =>
{
IsCompleted = true;
RemoveSubObjective(ref decontainObjective);
},
onAbandon: () =>
{
// Try again
IgnoreTargetItem();
Reset();
});
}
else
{
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
}
}
private bool IsValidContainable(Item item)
{
if (item == null) { return false; }
if (item.Removed) { return false; }
if (ignoredItems.Contains(item)) { return false; }
if ((item.SpawnedInCurrentOutpost && !item.AllowStealing) == character.IsOnPlayerTeam) { return false; }
var rootInventoryOwner = item.GetRootInventoryOwner();
if (rootInventoryOwner is Character owner && owner != character) { return false; }
if (rootInventoryOwner is Item parentItem)
{
if (parentItem.HasTag("donttakeitems")) { return false; }
if (!(parentItem.GetComponent<ItemContainer>()?.HasAccess(character) ?? true)) { return false; }
}
if (item.IsThisOrAnyContainerIgnoredByAI(character)) { return false; }
if (!character.HasItem(item) && !CanEquip(item)) { return false; }
if (!ItemContainer.HasAccess(character)) { return false; }
if (!ItemContainer.CanBeContained(item)) { return false; }
if (AIObjectiveLoadItems.ItemMatchesTargetCondition(item, TargetItemCondition)) { return false; }
if (TargetItemCondition == AIObjectiveLoadItems.ItemCondition.Full)
{
// Ignore items that have had their condition increase recently
if (TargetItemCondition == AIObjectiveLoadItems.ItemCondition.Full && item.ConditionIncreasedRecently) { return false; }
// Ignore items inside their (condition-restricted) primary containers
if (item.ParentInventory is ItemInventory itemInventory && item.IsContainerPreferred(itemInventory.Container, out bool _, out bool isSecondary, requireConditionRestriction: true) && !isSecondary) { return false; }
}
// Ignore items inside another valid container
if (AIObjectiveLoadItems.IsValidTarget(item.Container, character, TargetContainerTags)) { return false; }
return true;
}
protected override bool CheckObjectiveSpecific() => IsCompleted;
public override void Reset()
{
base.Reset();
// Don't reset the target item when resetting the objective because it affects priority calculations
decontainObjective = null;
itemIndex = 0;
}
private void IgnoreTargetItem()
{
if(targetItem == null) { return; }
ignoredItems.Add(targetItem);
targetItem = null;
}
}
}
@@ -0,0 +1,115 @@
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
namespace Barotrauma
{
class AIObjectiveLoadItems : AIObjectiveLoop<Item>
{
public override string Identifier { get; set; } = "load items";
protected override float IgnoreListClearInterval => 20.0f;
protected override bool ResetWhenClearingIgnoreList => false;
private ImmutableArray<string> TargetContainerTags { get; }
private List<Item> TargetContainers { get; } = new List<Item>();
private ItemCondition TargetCondition { get; }
public enum ItemCondition
{
Empty,
Full
}
public AIObjectiveLoadItems(Character character, AIObjectiveManager objectiveManager, string option, ImmutableArray<string> containerTags, Item targetContainer = null, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier, option)
{
if ((containerTags == null || containerTags.None()) && targetContainer == null)
{
Abandon = true;
return;
}
else
{
TargetContainerTags = containerTags.ToImmutableArray();
}
if (targetContainer != null)
{
TargetContainers.Add(targetContainer);
}
TargetCondition = option == "turretammo" ? ItemCondition.Empty : ItemCondition.Full;
}
protected override bool Filter(Item target)
{
if (!IsValidTarget(target, character, TargetContainerTags, TargetCondition)) { return false; }
if (target.CurrentHull == null || target.CurrentHull.FireSources.Count > 0) { return false; }
if (Character.CharacterList.Any(c => c.CurrentHull == target.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return false; }
return true;
}
public static bool IsValidTarget(Item item, Character character, ImmutableArray<string>? targetContainerTags = null, ItemCondition? targetCondition = null)
{
if (item == null) { return false; }
if (item.Removed) { return false; }
if (targetContainerTags.HasValue && !Order.TargetItemsMatchItem(targetContainerTags.Value, item)) { return false; }
if (!(item.GetComponent<ItemContainer>() is 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; }
if (item.GetRootInventoryOwner() is Character owner && owner != character) { return false; }
if (!item.IsInteractable(character)) { return false; }
if (item.IsThisOrAnyContainerIgnoredByAI(character)) { return false; }
if (!container.HasAccess(character)) { return false; }
// Ignore items that require power but don't have it
if (item.GetComponent<Powered>() is Powered powered && powered.PowerConsumption > 0 && powered.Voltage < powered.MinVoltage) { return false; }
return true;
}
public static bool ItemMatchesTargetCondition(Item item, ItemCondition targetCondition)
{
if(item == null) { return false; }
try
{
return targetCondition switch
{
ItemCondition.Empty => item.Condition <= 0.1f,
ItemCondition.Full => item.IsFullCondition,
_ => throw new NotImplementedException(),
};
}
catch (NotImplementedException)
{
#if DEBUG
DebugConsole.ShowError($"Unexpected target condition \"{targetCondition}\" in AIObjectiveLoadItems.ItemMatchesTargetCondition");
#endif
return false;
}
}
protected override IEnumerable<Item> GetList() => TargetContainers.Any() ? TargetContainers : Item.ItemList;
protected override AIObjective ObjectiveConstructor(Item target)
=> new AIObjectiveLoadItem(target, TargetContainerTags, TargetCondition, Option, character, objectiveManager, PriorityModifier);
protected override void OnObjectiveCompleted(AIObjective objective, Item target)
=> HumanAIController.RemoveTargets<AIObjectiveLoadItems, Item>(character, target);
protected override float TargetEvaluation()
{
if (Targets.None()) { return 0; }
if (objectiveManager.IsOrder(this))
{
float prio = objectiveManager.GetOrderPriority(this);
if (subObjectives.All(so => so.SubObjectives.None() || so.Priority <= 0))
{
ForceWalk = true;
}
return prio;
}
return AIObjectiveManager.RunPriority - 0.5f;
}
}
}
@@ -45,6 +45,7 @@ namespace Barotrauma
public override bool AbandonWhenCannotCompleteSubjectives => false;
public override bool AllowSubObjectiveSorting => true;
public virtual bool InverseTargetEvaluation => false;
protected virtual bool ResetWhenClearingIgnoreList => true;
public override bool IsLoop { get => true; set => throw new Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace.CleanupStackTrace()); }
@@ -55,7 +56,15 @@ namespace Barotrauma
{
if (ignoreListTimer > IgnoreListClearInterval)
{
Reset();
if (ResetWhenClearingIgnoreList)
{
Reset();
}
else
{
ignoreList.Clear();
ignoreListTimer = 0;
}
}
else
{
@@ -113,7 +122,7 @@ namespace Barotrauma
Priority = 0;
return Priority;
}
if (character.LockHands || character.Submarine == null)
if (character.LockHands)
{
Priority = 0;
}
@@ -131,7 +140,7 @@ namespace Barotrauma
// If the priority is higher than the target value, let's just use it.
// The priority calculation is more precise, but it takes into account things like distances,
// so it's better not to use it if it's lower than the rougher targetValue.
targetValue = Priority;
targetValue = currentSubObjective.Priority;
}
// If the target value is less than 1% of the max value, let's just treat it as zero.
if (targetValue < 1)
@@ -128,7 +128,7 @@ namespace Barotrauma
Item item = null;
if (orderPrefab.MustSetTarget)
{
item = orderPrefab.GetMatchingItems(character.Submarine, mustBelongToPlayerSub: false, requiredTeam: character.Info.TeamID, interactableFor: character)?.GetRandom();
item = orderPrefab.GetMatchingItems(character.Submarine, mustBelongToPlayerSub: false, requiredTeam: character.Info.TeamID, interactableFor: character, orderOption: autonomousObjective.option)?.GetRandom();
}
var order = new Order(orderPrefab, item ?? character.CurrentHull as Entity, orderPrefab.GetTargetItemComponent(item), orderGiver: character);
if (order == null) { continue; }
@@ -279,6 +279,7 @@ namespace Barotrauma
float highestPriority = 0;
for (int i = CurrentOrders.Count - 1; i >= 0; i--)
{
if (CurrentOrders.Count <= i) { break; }
var orderObjective = CurrentOrders[i].Objective;
if (orderObjective == null) { continue; }
orderObjective.CalculatePriority();
@@ -354,6 +355,7 @@ namespace Barotrauma
// Make sure the order priorities reflect those set by the player
for (int i = CurrentOrders.Count - 1; i >= 0; i--)
{
if (CurrentOrders.Count <= i) { break; }
var currentOrder = CurrentOrders[i];
if (currentOrder.Objective == null || currentOrder.MatchesOrder(order, option))
{
@@ -406,13 +408,14 @@ namespace Barotrauma
if (orderGiver == null) { return null; }
newObjective = new AIObjectiveGoTo(orderGiver, character, this, repeat: true, priorityModifier: priorityModifier)
{
CloseEnough = Rand.Range(90, 100) + Rand.Range(50, 70) * Math.Min(HumanAIController.CountCrew(c => c.ObjectiveManager.CurrentOrder is AIObjectiveGoTo gotoOrder && gotoOrder.Target == orderGiver, onlyBots: true), 4),
extraDistanceOutsideSub = 100,
extraDistanceWhileSwimming = 100,
CloseEnough = Rand.Range(80, 100),
CloseEnoughMultiplier = Math.Min(1 + HumanAIController.CountCrew(c => c.ObjectiveManager.HasOrder<AIObjectiveGoTo>(o => o.Target == orderGiver), onlyBots: true) * Rand.Range(0.8f, 1f), 4),
ExtraDistanceOutsideSub = 100,
ExtraDistanceWhileSwimming = 100,
AllowGoingOutside = true,
IgnoreIfTargetDead = true,
isFollowOrderObjective = true,
mimic = true,
IsFollowOrderObjective = true,
Mimic = character.IsOnPlayerTeam,
DialogueIdentifier = "dialogcannotreachplace"
};
break;
@@ -523,7 +526,7 @@ namespace Barotrauma
newObjective = new AIObjectiveEscapeHandcuffs(character, this, priorityModifier: priorityModifier);
break;
case "prepareforexpedition":
newObjective = new AIObjectivePrepare(character, this, order.TargetItems)
newObjective = new AIObjectivePrepare(character, this, order.GetTargetItems(option), order.RequireItems)
{
KeepActiveWhenReady = true,
CheckInventory = true,
@@ -532,16 +535,29 @@ namespace Barotrauma
};
break;
case "findweapon":
newObjective = new AIObjectivePrepare(character, this, order.TargetItems)
AIObjectivePrepare prepareObjective;
if (order.TargetEntity is Item tItem)
{
KeepActiveWhenReady = false,
CheckInventory = false,
Equip = true,
EvaluateCombatPriority = true,
FindAllItems = false
};
prepareObjective = new AIObjectivePrepare(character, this, targetItem: tItem);
}
else
{
prepareObjective = new AIObjectivePrepare(character, this, order.GetTargetItems(option), order.RequireItems)
{
KeepActiveWhenReady = false,
CheckInventory = false,
EvaluateCombatPriority = true,
FindAllItems = false
};
}
prepareObjective.KeepActiveWhenReady = false;
prepareObjective.Equip = true;
newObjective = prepareObjective;
newObjective.Completed += () => DismissSelf(order, option);
break;
case "loaditems":
newObjective = new AIObjectiveLoadItems(character, this, option, order.GetTargetItems(option), order.TargetEntity as Item, priorityModifier);
break;
default:
if (order.TargetItemComponent == null) { return null; }
if (!order.TargetItemComponent.Item.IsInteractable(character)) { return null; }
@@ -573,17 +589,20 @@ namespace Barotrauma
#endif
return;
}
Order dismissOrder = Order.GetPrefab("dismissed");
var orderOption = Order.GetDismissOrderOption(currentOrder);
int priority = currentOrder.ManualPriority;
#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);
GameMain.GameSession.CrewManager.SetCharacterOrder(character, dismissOrder, orderOption, priority, character);
}
#else
GameMain.Server?.SendOrderChatMessage(new OrderChatMessage(Order.GetPrefab("dismissed"), Order.GetDismissOrderOption(currentOrder), currentOrder.ManualPriority, currentOrder.Order?.TargetSpatialEntity, character, character));
GameMain.Server?.SendOrderChatMessage(new OrderChatMessage(dismissOrder, orderOption, priority, currentOrder.Order.TargetSpatialEntity, character, character));
SetOrder(dismissOrder, orderOption, priority, character, speak: false);
#endif
}
private bool IsAllowedToWait()
{
if (!character.IsOnPlayerTeam) { return false; }
@@ -632,10 +651,9 @@ namespace Barotrauma
return ForcedOrder != null || CurrentOrders.Any();
}
public bool HasOrder<T>() where T : AIObjective
{
return ForcedOrder is T || CurrentOrders.Any(o => o.Objective is T);
}
public bool HasOrder<T>(Func<T, bool> predicate = null) where T : AIObjective =>
ForcedOrder is T forcedOrder && (predicate == null || predicate(forcedOrder)) ||
CurrentOrders.Any(o => o.Objective is T order && (predicate == null || predicate(order)));
public float GetOrderPriority(AIObjective objective)
{
@@ -202,7 +202,7 @@ namespace Barotrauma
HumanAIController.FaceTarget(target.Item);
if (character.SelectedConstruction != target.Item)
{
target.Item.TryInteract(character, false, true);
target.Item.TryInteract(character, forceSelectKey: true);
}
if (component.AIOperate(deltaTime, character, this))
{
@@ -1,5 +1,5 @@
#nullable enable
using Barotrauma.Extensions;
using Barotrauma.Extensions;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.Immutable;
@@ -11,11 +11,15 @@ namespace Barotrauma
public override string Identifier { get; set; } = "prepare";
public override string DebugTag => $"{Identifier}";
public override bool KeepDivingGearOn => true;
public override bool PrioritizeIfSubObjectivesActive => true;
private AIObjectiveGetItem? getSingleItemObjective;
private AIObjectiveGetItems? getMultipleItemsObjective;
private AIObjectiveGetItem getSingleItemObjective;
private AIObjectiveGetItems getAllItemsObjective;
private AIObjectiveGetItems getMultipleItemsObjective;
private bool subObjectivesCreated;
private readonly ImmutableArray<string> gearTags;
private readonly Item targetItem;
private readonly ImmutableArray<string> requiredItems;
private readonly ImmutableArray<string> optionalItems;
private readonly HashSet<Item> items = new HashSet<Item>();
public bool KeepActiveWhenReady { get; set; }
public bool CheckInventory { get; set; }
@@ -23,12 +27,29 @@ namespace Barotrauma
public bool Equip { get; set; }
public bool EvaluateCombatPriority { get; set; }
private AIObjective? GetSubObjective() => getSingleItemObjective ?? getMultipleItemsObjective as AIObjective;
public AIObjectivePrepare(Character character, AIObjectiveManager objectiveManager, IEnumerable<string> items, float priorityModifier = 1)
private AIObjective GetSubObjective()
{
if (getSingleItemObjective != null) { return getSingleItemObjective; }
if (getAllItemsObjective == null || getAllItemsObjective.IsCompleted)
{
return getMultipleItemsObjective;
}
return getAllItemsObjective;
}
public AIObjectivePrepare(Character character, AIObjectiveManager objectiveManager, Item targetItem, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier)
{
gearTags = items.ToImmutableArray();
this.targetItem = targetItem;
}
public AIObjectivePrepare(Character character, AIObjectiveManager objectiveManager, IEnumerable<string> optionalItems, IEnumerable<string> requiredItems = null, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier)
{
this.optionalItems = optionalItems.ToImmutableArray();
if (requiredItems != null)
{
this.requiredItems = requiredItems.ToImmutableArray();
}
}
protected override bool CheckObjectiveSpecific() => IsCompleted;
@@ -69,48 +90,76 @@ namespace Barotrauma
}
if (!subObjectivesCreated)
{
if (FindAllItems)
if (FindAllItems && targetItem == null)
{
if (!TryAddSubObjective(ref getMultipleItemsObjective, () => new AIObjectiveGetItems(character, objectiveManager, gearTags)
getMultipleItemsObjective = CreateObjectives(optionalItems, requireAll: false);
if (requiredItems != null && requiredItems.Any())
{
CheckInventory = CheckInventory,
Equip = Equip,
EvaluateCombatPriority = EvaluateCombatPriority,
RequireLoaded = true
},
onCompleted: () =>
getAllItemsObjective = CreateObjectives(requiredItems, requireAll: true);
}
AIObjectiveGetItems CreateObjectives(IEnumerable<string> itemTags, bool requireAll)
{
if (KeepActiveWhenReady)
AIObjectiveGetItems objectiveReference = null;
if (!TryAddSubObjective(ref objectiveReference, () => new AIObjectiveGetItems(character, objectiveManager, itemTags)
{
if (getMultipleItemsObjective != null)
CheckInventory = CheckInventory,
Equip = Equip,
EvaluateCombatPriority = EvaluateCombatPriority,
RequireLoaded = true,
RequireAllItems = requireAll
},
onCompleted: () =>
{
if (KeepActiveWhenReady)
{
foreach (var item in getMultipleItemsObjective.achievedItems)
if (objectiveReference != null)
{
if (item?.IsOwnedBy(character) != null)
foreach (var item in objectiveReference.achievedItems)
{
items.Add(item);
if (item?.IsOwnedBy(character) != null)
{
items.Add(item);
}
}
}
}
}
else
else
{
IsCompleted = true;
}
},
onAbandon: () => Abandon = true))
{
IsCompleted = true;
Abandon = true;
}
},
onAbandon: () => Abandon = true))
{
Abandon = true;
return objectiveReference;
}
}
else
{
if (!TryAddSubObjective(ref getSingleItemObjective, () => new AIObjectiveGetItem(character, gearTags, objectiveManager, equip: Equip, checkInventory: CheckInventory)
Func<AIObjectiveGetItem> getItemConstructor;
if (targetItem != null)
{
EvaluateCombatPriority = EvaluateCombatPriority,
SpeakIfFails = true,
RequireLoaded = true
},
getItemConstructor = () => new AIObjectiveGetItem(character, targetItem, objectiveManager, equip: Equip)
{
SpeakIfFails = true
};
}
else
{
IEnumerable<string> allItems = optionalItems;
if (requiredItems != null && requiredItems.Any())
{
allItems = requiredItems;
}
getItemConstructor = () => new AIObjectiveGetItem(character, allItems, objectiveManager, equip: Equip, checkInventory: CheckInventory)
{
EvaluateCombatPriority = EvaluateCombatPriority,
SpeakIfFails = true,
RequireLoaded = true
};
}
if (!TryAddSubObjective(ref getSingleItemObjective, getItemConstructor,
onCompleted: () =>
{
if (KeepActiveWhenReady)
@@ -143,8 +192,9 @@ namespace Barotrauma
base.Reset();
items.Clear();
subObjectivesCreated = false;
RemoveSubObjective(ref getMultipleItemsObjective);
RemoveSubObjective(ref getSingleItemObjective);
getMultipleItemsObjective = null;
getSingleItemObjective = null;
getAllItemsObjective = null;
}
}
}
@@ -20,6 +20,9 @@ namespace Barotrauma
private float previousCondition = -1;
private RepairTool repairTool;
private const float WaitTimeBeforeRepair = 0.5f;
private float waitTimer;
private bool IsRepairing() => IsRepairing(character, Item);
private readonly bool isPriority;
@@ -160,6 +163,9 @@ namespace Barotrauma
}
if (!character.IsClimbing && character.CanInteractWith(Item, out _, checkLinked: false))
{
waitTimer += deltaTime;
if (waitTimer < WaitTimeBeforeRepair) { return; }
HumanAIController.FaceTarget(Item);
if (repairTool != null)
{
@@ -177,7 +183,7 @@ namespace Barotrauma
if (character.SelectedConstruction != Item)
{
if (!Item.TryInteract(character, ignoreRequiredItems: true, forceSelectKey: true) &&
!Item.TryInteract(character, ignoreRequiredItems: true, forceActionKey: true))
!Item.TryInteract(character, ignoreRequiredItems: true, forceUseKey: true))
{
Abandon = true;
}
@@ -209,6 +215,7 @@ namespace Barotrauma
}
else
{
waitTimer = 0.0f;
RemoveSubObjective(ref refuelObjective);
// If cannot reach the item, approach it.
TryAddSubObjective(ref goToObjective,
@@ -36,9 +36,9 @@ namespace Barotrauma
{
if (targetCharacter == null)
{
string errorMsg = $"{character.Name}: Attempted to create a Rescue objective with no target!\n" + Environment.StackTrace.CleanupStackTrace();
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("AIObjectiveRescue:ctor:targetnull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
string errorMsg = $"Attempted to create a Rescue objective with no target!\n" + Environment.StackTrace.CleanupStackTrace();
DebugConsole.ThrowError(character.Name + ": " + errorMsg);
GameAnalyticsManager.AddErrorEventOnce("AIObjectiveRescue:ctor:targetnull", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
Abandon = true;
return;
}
@@ -59,7 +59,7 @@ namespace Barotrauma
protected override void Act(float deltaTime)
{
if (character.LockHands || targetCharacter == null || targetCharacter.CurrentHull == null || targetCharacter.Removed || targetCharacter.IsDead)
if (character.LockHands || targetCharacter == null || targetCharacter.Removed || targetCharacter.IsDead)
{
Abandon = true;
return;
@@ -137,66 +137,69 @@ namespace Barotrauma
recursive: true);
}
}
if (HumanAIController.GetHullSafety(targetCharacter.CurrentHull, targetCharacter) < HumanAIController.HULL_SAFETY_THRESHOLD)
if (character.Submarine != null)
{
// Incapacitated target is not in a safe place -> Move to a safe place first
if (character.SelectedCharacter != targetCharacter)
if (HumanAIController.GetHullSafety(targetCharacter.CurrentHull, targetCharacter) < HumanAIController.HULL_SAFETY_THRESHOLD)
{
if (targetCharacter.CurrentHull != null && HumanAIController.VisibleHulls.Contains(targetCharacter.CurrentHull) && targetCharacter.CurrentHull.DisplayName != null)
// Incapacitated target is not in a safe place -> Move to a safe place first
if (character.SelectedCharacter != targetCharacter)
{
character.Speak(TextManager.GetWithVariables("DialogFoundUnconsciousTarget", new string[2] { "[targetname]", "[roomname]" },
new string[2] { targetCharacter.Name, targetCharacter.CurrentHull.DisplayName }, new bool[2] { false, true }),
null, 1.0f, "foundunconscioustarget" + targetCharacter.Name, 60.0f);
}
// Go to the target and select it
if (!character.CanInteractWith(targetCharacter))
{
RemoveSubObjective(ref replaceOxygenObjective);
RemoveSubObjective(ref goToObjective);
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager)
if (targetCharacter.CurrentHull != null && HumanAIController.VisibleHulls.Contains(targetCharacter.CurrentHull) && targetCharacter.CurrentHull.DisplayName != null)
{
CloseEnough = CloseEnoughToTreat,
DialogueIdentifier = "dialogcannotreachpatient",
TargetName = targetCharacter.DisplayName
},
onCompleted: () => RemoveSubObjective(ref goToObjective),
onAbandon: () =>
character.Speak(TextManager.GetWithVariables("DialogFoundUnconsciousTarget", new string[2] { "[targetname]", "[roomname]" },
new string[2] { targetCharacter.Name, targetCharacter.CurrentHull.DisplayName }, new bool[2] { false, true }),
null, 1.0f, "foundunconscioustarget" + targetCharacter.Name, 60.0f);
}
// Go to the target and select it
if (!character.CanInteractWith(targetCharacter))
{
RemoveSubObjective(ref replaceOxygenObjective);
RemoveSubObjective(ref goToObjective);
Abandon = true;
});
}
else
{
character.SelectCharacter(targetCharacter);
}
}
else
{
// Drag the character into safety
if (safeHull == null)
{
if (findHullTimer > 0)
{
findHullTimer -= deltaTime;
}
else
{
safeHull = objectiveManager.GetObjective<AIObjectiveFindSafety>().FindBestHull(HumanAIController.VisibleHulls);
findHullTimer = findHullInterval * Rand.Range(0.9f, 1.1f);
}
}
if (safeHull != null && character.CurrentHull != safeHull)
{
RemoveSubObjective(ref replaceOxygenObjective);
RemoveSubObjective(ref goToObjective);
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(safeHull, character, objectiveManager),
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager)
{
CloseEnough = CloseEnoughToTreat,
DialogueIdentifier = "dialogcannotreachpatient",
TargetName = targetCharacter.DisplayName
},
onCompleted: () => RemoveSubObjective(ref goToObjective),
onAbandon: () =>
{
RemoveSubObjective(ref goToObjective);
safeHull = character.CurrentHull;
Abandon = true;
});
}
else
{
character.SelectCharacter(targetCharacter);
}
}
else
{
// Drag the character into safety
if (safeHull == null)
{
if (findHullTimer > 0)
{
findHullTimer -= deltaTime;
}
else
{
safeHull = objectiveManager.GetObjective<AIObjectiveFindSafety>().FindBestHull(HumanAIController.VisibleHulls);
findHullTimer = findHullInterval * Rand.Range(0.9f, 1.1f);
}
}
if (safeHull != null && character.CurrentHull != safeHull)
{
RemoveSubObjective(ref replaceOxygenObjective);
RemoveSubObjective(ref goToObjective);
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(safeHull, character, objectiveManager),
onCompleted: () => RemoveSubObjective(ref goToObjective),
onAbandon: () =>
{
RemoveSubObjective(ref goToObjective);
safeHull = character.CurrentHull;
});
}
}
}
}
@@ -228,7 +231,7 @@ namespace Barotrauma
// We can start applying treatment
if (character != targetCharacter && character.SelectedCharacter != targetCharacter)
{
if (targetCharacter.CurrentHull.DisplayName != null)
if (targetCharacter.CurrentHull?.DisplayName != null)
{
character.Speak(TextManager.GetWithVariables("DialogFoundWoundedTarget", new string[2] { "[targetname]", "[roomname]" },
new string[2] { targetCharacter.Name, targetCharacter.CurrentHull.DisplayName }, new bool[2] { false, true }),
@@ -252,7 +255,7 @@ namespace Barotrauma
return;
}
SteeringManager?.Reset();
SteeringManager.Reset();
if (!targetCharacter.IsPlayer)
{
@@ -269,26 +272,36 @@ namespace Barotrauma
float cprSuitability = targetCharacter.Oxygen < 0.0f ? -targetCharacter.Oxygen * 100.0f : 0.0f;
//find which treatments are the most suitable to treat the character's current condition
targetCharacter.CharacterHealth.GetSuitableTreatments(currentTreatmentSuitabilities, normalize: false);
targetCharacter.CharacterHealth.GetSuitableTreatments(currentTreatmentSuitabilities, normalize: false, predictFutureDuration: 10.0f);
//check if we already have a suitable treatment for any of the afflictions
foreach (Affliction affliction in GetSortedAfflictions(targetCharacter))
{
if (affliction == null) { throw new Exception("Affliction was null"); }
if (affliction.Prefab == null) { throw new Exception("Affliction prefab was null"); }
float bestSuitability = 0.0f;
Item bestItem = null;
foreach (KeyValuePair<string, float> treatmentSuitability in affliction.Prefab.TreatmentSuitability)
{
if (currentTreatmentSuitabilities.ContainsKey(treatmentSuitability.Key) && currentTreatmentSuitabilities[treatmentSuitability.Key] > 0.0f)
if (currentTreatmentSuitabilities.ContainsKey(treatmentSuitability.Key) &&
currentTreatmentSuitabilities[treatmentSuitability.Key] > bestSuitability)
{
Item matchingItem = character.Inventory.FindItemByIdentifier(treatmentSuitability.Key, true);
if (matchingItem == null) { continue; }
if (targetCharacter != character) { character.SelectCharacter(targetCharacter); }
ApplyTreatment(affliction, matchingItem);
//wait a bit longer after applying a treatment to wait for potential side-effects to manifest
treatmentTimer = TreatmentDelay * 4;
return;
if (matchingItem != null)
{
bestItem = matchingItem;
bestSuitability = currentTreatmentSuitabilities[treatmentSuitability.Key];
}
}
}
if (bestItem != null)
{
if (targetCharacter != character) { character.SelectCharacter(targetCharacter); }
ApplyTreatment(affliction, bestItem);
//wait a bit longer after applying a treatment to wait for potential side-effects to manifest
treatmentTimer = TreatmentDelay * 4;
return;
}
}
// Find treatments outside of own inventory only if inside the own sub.
if (character.Submarine != null && character.Submarine.TeamID == character.TeamID)
@@ -418,21 +431,7 @@ namespace Barotrauma
protected override bool CheckObjectiveSpecific()
{
if (character.LockHands || targetCharacter == null || targetCharacter.CurrentHull == null || targetCharacter.Removed || targetCharacter.IsDead)
{
Abandon = true;
return false;
}
// Don't go into rooms that have enemies
if (Character.CharacterList.Any(c => c.CurrentHull == targetCharacter.CurrentHull && !HumanAIController.IsFriendly(character, c) && HumanAIController.IsActive(c)))
{
Abandon = true;
return false;
}
bool isCompleted =
AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter) ||
targetCharacter.CharacterHealth.GetAllAfflictions().All(a => a.Prefab.IsBuff || a.Strength <= a.Prefab.TreatmentThreshold);
bool isCompleted = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter);
if (isCompleted && targetCharacter != character && character.IsOnPlayerTeam)
{
character.Speak(TextManager.GetWithVariable("DialogTargetHealed", "[targetname]", targetCharacter.Name),
@@ -449,16 +448,36 @@ namespace Barotrauma
Abandon = true;
return Priority;
}
if (character.LockHands || targetCharacter == null || targetCharacter.CurrentHull == null || targetCharacter.Removed || targetCharacter.IsDead)
if (character.CurrentHull == null)
{
if (!objectiveManager.HasOrder<AIObjectiveRescueAll>())
{
Priority = 0;
Abandon = true;
return Priority;
}
}
else 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;
}
if (targetCharacter == null)
{
Priority = 0;
Abandon = true;
}
else
{
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
float dist = Math.Abs(character.WorldPosition.X - targetCharacter.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - targetCharacter.WorldPosition.Y) * 2.0f;
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, dist));
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;
@@ -472,6 +491,16 @@ namespace Barotrauma
public static IEnumerable<Affliction> GetSortedAfflictions(Character character) => CharacterHealth.SortAfflictionsBySeverity(character.CharacterHealth.GetAllAfflictions());
public static IEnumerable<Affliction> GetTreatableAfflictions(Character character)
{
foreach (Affliction affliction in character.CharacterHealth.GetAllAfflictions())
{
if (affliction.Prefab.IsBuff || affliction.Strength < affliction.Prefab.TreatmentThreshold) { continue; }
if (!affliction.Prefab.TreatmentSuitability.Any(kvp => kvp.Value > 0)) { continue; }
yield return affliction;
}
}
public override void Reset()
{
base.Reset();
@@ -64,8 +64,15 @@ namespace Barotrauma
public static float GetVitalityFactor(Character character)
{
float vitality = character.HealthPercentage - (character.Bleeding * 2) - character.Bloodloss + Math.Min(character.Oxygen, 0);
float vitality = 100;
vitality -= character.Bleeding * 2;
vitality += Math.Min(character.Oxygen, 0);
vitality -= character.CharacterHealth.GetAfflictionStrength("paralysis");
foreach (Affliction affliction in AIObjectiveRescue.GetTreatableAfflictions(character))
{
float strength = character.CharacterHealth.GetPredictedStrength(affliction, predictFutureDuration: 10.0f);
vitality -= affliction.GetVitalityDecrease(character.CharacterHealth, strength) / character.MaxVitality * 100;
}
return Math.Clamp(vitality, 0, 100);
}
@@ -83,8 +90,7 @@ namespace Barotrauma
if (!HumanAIController.IsFriendly(character, target, onlySameTeam: true)) { return false; }
if (character.AIController is HumanAIController humanAI)
{
if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target) ||
target.CharacterHealth.GetAllAfflictions().All(a => a.Prefab.IsBuff || a.Strength <= a.Prefab.TreatmentThreshold))
if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target))
{
return false;
}
@@ -106,10 +112,13 @@ namespace Barotrauma
{
if (GetVitalityFactor(target) >= vitalityThreshold) { return false; }
}
if (target.Submarine == null || character.Submarine == null) { return false; }
// 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; }
if (target != character &&!target.IsPlayer && HumanAIController.IsActive(target) && target.AIController is HumanAIController targetAI)
if (target.Submarine != character.Submarine) { return false; }
if (character.Submarine != null)
{
// 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; }
}
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
if (targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveCombat>() ||
@@ -120,9 +129,12 @@ namespace Barotrauma
return false;
}
}
// Don't go into rooms that have enemies
if (Character.CharacterList.Any(c => c.CurrentHull == target.CurrentHull && !HumanAIController.IsFriendly(character, c) && HumanAIController.IsActive(c))) { return false; }
return true;
if (target.CurrentHull != null)
{
// Don't go into rooms that have enemies
if (Character.CharacterList.Any(c => c.CurrentHull == target.CurrentHull && !HumanAIController.IsFriendly(character, c) && HumanAIController.IsActive(c))) { return false; }
}
return character.GetDamageDoneByAttacker(target) <= 0;
}
}
}