v0.12.0.2

This commit is contained in:
Joonas Rikkonen
2021-02-10 17:08:21 +02:00
parent 5c80a59bdd
commit 694cdfee7b
353 changed files with 12897 additions and 5028 deletions
@@ -220,7 +220,7 @@ namespace Barotrauma
{
if (!AllowOutsideSubmarine && character.Submarine == null) { return false; }
if (AllowInAnySub) { return true; }
if (AllowInFriendlySubs && character.Submarine.TeamID == Character.TeamType.FriendlyNPC) { return true; }
if (AllowInFriendlySubs && character.Submarine.TeamID == CharacterTeamType.FriendlyNPC) { return true; }
return character.Submarine.TeamID == character.TeamID || character.Submarine.DockedTo.Any(sub => sub.TeamID == character.TeamID);
}
}
@@ -21,7 +21,7 @@ namespace Barotrauma
if (battery == null) { return false; }
var item = battery.Item;
if (item.IgnoreByAI) { return false; }
if (item.NonInteractable) { return false; }
if (!item.IsInteractable(character)) { return false; }
if (item.Submarine == null) { return false; }
if (item.CurrentHull == null) { return false; }
if (item.Submarine.TeamID != character.TeamID) { return false; }
@@ -58,6 +58,11 @@ namespace Barotrauma
{
// Only continue when the get item sub objectives have been completed.
if (subObjectives.Any()) { return; }
if (item.IgnoreByAI)
{
Abandon = true;
return;
}
if (HumanAIController.FindSuitableContainer(character, item, ignoredContainers, ref itemIndex, out Item suitableContainer))
{
itemIndex = 0;
@@ -12,15 +12,24 @@ namespace Barotrauma
public override bool AllowAutomaticItemUnequipping => false;
public override bool ForceOrderPriority => false;
public readonly Item prioritizedItem;
public readonly List<Item> prioritizedItems = new List<Item>();
public AIObjectiveCleanupItems(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1, Item prioritizedItem = null)
public AIObjectiveCleanupItems(Character character, AIObjectiveManager objectiveManager, Item prioritizedItem = null, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier)
{
this.prioritizedItem = prioritizedItem;
if (prioritizedItem != null)
{
prioritizedItems.Add(prioritizedItem);
}
}
protected override float TargetEvaluation() => Targets.Any() ? AIObjectiveManager.RunPriority - 1 : 0;
public AIObjectiveCleanupItems(Character character, AIObjectiveManager objectiveManager, IEnumerable<Item> prioritizedItems, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier)
{
this.prioritizedItems.AddRange(prioritizedItems.Where(i => i != null));
}
protected override float TargetEvaluation() => Targets.Any() ? (objectiveManager.CurrentOrder == this ? AIObjectiveManager.OrderPriority : AIObjectiveManager.RunPriority - 1) : 0;
protected override bool Filter(Item target)
{
@@ -38,7 +47,7 @@ namespace Barotrauma
protected override AIObjective ObjectiveConstructor(Item item)
=> new AIObjectiveCleanupItem(item, character, objectiveManager, priorityModifier: PriorityModifier)
{
IsPriority = prioritizedItem == item
IsPriority = prioritizedItems.Contains(item)
};
protected override void OnObjectiveCompleted(AIObjective objective, Item target)
@@ -56,12 +65,19 @@ namespace Barotrauma
return true;
}
public static bool IsValidContainer(Item item, Character character) =>
!item.IgnoreByAI && item.IsInteractable(character) && item.HasTag("allowcleanup") && item.ParentInventory == null && item.OwnInventory != null && item.OwnInventory.AllItems.Any() && IsItemInsideValidSubmarine(item, character);
public static bool IsValidTarget(Item item, Character character, bool checkInventory)
{
if (item == null) { return false; }
if (item.IgnoreByAI) { return false; }
if (item.NonInteractable) { return false; }
if (item.ParentInventory != null) { return false; }
if (!item.IsInteractable(character)) { return false; }
if (item.SpawnedInOutpost) { return false; }
if (item.ParentInventory != null)
{
if (item.Container == null || !IsValidContainer(item.Container, character)) { return false; }
}
if (character != null && !IsItemInsideValidSubmarine(item, character)) { return false; }
var pickable = item.GetComponent<Pickable>();
if (pickable == null) { return false; }
@@ -96,18 +112,16 @@ namespace Barotrauma
{
foreach (var slotType in inv.SlotTypes)
{
if (allowedSlot.HasFlag(slotType))
if (!allowedSlot.HasFlag(slotType)) { continue; }
for (int i = 0; i < inv.Capacity; i++)
{
for (int i = 0; i < inv.Capacity; i++)
canEquip = true;
if (allowedSlot.HasFlag(inv.SlotTypes[i]) && inv.GetItemAt(i) != null)
{
canEquip = true;
if (allowedSlot.HasFlag(inv.SlotTypes[i]) && inv.Items[i] != null)
{
canEquip = false;
break;
}
canEquip = false;
break;
}
}
}
}
}
}
@@ -140,7 +140,7 @@ namespace Barotrauma
public override float GetPriority()
{
if (character.TeamID == Character.TeamType.FriendlyNPC && Enemy != null)
if (character.TeamID == CharacterTeamType.FriendlyNPC && Enemy != null)
{
if (Enemy.Submarine == null || (Enemy.Submarine.TeamID != character.TeamID && Enemy.Submarine != character.Submarine))
{
@@ -238,7 +238,9 @@ namespace Barotrauma
}
}
private bool IsLoaded(ItemComponent weapon) => weapon.HasRequiredContainedItems(character, addMessage: false);
private bool IsLoaded(ItemComponent weapon, bool checkContainedItems = true) =>
weapon.HasRequiredContainedItems(character, addMessage: false) &&
(!checkContainedItems || weapon.Item.OwnInventory == null || weapon.Item.OwnInventory.AllItems.Any(i => i.Condition > 0));
private bool TryArm()
{
@@ -260,21 +262,21 @@ namespace Barotrauma
// No weapons
break;
}
if (!character.Inventory.Items.Contains(Weapon) || WeaponComponent == null)
if (!character.Inventory.Contains(Weapon) || WeaponComponent == null)
{
// Not in the inventory anymore or cannot find the weapon component
allWeapons.Remove(WeaponComponent);
Weapon = null;
continue;
}
if (IsLoaded(WeaponComponent))
if (IsLoaded(WeaponComponent, checkContainedItems: true))
{
// All good, the weapon is loaded
break;
}
if (Reload(seekAmmo: false))
{
// All good, reloading successful
// All good, we can use the weapon.
break;
}
else
@@ -304,7 +306,7 @@ namespace Barotrauma
}
}
}
bool isAllowedToSeekWeapons = !EnemyIsClose() && character.TeamID != Character.TeamType.FriendlyNPC && IsOffensiveOrArrest;
bool isAllowedToSeekWeapons = !EnemyIsClose() && character.TeamID != CharacterTeamType.FriendlyNPC && IsOffensiveOrArrest;
if (!isAllowedToSeekWeapons)
{
if (WeaponComponent == null)
@@ -369,7 +371,7 @@ namespace Barotrauma
bool CheckWeapon(bool seekAmmo)
{
if (!character.Inventory.Items.Contains(Weapon) || WeaponComponent == null)
if (!character.Inventory.Contains(Weapon) || WeaponComponent == null)
{
// Not in the inventory anymore or cannot find the weapon component
return false;
@@ -564,21 +566,20 @@ namespace Barotrauma
container.ContainableItems.Any(containable => containable.Identifiers.Any(id => id.Equals(mobileBatteryTag))));
// If there's no such container, assume that the melee weapon can stun without a battery.
return containers.None() || containers.Any(container =>
(container as ItemContainer)?.Inventory.Items.Any(i => i != null && i.HasTag(mobileBatteryTag) && i.Condition > 0.0f) ?? false);
(container as ItemContainer)?.Inventory.AllItems.Any(i => i != null && i.HasTag(mobileBatteryTag) && i.Condition > 0.0f) ?? false);
}
}
private HashSet<ItemComponent> FindWeaponsFromInventory()
{
weapons.Clear();
foreach (var item in character.Inventory.Items)
foreach (var item in character.Inventory.AllItems)
{
if (item == null) { continue; }
if (ignoredWeapons.Contains(item)) { continue; }
GetWeapons(item, weapons);
if (item.OwnInventory != null)
{
item.OwnInventory.Items.ForEach(i => GetWeapons(i, weapons));
item.OwnInventory.AllItems.ForEach(i => GetWeapons(i, weapons));
}
}
return weapons;
@@ -598,7 +599,7 @@ namespace Barotrauma
private void Unequip()
{
if (!character.LockHands && character.SelectedItems.Contains(Weapon))
if (!character.LockHands && character.HeldItems.Contains(Weapon))
{
if (!Weapon.AllowedSlots.Contains(InvSlotType.Any) || !character.Inventory.TryPutItem(Weapon, character, new List<InvSlotType>() { InvSlotType.Any }))
{
@@ -617,7 +618,7 @@ namespace Barotrauma
if (!character.HasEquippedItem(Weapon))
{
Weapon.TryInteract(character, forceSelectKey: true);
var slots = Weapon.AllowedSlots.FindAll(s => s == InvSlotType.LeftHand || s == InvSlotType.RightHand || s == (InvSlotType.LeftHand | InvSlotType.RightHand));
var slots = Weapon.AllowedSlots.Where(s => s == InvSlotType.LeftHand || s == InvSlotType.RightHand || s == (InvSlotType.LeftHand | InvSlotType.RightHand));
if (character.Inventory.TryPutItem(Weapon, character, slots))
{
aimTimer = Rand.Range(0.5f, 1f);
@@ -651,7 +652,7 @@ namespace Barotrauma
}
else
{
retreatTarget = findSafety.FindBestHull(HumanAIController.VisibleHulls, allowChangingTheSubmarine: character.TeamID != Character.TeamType.FriendlyNPC);
retreatTarget = findSafety.FindBestHull(HumanAIController.VisibleHulls, allowChangingTheSubmarine: character.TeamID != CharacterTeamType.FriendlyNPC);
findHullTimer = findHullInterval * Rand.Range(0.9f, 1.1f);
}
}
@@ -724,7 +725,7 @@ namespace Barotrauma
}
else
{
if (character.TeamID == Character.TeamType.FriendlyNPC)
if (character.TeamID == CharacterTeamType.FriendlyNPC)
{
ItemPrefab prefab = ItemPrefab.Find(null, "handcuffs");
if (prefab != null)
@@ -769,9 +770,9 @@ namespace Barotrauma
#endif
}
// Confiscate stolen goods.
foreach (var item in Enemy.Inventory.Items)
foreach (var item in Enemy.Inventory.AllItemsMod)
{
if (item == null || item == handCuffs) { continue; }
if (item == handCuffs) { continue; }
if (item.StolenDuringRound)
{
item.Drop(character);
@@ -814,33 +815,50 @@ namespace Barotrauma
/// </summary>
private bool Reload(bool seekAmmo)
{
if (WeaponComponent == null) { return false; }
if (!WeaponComponent.requiredItems.ContainsKey(RelatedItem.RelationType.Contained)) { return false; }
var containedItems = Weapon.OwnInventory?.Items;
if (containedItems == null) { return true; }
// Drop empty ammo
foreach (Item containedItem in containedItems)
if (WeaponComponent == null) { return false; }
if (Weapon.OwnInventory == null) { return true; }
// Eject empty ammo
if (Weapon.OwnInventory.AllItems.Any(it => it.Condition <= 0.0f))
{
if (containedItem == null) { continue; }
if (containedItem.Condition <= 0)
foreach (Item containedItem in Weapon.OwnInventory.AllItemsMod)
{
containedItem.Drop(character);
if (containedItem.Condition <= 0)
{
if (character.Submarine == null)
{
// If we are outside of main sub, try to put the ammo in the inventory instead dropping it in the sea.
if (character.Inventory.TryPutItem(containedItem, character, CharacterInventory.anySlot))
{
continue;
}
}
containedItem.Drop(character);
}
}
}
RelatedItem item = null;
Item ammunition = null;
string[] ammunitionIdentifiers = null;
foreach (RelatedItem requiredItem in WeaponComponent.requiredItems[RelatedItem.RelationType.Contained])
if (WeaponComponent.requiredItems.ContainsKey(RelatedItem.RelationType.Contained))
{
ammunition = containedItems.FirstOrDefault(it => it != null && it.Condition > 0 && requiredItem.MatchesItem(it));
if (ammunition != null)
foreach (RelatedItem requiredItem in WeaponComponent.requiredItems[RelatedItem.RelationType.Contained])
{
// Ammunition still remaining
return true;
ammunition = Weapon.OwnInventory.AllItems.FirstOrDefault(it => it.Condition > 0 && requiredItem.MatchesItem(it));
if (ammunition != null)
{
// Ammunition still remaining
return true;
}
item = requiredItem;
ammunitionIdentifiers = requiredItem.Identifiers;
}
item = requiredItem;
ammunitionIdentifiers = requiredItem.Identifiers;
}
else if (WeaponComponent is MeleeWeapon meleeWeapon)
{
ammunitionIdentifiers = meleeWeapon.PreferredContainedItems;
}
// No ammo
if (ammunition == null)
{
@@ -67,14 +67,14 @@ namespace Barotrauma
}
if (item != null)
{
return container.Inventory.Items.Contains(item);
return container.Inventory.Contains(item);
}
else
{
int containedItemCount = 0;
foreach (Item i in container.Inventory.Items)
foreach (Item it in container.Inventory.AllItems)
{
if (i != null && CheckItem(i))
if (CheckItem(it))
{
containedItemCount++;
}
@@ -83,7 +83,7 @@ namespace Barotrauma
}
}
private bool CheckItem(Item i) => itemIdentifiers.Any(id => i.Prefab.Identifier == id || i.HasTag(id)) && i.ConditionPercentage >= ConditionLevel;
private bool CheckItem(Item i) => itemIdentifiers.Any(id => i.Prefab.Identifier == id || i.HasTag(id)) && i.ConditionPercentage >= ConditionLevel && !i.IsThisOrAnyContainerIgnoredByAI();
protected override void Act(float deltaTime)
{
@@ -102,11 +102,10 @@ namespace Barotrauma
}
if (character.CanInteractWith(container.Item, checkLinked: false))
{
if (RemoveEmpty)
if (RemoveEmpty && container.Inventory.AllItems.Any(it => it.Condition <= 0.0f))
{
foreach (var emptyItem in container.Inventory.Items)
foreach (var emptyItem in container.Inventory.AllItemsMod)
{
if (emptyItem == null) { continue; }
if (emptyItem.Condition <= 0)
{
emptyItem.Drop(character);
@@ -58,12 +58,17 @@ namespace Barotrauma
protected override void Act(float deltaTime)
{
Item itemToDecontain = targetItem ?? sourceContainer.Inventory.FindItem(i => itemIdentifiers.Any(id => i.Prefab.Identifier == id || i.HasTag(id)), recursive: false);
Item itemToDecontain = targetItem ?? sourceContainer.Inventory.FindItem(i => itemIdentifiers.Any(id => i.Prefab.Identifier == id || i.HasTag(id) && !i.IgnoreByAI), recursive: false);
if (itemToDecontain == null)
{
Abandon = true;
return;
}
if (itemToDecontain.IgnoreByAI)
{
Abandon = true;
return;
}
if (targetContainer == null)
{
if (sourceContainer == null)
@@ -77,7 +82,7 @@ namespace Barotrauma
return;
}
}
else if (targetContainer.Inventory.Items.Contains(itemToDecontain))
else if (targetContainer.Inventory.Contains(itemToDecontain))
{
IsCompleted = true;
return;
@@ -111,9 +111,10 @@ namespace Barotrauma
float xDist = Math.Abs(character.WorldPosition.X - fs.WorldPosition.X) - fs.DamageRange;
float yDist = Math.Abs(character.WorldPosition.Y - fs.WorldPosition.Y);
bool inRange = xDist + yDist < extinguisher.Range;
bool canSee = HumanAIController.VisibleHulls.Contains(fs.Hull) || character.CanSeeTarget(fs);
bool move = !inRange || !canSee;
if ((inRange && canSee) || useExtinquisherTimer > 0)
// Use the hull position, because the fire x pos is sometimes inside a wall -> the bot can't ever see it and continues running towards the wall.
ISpatialEntity lookTarget = character.CurrentHull == targetHull || character.CurrentHull.linkedTo.Contains(targetHull) ? targetHull : fs as ISpatialEntity;
bool move = !inRange || !character.CanSeeTarget(lookTarget);
if ((inRange && character.CanSeeTarget(lookTarget)) || useExtinquisherTimer > 0)
{
useExtinquisherTimer += deltaTime;
if (useExtinquisherTimer > 2.0f)
@@ -148,7 +149,7 @@ namespace Barotrauma
onAbandon: () => Abandon = true,
onCompleted: () => RemoveSubObjective(ref gotoObjective)))
{
gotoObjective.requiredCondition = () => HumanAIController.VisibleHulls.Contains(fs.Hull);
gotoObjective.requiredCondition = () => targetHull == null || character.CanSeeTarget(targetHull);
}
}
else
@@ -38,7 +38,6 @@ namespace Barotrauma
public static bool IsValidTarget(Hull hull, Character character)
{
if (hull == null) { return false; }
if (hull.IgnoreByAI) { return false; }
if (hull.FireSources.None()) { return false; }
if (hull.Submarine == null) { return false; }
if (character.Submarine == null) { return false; }
@@ -26,7 +26,7 @@ namespace Barotrauma
protected override AIObjective ObjectiveConstructor(Character target)
{
var combatObjective = new AIObjectiveCombat(character, target, AIObjectiveCombat.CombatMode.Offensive, objectiveManager, PriorityModifier);
if (character.TeamID == Character.TeamType.FriendlyNPC && target.TeamID == Character.TeamType.Team1 && GameMain.GameSession?.GameMode is CampaignMode campaign)
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)
@@ -1,5 +1,6 @@
using Barotrauma.Items.Components;
using Barotrauma.Extensions;
using System.Collections.Generic;
namespace Barotrauma
{
@@ -56,7 +57,7 @@ namespace Barotrauma
}
else
{
if (!DropEmptyTanks(character, targetItem, out Item[] containedItems))
if (!EjectEmptyTanks(character, targetItem, out var containedItems))
{
#if DEBUG
DebugConsole.ThrowError($"{character.Name}: AIObjectiveFindDivingGear failed - the item \"" + targetItem + "\" has no proper inventory");
@@ -70,8 +71,11 @@ namespace Barotrauma
// Seek oxygen that has min 10% condition left.
TryAddSubObjective(ref getOxygen, () =>
{
character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f);
return new AIObjectiveContainItem(character, OXYGEN_SOURCE, targetItem.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == Character.TeamType.FriendlyNPC)
if (!HumanAIController.HasItem(character, "oxygensource", out _, conditionPercentage: 10))
{
character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f);
}
return new AIObjectiveContainItem(character, OXYGEN_SOURCE, targetItem.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
{
AllowToFindDivingGear = false,
AllowDangerousPressure = true,
@@ -83,7 +87,7 @@ namespace Barotrauma
// Try to seek any oxygen sources.
TryAddSubObjective(ref getOxygen, () =>
{
return new AIObjectiveContainItem(character, OXYGEN_SOURCE, targetItem.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == Character.TeamType.FriendlyNPC)
return new AIObjectiveContainItem(character, OXYGEN_SOURCE, targetItem.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
{
AllowToFindDivingGear = false,
AllowDangerousPressure = true
@@ -100,18 +104,22 @@ namespace Barotrauma
/// <summary>
/// Returns false only when no inventory can be found from the item.
/// </summary>
public static bool DropEmptyTanks(Character actor, Item target, out Item[] containedItems)
public static bool EjectEmptyTanks(Character actor, Item target, out IEnumerable<Item> containedItems)
{
containedItems = target.OwnInventory?.Items;
if (containedItems == null)
containedItems = target.OwnInventory?.AllItems;
if (containedItems == null) { return false; }
foreach (Item containedItem in target.OwnInventory.AllItemsMod)
{
return false;
}
foreach (Item containedItem in containedItems)
{
if (containedItem == null) { continue; }
if (containedItem.Condition <= 0.0f)
{
if (actor.Submarine == null)
{
// If we are outside of main sub, try to put the tank in the inventory instead dropping it in the sea.
if (actor.Inventory.TryPutItem(containedItem, actor, CharacterInventory.anySlot))
{
continue;
}
}
containedItem.Drop(actor);
}
}
@@ -168,7 +168,7 @@ namespace Barotrauma
{
searchHullTimer = SearchHullInterval * Rand.Range(0.9f, 1.1f);
previousSafeHull = currentSafeHull;
currentSafeHull = FindBestHull(allowChangingTheSubmarine: character.TeamID != Character.TeamType.FriendlyNPC);
currentSafeHull = FindBestHull(allowChangingTheSubmarine: character.TeamID != CharacterTeamType.FriendlyNPC);
cannotFindSafeHull = currentSafeHull == null || HumanAIController.NeedsDivingGear(currentSafeHull, out _);
if (currentSafeHull == null)
{
@@ -359,7 +359,7 @@ namespace Barotrauma
hullSafety *= distanceFactor;
// If the target is not inside a friendly submarine, considerably reduce the hull safety.
// Intentionally exclude wrecks from this check
if (hull.Submarine.TeamID != character.TeamID && hull.Submarine.TeamID != Character.TeamType.FriendlyNPC)
if (hull.Submarine.TeamID != character.TeamID && hull.Submarine.TeamID != CharacterTeamType.FriendlyNPC)
{
hullSafety /= 10;
}
@@ -64,7 +64,7 @@ namespace Barotrauma
var weldingTool = character.Inventory.FindItemByTag("weldingequipment", true);
if (weldingTool == null)
{
TryAddSubObjective(ref getWeldingTool, () => new AIObjectiveGetItem(character, "weldingequipment", objectiveManager, equip: true, spawnItemIfNotFound: character.TeamID == Character.TeamType.FriendlyNPC),
TryAddSubObjective(ref getWeldingTool, () => new AIObjectiveGetItem(character, "weldingequipment", objectiveManager, equip: true, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC),
onAbandon: () =>
{
if (objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>())
@@ -78,8 +78,7 @@ namespace Barotrauma
}
else
{
var containedItems = weldingTool.OwnInventory?.Items;
if (containedItems == null)
if (weldingTool.OwnInventory == null)
{
#if DEBUG
DebugConsole.ThrowError($"{character.Name}: AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no proper inventory");
@@ -88,17 +87,20 @@ namespace Barotrauma
return;
}
// Drop empty tanks
foreach (Item containedItem in containedItems)
if (weldingTool.OwnInventory.AllItems.Any(it => it.Condition <= 0.0f))
{
if (containedItem == null) { continue; }
if (containedItem.Condition <= 0.0f)
foreach (Item containedItem in weldingTool.OwnInventory.AllItemsMod)
{
containedItem.Drop(character);
if (containedItem.Condition <= 0.0f)
{
containedItem.Drop(character);
}
}
}
if (containedItems.None(i => i != null && i.HasTag("weldingfuel") && i.Condition > 0.0f))
if (weldingTool.OwnInventory.AllItems.None(i => i.HasTag("weldingfuel") && i.Condition > 0.0f))
{
TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, "weldingfuel", weldingTool.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == Character.TeamType.FriendlyNPC),
TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, "weldingfuel", weldingTool.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC),
onAbandon: () => Abandon = true,
onCompleted: () => RemoveSubObjective(ref refuelObjective));
return;
@@ -177,7 +177,7 @@ namespace Barotrauma
}
else if (moveToTarget is Item parentItem)
{
canInteract = character.CanInteractWith(parentItem, out _, checkLinked: false);
canInteract = character.CanInteractWith(parentItem, checkLinked: false);
}
if (canInteract)
{
@@ -256,7 +256,7 @@ namespace Barotrauma
if (mySub == null) { continue; }
if (!AllowStealing)
{
if (character.TeamID == Character.TeamType.FriendlyNPC != item.SpawnedInOutpost) { continue; }
if (character.TeamID == CharacterTeamType.FriendlyNPC != item.SpawnedInOutpost) { continue; }
}
if (!CheckItem(item)) { continue; }
if (ignoredContainerIdentifiers != null && item.Container != null)
@@ -276,6 +276,10 @@ namespace Barotrauma
itemPriority = GetItemPriority(item);
}
Entity rootInventoryOwner = item.GetRootInventoryOwner();
if (rootInventoryOwner is Item ownerItem)
{
if (!ownerItem.IsInteractable(character)) { continue; }
}
Vector2 itemPos = (rootInventoryOwner ?? item).WorldPosition;
float yDist = Math.Abs(character.WorldPosition.Y - itemPos.Y);
yDist = yDist > 100 ? yDist * 5 : 0;
@@ -308,7 +312,7 @@ namespace Barotrauma
Entity.Spawner.AddToSpawnQueue(prefab, character.Inventory, onSpawned: (Item spawnedItem) =>
{
targetItem = spawnedItem;
if (character.TeamID == Character.TeamType.FriendlyNPC && (character.Submarine?.Info.IsOutpost ?? false))
if (character.TeamID == CharacterTeamType.FriendlyNPC && (character.Submarine?.Info.IsOutpost ?? false))
{
spawnedItem.SpawnedInOutpost = true;
}
@@ -347,7 +351,7 @@ namespace Barotrauma
private bool CheckItem(Item item)
{
if (item.NonInteractable) { return false; }
if (!item.IsInteractable(character)) { return false; }
if (item.IsThisOrAnyContainerIgnoredByAI()) { return false; }
if (ignoredItems.Contains(item)) { return false; };
if (item.Condition < TargetCondition) { return false; }
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -34,6 +35,9 @@ namespace Barotrauma
public float extraDistanceOutsideSub;
private float _closeEnough = 50;
private readonly float minDistance = 50;
private readonly float seekGapsInterval = 1;
private float seekGapsTimer;
/// <summary>
/// Display units
/// </summary>
@@ -116,6 +120,8 @@ namespace Barotrauma
return Priority;
}
private readonly float avoidLookAheadDistance = 5;
public AIObjectiveGoTo(ISpatialEntity target, Character character, AIObjectiveManager objectiveManager, bool repeat = false, bool getDivingGearIfNeeded = true, float priorityModifier = 1, float closeEnough = 0)
: base(character, objectiveManager, priorityModifier)
{
@@ -208,14 +214,14 @@ namespace Barotrauma
{
Abandon = true;
}
else if (waitUntilPathUnreachable < 0)
else if (SteeringManager == PathSteering && PathSteering.CurrentPath != null && PathSteering.CurrentPath.Unreachable && !PathSteering.IsPathDirty)
{
if (SteeringManager == PathSteering && PathSteering.CurrentPath != null && PathSteering.CurrentPath.Unreachable && !PathSteering.IsPathDirty)
SteeringManager.Reset();
if (waitUntilPathUnreachable < 0)
{
if (repeat)
{
SpeakCannotReach();
SteeringManager.Reset();
}
else
{
@@ -223,12 +229,7 @@ namespace Barotrauma
}
}
}
if (Abandon)
{
SpeakCannotReach();
SteeringManager.Reset();
}
else
if (!Abandon)
{
if (getDivingGearIfNeeded && !character.LockHands)
{
@@ -286,52 +287,134 @@ namespace Barotrauma
}
}
}
if (!character.AnimController.InWater)
if (character.AnimController.InWater)
{
useScooter = false;
checkScooterTimer = 0;
}
else if (checkScooterTimer <= 0)
{
useScooter = false;
checkScooterTimer = checkScooterTime;
string scooterTag = "scooter";
string batteryTag = "mobilebattery";
Item scooter = null;
bool isScooterEquipped = false;
float closeEnough = 250;
float squaredDistance = Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition);
bool shouldUseScooter = squaredDistance > closeEnough * closeEnough && (!mimic ||
(Target is Character targetCharacter && targetCharacter.HasEquippedItem(scooterTag, allowBroken: false)) || squaredDistance > Math.Pow(closeEnough * 2, 2));
if (HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> equippedScooters, batteryTag, requireEquipped: true))
if (character.CurrentHull == null)
{
scooter = equippedScooters.FirstOrDefault();
isScooterEquipped = scooter != null;
}
else if (shouldUseScooter && HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> scooters, batteryTag, requireEquipped: false))
{
scooter = scooters.FirstOrDefault();
if (scooter != null)
if (seekGapsTimer > 0)
{
isScooterEquipped = HumanAIController.TakeItem(scooter, character.Inventory, equip: true, dropOtherIfCannotMove: false, allowSwapping: true, storeUnequipped: false);
}
}
if (scooter != null && isScooterEquipped)
{
if (shouldUseScooter)
{
useScooter = true;
seekGapsTimer -= deltaTime;
}
else
{
// Unequip
character.Inventory.TryPutItem(scooter, character, CharacterInventory.anySlot);
SeekGaps(maxDistance: 500);
seekGapsTimer = seekGapsInterval * Rand.Range(0.1f, 1.1f);
if (TargetGap != null)
{
// Check that nothing is blocking the way
Vector2 rayStart = character.SimPosition;
Vector2 rayEnd = TargetGap.SimPosition;
if (TargetGap.Submarine != null && character.Submarine == null)
{
rayStart -= TargetGap.Submarine.SimPosition;
}
else if (TargetGap.Submarine == null && character.Submarine != null)
{
rayEnd -= character.Submarine.SimPosition;
}
var closestBody = Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true);
if (closestBody != null)
{
TargetGap = null;
}
}
}
}
else
{
TargetGap = null;
}
if (TargetGap != null)
{
if (TargetGap.FlowTargetHull != null && HumanAIController.SteerThroughGap(TargetGap, TargetGap.FlowTargetHull.WorldPosition, deltaTime))
{
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 1);
return;
}
else
{
TargetGap = null;
}
}
if (checkScooterTimer <= 0)
{
useScooter = false;
checkScooterTimer = checkScooterTime;
string scooterTag = "scooter";
string batteryTag = "mobilebattery";
Item scooter = null;
float closeEnough = 250;
float squaredDistance = Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition);
bool shouldUseScooter = squaredDistance > closeEnough * closeEnough && (!mimic ||
(Target is Character targetCharacter && targetCharacter.HasEquippedItem(scooterTag, allowBroken: false)) || squaredDistance > Math.Pow(closeEnough * 2, 2));
if (HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> equippedScooters, recursive: false, requireEquipped: true))
{
// Currently equipped scooter
scooter = equippedScooters.FirstOrDefault();
}
else if (shouldUseScooter)
{
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);
if (scooter != null && isScooterEquipped)
{
if (shouldUseScooter)
{
useScooter = true;
// Check the battery
if (scooter.ContainedItems.None(i => i.Condition > 0))
{
// Try to switch batteries
if (HumanAIController.HasItem(character, batteryTag, out IEnumerable<Item> batteries, conditionPercentage: 1, recursive: false))
{
scooter.ContainedItems.ForEachMod(emptyBattery => character.Inventory.TryPutItem(emptyBattery, character, CharacterInventory.anySlot));
if (!scooter.Combine(batteries.OrderByDescending(b => b.Condition).First(), character))
{
useScooter = false;
}
}
else
{
useScooter = false;
}
}
}
if (!useScooter)
{
// Unequip
character.Inventory.TryPutItem(scooter, character, CharacterInventory.anySlot);
}
}
}
else
{
checkScooterTimer -= deltaTime;
}
}
else
{
checkScooterTimer -= deltaTime;
TargetGap = null;
useScooter = false;
checkScooterTimer = 0;
}
if (SteeringManager == PathSteering)
{
@@ -347,7 +430,7 @@ namespace Barotrauma
nodeFilter,
CheckVisibility);
if (!isInside && PathSteering.CurrentPath == null || PathSteering.IsPathDirty || PathSteering.CurrentPath.Unreachable)
if (!isInside && (PathSteering.CurrentPath == null || PathSteering.IsPathDirty || PathSteering.CurrentPath.Unreachable))
{
if (useScooter)
{
@@ -358,7 +441,7 @@ namespace Barotrauma
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(Target.WorldPosition - character.WorldPosition));
if (character.AnimController.InWater)
{
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 2);
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 2);
}
}
}
@@ -378,7 +461,7 @@ namespace Barotrauma
SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Target), 10);
if (character.AnimController.InWater)
{
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 15);
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 15);
}
}
}
@@ -439,6 +522,27 @@ namespace Barotrauma
return null;
}
public Gap TargetGap { get; private set; }
private void SeekGaps(float maxDistance)
{
Gap selectedGap = null;
float selectedDistance = -1;
foreach (Gap gap in Gap.GapList)
{
if (gap.Open < 1) { continue; }
if (gap.FlowTargetHull == null) { continue; }
if (gap.Submarine != Target.Submarine) { continue; }
float distance = Vector2.DistanceSquared(character.WorldPosition, gap.WorldPosition);
if (distance > maxDistance * maxDistance) { continue; }
if (selectedGap == null || distance < selectedDistance)
{
selectedGap = gap;
selectedDistance = distance;
}
}
TargetGap = selectedGap;
}
public bool IsCloseEnough
{
get
@@ -507,6 +611,7 @@ namespace Barotrauma
{
PathSteering.ResetPath();
}
SpeakCannotReach();
base.OnAbandon();
}
@@ -530,6 +635,8 @@ namespace Barotrauma
{
base.Reset();
findDivingGear = null;
seekGapsTimer = 0;
TargetGap = null;
}
}
}
@@ -21,7 +21,7 @@ namespace Barotrauma
set
{
behavior = value;
if (behavior == BehaviorType.StayInHull && character.TeamID != Character.TeamType.FriendlyNPC)
if (behavior == BehaviorType.StayInHull && character.TeamID != CharacterTeamType.FriendlyNPC)
{
DebugConsole.NewMessage($"AIObjectiveIdle.BehaviorType.StayInHull is implemented only for outpost NPCs. Using passive behavior for {character.Name} ({character.Info.Job.Prefab.Identifier})", color: Color.Red);
behavior = BehaviorType.Passive;
@@ -203,7 +203,7 @@ namespace Barotrauma
if (currentTarget != null && !currentTargetIsInvalid)
{
if (character.TeamID == Character.TeamType.FriendlyNPC)
if (character.TeamID == CharacterTeamType.FriendlyNPC)
{
if (currentTarget.Submarine.TeamID != character.TeamID)
{
@@ -260,7 +260,7 @@ namespace Barotrauma
{
//choose a random available hull
currentTarget = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced);
bool isInWrongSub = character.TeamID == Character.TeamType.FriendlyNPC && character.Submarine.TeamID != character.TeamID;
bool isInWrongSub = character.TeamID == CharacterTeamType.FriendlyNPC && character.Submarine.TeamID != character.TeamID;
bool isCurrentHullAllowed = !isInWrongSub && !IsForbidden(character.CurrentHull);
var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsgStr: $"AIObjectiveIdle {character.DisplayName}", nodeFilter: node =>
{
@@ -402,6 +402,14 @@ namespace Barotrauma
PathSteering.Wander(deltaTime);
}
public void FaceTargetAndWait(ISpatialEntity target, float waitTime)
{
standStillTimer = waitTime;
HumanAIController.FaceTarget(target);
currentTarget = null;
SetTargetTimerHigh();
}
private void FindTargetHulls()
{
targetHulls.Clear();
@@ -411,7 +419,7 @@ namespace Barotrauma
if (HumanAIController.UnsafeHulls.Contains(hull)) { continue; }
if (hull.Submarine == null) { continue; }
if (character.Submarine == null) { break; }
if (character.TeamID == Character.TeamType.FriendlyNPC)
if (character.TeamID == CharacterTeamType.FriendlyNPC)
{
if (hull.Submarine.TeamID != character.TeamID)
{
@@ -127,10 +127,14 @@ namespace Barotrauma
{
var orderPrefab = Order.GetPrefab(autonomousObjective.identifier);
if (orderPrefab == null) { throw new Exception($"Could not find a matching prefab by the identifier: '{autonomousObjective.identifier}'"); }
var item = orderPrefab.MustSetTarget ? orderPrefab.GetMatchingItems(character.Submarine, mustBelongToPlayerSub: false, requiredTeam: character.Info.TeamID)?.GetRandom() : null;
Item item = null;
if (orderPrefab.MustSetTarget)
{
item = orderPrefab.GetMatchingItems(character.Submarine, mustBelongToPlayerSub: false, requiredTeam: character.Info.TeamID, interactableFor: character)?.GetRandom();
}
var order = new Order(orderPrefab, item ?? character.CurrentHull as Entity, orderPrefab.GetTargetItemComponent(item), orderGiver: character);
if (order == null) { continue; }
if (autonomousObjective.ignoreAtOutpost && Level.IsLoadedOutpost && character.TeamID != Character.TeamType.FriendlyNPC) { continue; }
if (autonomousObjective.ignoreAtOutpost && Level.IsLoadedOutpost && character.TeamID != CharacterTeamType.FriendlyNPC) { continue; }
var objective = CreateObjective(order, autonomousObjective.option, character, isAutonomous: true, autonomousObjective.priorityModifier);
if (objective != null && objective.CanBeCompleted)
{
@@ -273,7 +277,8 @@ namespace Barotrauma
CurrentOrder = objective;
}
public void SetOrder(Order order, string option, Character orderGiver)
private CoroutineHandle speakRoutine;
public void SetOrder(Order order, string option, Character orderGiver, bool speak)
{
if (character.IsDead)
{
@@ -294,6 +299,49 @@ namespace Barotrauma
{
// This should be redundant, because all the objectives are reset when they are selected as active.
CurrentOrder.Reset();
if (speak)
{
character.Speak(TextManager.Get("DialogAffirmative"), null, 1.0f);
if (speakRoutine != null)
{
CoroutineManager.StopCoroutines(speakRoutine);
}
speakRoutine = CoroutineManager.InvokeAfter(() =>
{
if (GameMain.GameSession == null || Level.Loaded == null) { return; }
if (CurrentOrder != null && character.SpeechImpediment < 100.0f)
{
if (CurrentOrder is AIObjectiveRepairItems repairItems && repairItems.Targets.None())
{
character.Speak(TextManager.Get("DialogNoRepairTargets"), null, 3.0f, "norepairtargets");
}
else if (CurrentOrder is AIObjectiveChargeBatteries chargeBatteries && chargeBatteries.Targets.None())
{
character.Speak(TextManager.Get("DialogNoBatteries"), null, 3.0f, "nobatteries");
}
else if (CurrentOrder is AIObjectiveExtinguishFires extinguishFires && extinguishFires.Targets.None())
{
character.Speak(TextManager.Get("DialogNoFire"), null, 3.0f, "nofire");
}
else if (CurrentOrder is AIObjectiveFixLeaks fixLeaks && fixLeaks.Targets.None())
{
character.Speak(TextManager.Get("DialogNoLeaks"), null, 3.0f, "noleaks");
}
else if (CurrentOrder is AIObjectiveFightIntruders fightIntruders && fightIntruders.Targets.None())
{
character.Speak(TextManager.Get("DialogNoEnemies"), null, 3.0f, "noenemies");
}
else if (CurrentOrder is AIObjectiveRescueAll rescueAll && rescueAll.Targets.None())
{
character.Speak(TextManager.Get("DialogNoRescueTargets"), null, 3.0f, "norescuetargets");
}
else if (CurrentOrder is AIObjectivePumpWater pumpWater && pumpWater.Targets.None())
{
character.Speak(TextManager.Get("DialogNoPumps"), null, 3.0f, "nopumps");
}
}
}, 3);
}
}
}
@@ -320,8 +368,7 @@ namespace Barotrauma
case "wait":
newObjective = new AIObjectiveGoTo(order.TargetSpatialEntity ?? character, character, this, repeat: true, priorityModifier: priorityModifier)
{
AllowGoingOutside = order.TargetSpatialEntity == null ? character.CurrentHull == null :
character.Submarine == null || character.Submarine != order.TargetSpatialEntity.Submarine
AllowGoingOutside = character.Submarine == null || (order.TargetSpatialEntity != null && character.Submarine != order.TargetSpatialEntity.Submarine)
};
break;
case "fixleaks":
@@ -345,7 +392,7 @@ namespace Barotrauma
case "pumpwater":
if (order.TargetItemComponent is Pump targetPump)
{
if (order.TargetItemComponent.Item.NonInteractable) { return null; }
if (!order.TargetItemComponent.Item.IsInteractable(character)) { return null; }
newObjective = new AIObjectiveOperateItem(targetPump, character, this, option, false, priorityModifier: priorityModifier)
{
IsLoop = true,
@@ -370,7 +417,7 @@ namespace Barotrauma
var steering = (order?.TargetEntity as Item)?.GetComponent<Steering>();
if (steering != null) { steering.PosToMaintain = steering.Item.Submarine?.WorldPosition; }
if (order.TargetItemComponent == null) { return null; }
if (order.TargetItemComponent.Item.NonInteractable) { return null; }
if (!order.TargetItemComponent.Item.IsInteractable(character)) { return null; }
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option,
requireEquip: false, useController: order.UseController, controller: order.ConnectedController, priorityModifier: priorityModifier)
{
@@ -403,11 +450,26 @@ namespace Barotrauma
};
break;
case "cleanupitems":
newObjective = new AIObjectiveCleanupItems(character, this, priorityModifier, order.TargetEntity as Item);
if (order.TargetEntity is Item targetItem)
{
if (targetItem.HasTag("allowcleanup") && targetItem.ParentInventory == null && targetItem.OwnInventory != null)
{
// Target all items inside the container
newObjective = new AIObjectiveCleanupItems(character, this, targetItem.OwnInventory.AllItems, priorityModifier);
}
else
{
newObjective = new AIObjectiveCleanupItems(character, this, targetItem, priorityModifier);
}
}
else
{
newObjective = new AIObjectiveCleanupItems(character, this, priorityModifier: priorityModifier);
}
break;
default:
if (order.TargetItemComponent == null) { return null; }
if (order.TargetItemComponent.Item.NonInteractable) { return null; }
if (!order.TargetItemComponent.Item.IsInteractable(character)) { return null; }
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option,
requireEquip: false, useController: order.UseController, controller: order.ConnectedController, priorityModifier: priorityModifier)
{
@@ -69,7 +69,7 @@ namespace Barotrauma
{
if (!isOrder)
{
if (reactor.LastUserWasPlayer && character.TeamID != Character.TeamType.FriendlyNPC ||
if (reactor.LastUserWasPlayer && character.TeamID != CharacterTeamType.FriendlyNPC ||
HumanAIController.IsTrueForAnyCrewMember(c =>
c.ObjectiveManager.CurrentOrder is AIObjectiveOperateItem operateOrder && operateOrder.GetTarget() == target))
{
@@ -101,7 +101,8 @@ namespace Barotrauma
targetItem.Submarine != character.Submarine && !isOrder ||
targetItem.CurrentHull.FireSources.Any() ||
HumanAIController.IsItemOperatedByAnother(target, out _) ||
Character.CharacterList.Any(c => c.CurrentHull == targetItem.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c)))
Character.CharacterList.Any(c => c.CurrentHull == targetItem.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))
|| component.Item.IgnoreByAI || (useController && controller.Item.IgnoreByAI))
{
Priority = 0;
}
@@ -137,7 +138,7 @@ namespace Barotrauma
throw new Exception("target null");
#endif
}
else if (target.Item.NonInteractable)
else if (!target.Item.IsInteractable(character))
{
Abandon = true;
}
@@ -157,21 +158,6 @@ namespace Barotrauma
Abandon = true;
return;
}
// If this is not an order...
if (objectiveManager.CurrentOrder != this)
{
// Don't allow to operate an item that someone with a better skills already operates
if (HumanAIController.IsItemOperatedByAnother(target, out _))
{
// Don't abandon
return;
}
if (component.Item.IgnoreByAI || (useController && controller.Item.IgnoreByAI))
{
Abandon = true;
return;
}
}
if (operateTarget != null)
{
if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.ObjectiveManager.GetActiveObjective() is AIObjectiveOperateItem operateObjective && operateObjective.operateTarget == operateTarget))
@@ -215,7 +201,7 @@ namespace Barotrauma
Abandon = true;
return;
}
else if (!character.Inventory.Items.Contains(component.Item))
else if (!character.Inventory.Contains(component.Item))
{
TryAddSubObjective(ref getItemObjective, () => new AIObjectiveGetItem(character, component.Item, objectiveManager, equip: true),
onAbandon: () => Abandon = true,
@@ -241,13 +227,14 @@ namespace Barotrauma
continue;
}
//equip slot already taken
if (character.Inventory.Items[i] != null)
var existingItem = character.Inventory.GetItemAt(i);
if (existingItem != null)
{
//try to put the item in an Any slot, and drop it if that fails
if (!character.Inventory.Items[i].AllowedSlots.Contains(InvSlotType.Any) ||
!character.Inventory.TryPutItem(character.Inventory.Items[i], character, new List<InvSlotType>() { InvSlotType.Any }))
if (!existingItem.AllowedSlots.Contains(InvSlotType.Any) ||
!character.Inventory.TryPutItem(existingItem, character, new List<InvSlotType>() { InvSlotType.Any }))
{
character.Inventory.Items[i].Drop(character);
existingItem.Drop(character);
}
}
if (character.Inventory.TryPutItem(component.Item, i, true, false, character))
@@ -28,7 +28,7 @@ namespace Barotrauma
{
if (pump == null) { return false; }
if (pump.Item.IgnoreByAI) { return false; }
if (pump.Item.NonInteractable) { return false; }
if (!pump.Item.IsInteractable(character)) { return false; }
if (pump.Item.HasTag("ballast")) { return false; }
if (pump.Item.Submarine == null) { return false; }
if (pump.Item.CurrentHull == null) { return false; }
@@ -33,10 +33,14 @@ namespace Barotrauma
public override float GetPriority()
{
if (!IsAllowed)
if (!IsAllowed || Item.IgnoreByAI)
{
Priority = 0;
Abandon = true;
if (IsRepairing())
{
Item.Repairables.ForEach(r => r.StopRepairing(character));
}
return Priority;
}
// TODO: priority list?
@@ -107,8 +111,7 @@ namespace Barotrauma
}
if (repairTool != null)
{
var containedItems = repairTool.Item.OwnInventory?.Items;
if (containedItems == null)
if (repairTool.Item.OwnInventory == null)
{
#if DEBUG
DebugConsole.ThrowError($"{character.Name}: AIObjectiveRepairItem failed - the item \"" + repairTool + "\" has no proper inventory");
@@ -116,27 +119,39 @@ namespace Barotrauma
Abandon = true;
return;
}
// Drop empty tanks
foreach (Item containedItem in containedItems)
// Eject empty tanks
if (repairTool.Item.OwnInventory.AllItems.Any(it => it.Condition <= 0.0f))
{
if (containedItem == null) { continue; }
if (containedItem.Condition <= 0.0f)
foreach (Item containedItem in repairTool.Item.OwnInventory.AllItemsMod)
{
containedItem.Drop(character);
if (containedItem == null) { continue; }
if (containedItem.Condition <= 0.0f)
{
if (character.Submarine == null)
{
// If we are outside of main sub, try to put the tank in the inventory instead dropping it in the sea.
if (character.Inventory.TryPutItem(containedItem, character, CharacterInventory.anySlot))
{
continue;
}
}
containedItem.Drop(character);
}
}
}
RelatedItem item = null;
Item fuel = null;
foreach (RelatedItem requiredItem in repairTool.requiredItems[RelatedItem.RelationType.Contained])
{
item = requiredItem;
fuel = containedItems.FirstOrDefault(it => it != null && it.Condition > 0.0f && requiredItem.MatchesItem(it));
fuel = repairTool.Item.OwnInventory.AllItems.FirstOrDefault(it => it.Condition > 0.0f && requiredItem.MatchesItem(it));
if (fuel != null) { break; }
}
if (fuel == null)
{
RemoveSubObjective(ref goToObjective);
TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, item.Identifiers, repairTool.Item.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == Character.TeamType.FriendlyNPC),
TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, item.Identifiers, repairTool.Item.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC),
onCompleted: () => RemoveSubObjective(ref refuelObjective),
onAbandon: () => Abandon = true);
return;
@@ -229,7 +244,7 @@ namespace Barotrauma
{
foreach (RelatedItem requiredItem in kvp.Value)
{
foreach (var item in character.Inventory.Items)
foreach (var item in character.Inventory.AllItems)
{
if (requiredItem.MatchesItem(item))
{
@@ -149,7 +149,7 @@ namespace Barotrauma
{
if (item == null) { return false; }
if (item.IgnoreByAI) { return false; }
if (item.NonInteractable) { return false; }
if (!item.IsInteractable(character)) { return false; }
if (item.IsFullCondition) { return false; }
if (item.CurrentHull == null) { return false; }
if (item.Submarine == null || character.Submarine == null) { return false; }
@@ -85,7 +85,7 @@ namespace Barotrauma
Item suit = suits.FirstOrDefault();
if (suit != null)
{
AIObjectiveFindDivingGear.DropEmptyTanks(character, suit, out _);
AIObjectiveFindDivingGear.EjectEmptyTanks(character, suit, out _);
}
}
else if (HumanAIController.HasItem(targetCharacter, AIObjectiveFindDivingGear.LIGHT_DIVING_GEAR, out IEnumerable<Item> masks, requireEquipped: true))
@@ -93,7 +93,7 @@ namespace Barotrauma
Item mask = masks.FirstOrDefault();
if (mask != null)
{
AIObjectiveFindDivingGear.DropEmptyTanks(character, mask, out _);
AIObjectiveFindDivingGear.EjectEmptyTanks(character, mask, out _);
}
}
bool ShouldRemoveDivingSuit() => targetCharacter.OxygenAvailable < CharacterHealth.InsufficientOxygenThreshold && targetCharacter.CurrentHull?.LethalPressure <= 0;
@@ -101,7 +101,7 @@ namespace Barotrauma
{
suits.ForEach(suit => suit.Drop(character));
}
else if (suits.Any() && suits.None(s => s.OwnInventory?.Items != null && s.OwnInventory.Items.Any(it => it != null && it.HasTag(AIObjectiveFindDivingGear.OXYGEN_SOURCE) && it.ConditionPercentage > 0)))
else if (suits.Any() && suits.None(s => s.OwnInventory?.AllItems != null && s.OwnInventory.AllItems.Any(it => it.HasTag(AIObjectiveFindDivingGear.OXYGEN_SOURCE) && it.ConditionPercentage > 0)))
{
// The target has a suit equipped with an empty oxygen tank.
// Can't remove the suit, because the target needs it.
@@ -331,9 +331,13 @@ namespace Barotrauma
character.DeselectCharacter();
RemoveSubObjective(ref getItemObjective);
TryAddSubObjective(ref getItemObjective,
constructor: () => new AIObjectiveGetItem(character, suitableItemIdentifiers.ToArray(), objectiveManager, equip: true, spawnItemIfNotFound: character.TeamID == Character.TeamType.FriendlyNPC),
constructor: () => new AIObjectiveGetItem(character, suitableItemIdentifiers.ToArray(), objectiveManager, equip: true, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC),
onCompleted: () => RemoveSubObjective(ref getItemObjective),
onAbandon: () => RemoveSubObjective(ref getItemObjective));
onAbandon: () =>
{
Abandon = true;
character.Speak(TextManager.GetWithVariable("dialogcannottreatpatient", "[name]", targetCharacter.DisplayName, formatCapitals: false), identifier: "cannottreatpatient", minDurationBetweenSimilar: 20.0f);
});
}
}
}
@@ -427,6 +431,13 @@ namespace Barotrauma
replaceOxygenObjective = null;
safeHull = null;
ignoreOxygen = false;
character.SelectedCharacter = null;
}
public override void OnDeselected()
{
character.SelectedCharacter = null;
base.OnDeselected();
}
}
}
@@ -14,7 +14,7 @@ namespace Barotrauma
public override bool AllowInAnySub => true;
private const float vitalityThreshold = 75;
private const float vitalityThresholdForOrders = 85;
private const float vitalityThresholdForOrders = 90;
public static float GetVitalityThreshold(AIObjectiveManager manager, Character character, Character target)
{
if (manager == null)
@@ -23,7 +23,10 @@ namespace Barotrauma
}
else
{
return character == target || manager.CurrentOrder is AIObjectiveRescueAll ? vitalityThresholdForOrders : vitalityThreshold;
// When targeting player characters, always treat them when ordered, else use the threshold so that minor/non-severe damage is ignored.
// If we ignore any damage when the player orders a bot to do healings, it's observed to cause confusion among the players.
// On the other hand, if the bots too eagerly heal characters when it's not nevessary, it's inefficient and can feel frustrating, because it can't be controlled.
return character == target || manager.CurrentOrder is AIObjectiveRescueAll ? (target.IsPlayer ? 100 : vitalityThresholdForOrders) : vitalityThreshold;
}
}