v1.0.13.1 (first post-1.0 patch)
This commit is contained in:
+103
-32
@@ -1,9 +1,11 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -31,14 +33,15 @@ namespace Barotrauma
|
||||
private ISpatialEntity moveToTarget;
|
||||
private bool isDoneSeeking;
|
||||
public Item TargetItem => targetItem;
|
||||
private int currSearchIndex;
|
||||
private int currentSearchIndex;
|
||||
public ImmutableHashSet<Identifier> ignoredContainerIdentifiers;
|
||||
public ImmutableHashSet<Identifier> ignoredIdentifiersOrTags;
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private float currItemPriority;
|
||||
private readonly bool checkInventory;
|
||||
|
||||
public static float DefaultReach = 100;
|
||||
public const float DefaultReach = 100;
|
||||
public const float MaxReach = 150;
|
||||
|
||||
public bool AllowToFindDivingGear { get; set; } = true;
|
||||
public bool MustBeSpecificItem { get; set; }
|
||||
@@ -76,7 +79,7 @@ namespace Barotrauma
|
||||
public AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip = true, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
currSearchIndex = -1;
|
||||
currentSearchIndex = 0;
|
||||
Equip = equip;
|
||||
originalTarget = targetItem;
|
||||
this.targetItem = targetItem;
|
||||
@@ -89,7 +92,7 @@ namespace Barotrauma
|
||||
public AIObjectiveGetItem(Character character, IEnumerable<Identifier> identifiersOrTags, AIObjectiveManager objectiveManager, bool equip = true, bool checkInventory = true, float priorityModifier = 1, bool spawnItemIfNotFound = false)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
currSearchIndex = -1;
|
||||
currentSearchIndex = 0;
|
||||
Equip = equip;
|
||||
this.spawnItemIfNotFound = spawnItemIfNotFound;
|
||||
this.checkInventory = checkInventory;
|
||||
@@ -125,7 +128,7 @@ namespace Barotrauma
|
||||
|
||||
public static Func<PathNode, bool> CreateEndNodeFilter(ISpatialEntity targetEntity)
|
||||
{
|
||||
return n => (n.Waypoint.Ladders == null || n.Waypoint.IsInWater) && Vector2.DistanceSquared(n.Waypoint.WorldPosition, targetEntity.WorldPosition) <= MathUtils.Pow2(DefaultReach);
|
||||
return n => (n.Waypoint.Ladders == null || n.Waypoint.IsInWater) && Vector2.DistanceSquared(n.Waypoint.WorldPosition, targetEntity.WorldPosition) <= MathUtils.Pow2(MaxReach);
|
||||
}
|
||||
|
||||
private bool CheckInventory()
|
||||
@@ -246,7 +249,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
character.SelectCharacter(c);
|
||||
canInteract = character.CanInteractWith(c, maxDist: DefaultReach);
|
||||
canInteract = character.CanInteractWith(c);
|
||||
character.DeselectCharacter();
|
||||
}
|
||||
}
|
||||
@@ -268,7 +271,7 @@ namespace Barotrauma
|
||||
|
||||
Inventory itemInventory = targetItem.ParentInventory;
|
||||
var slots = itemInventory?.FindIndices(targetItem);
|
||||
if (HumanAIController.TakeItem(targetItem, character.Inventory, Equip, Wear, storeUnequipped: true))
|
||||
if (HumanAIController.TakeItem(targetItem, character.Inventory, Equip, Wear, storeUnequipped: true, targetTags: IdentifiersOrTags))
|
||||
{
|
||||
if (TakeWholeStack && slots != null)
|
||||
{
|
||||
@@ -298,8 +301,11 @@ namespace Barotrauma
|
||||
if (!Equip)
|
||||
{
|
||||
// Try equipping and wearing the item
|
||||
Wear = true;
|
||||
Equip = true;
|
||||
if (!objectiveManager.HasActiveObjective<AIObjectiveCleanupItem>() && !objectiveManager.HasActiveObjective<AIObjectiveLoadItem>())
|
||||
{
|
||||
Wear = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
#if DEBUG
|
||||
@@ -342,6 +348,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private Stopwatch sw;
|
||||
private Stopwatch StopWatch => sw ??= new Stopwatch();
|
||||
private readonly List<(Item item, float priority)> itemCandidates = new List<(Item, float)>();
|
||||
private List<Item> itemList;
|
||||
private void FindTargetItem()
|
||||
{
|
||||
if (IdentifiersOrTags == null)
|
||||
@@ -349,13 +359,16 @@ namespace Barotrauma
|
||||
if (targetItem == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot find an item, because neither identifiers nor item was defined.", Color.Red);
|
||||
DebugConsole.AddWarning($"{character.Name}: Cannot find an item, because neither identifiers nor item was defined.");
|
||||
#endif
|
||||
Abandon = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (HumanAIController.DebugAI)
|
||||
{
|
||||
StopWatch.Restart();
|
||||
}
|
||||
float priority = Math.Clamp(objectiveManager.GetCurrentPriority(), 10, 100);
|
||||
if (!CheckPathForEachItem)
|
||||
{
|
||||
@@ -366,12 +379,22 @@ namespace Barotrauma
|
||||
CheckPathForEachItem = priority >= AIObjectiveManager.LowestOrderPriority && (objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>() || objectiveManager.CurrentOrder is AIObjectiveGoTo gotoOrder && gotoOrder.IsFollowOrderObjective);
|
||||
}
|
||||
bool checkPath = CheckPathForEachItem;
|
||||
bool hasCalledPathFinder = false;
|
||||
int itemsPerFrame = (int)priority;
|
||||
for (int i = 0; i < itemsPerFrame && currSearchIndex < Item.ItemList.Count - 1; i++)
|
||||
// Reset if the character has switched subs.
|
||||
if (itemList != null && !character.Submarine.IsEntityFoundOnThisSub(itemList.FirstOrDefault(), includingConnectedSubs: true))
|
||||
{
|
||||
currSearchIndex++;
|
||||
var item = Item.ItemList[currSearchIndex];
|
||||
currentSearchIndex = 0;
|
||||
}
|
||||
if (currentSearchIndex == 0)
|
||||
{
|
||||
itemCandidates.Clear();
|
||||
itemList = character.Submarine.GetItems(alsoFromConnectedSubs: true);
|
||||
}
|
||||
int itemsPerFrame = (int)MathHelper.Lerp(30, 300, MathUtils.InverseLerp(10, 100, priority));
|
||||
int checkedItems = 0;
|
||||
for (int i = 0; i < itemsPerFrame && currentSearchIndex < itemList.Count; i++, currentSearchIndex++)
|
||||
{
|
||||
checkedItems++;
|
||||
var item = itemList[currentSearchIndex];
|
||||
Submarine itemSub = item.Submarine ?? item.ParentInventory?.Owner?.Submarine;
|
||||
if (itemSub == null) { continue; }
|
||||
Submarine mySub = character.Submarine;
|
||||
@@ -395,8 +418,6 @@ namespace Barotrauma
|
||||
if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; }
|
||||
}
|
||||
}
|
||||
// Don't allow going into another sub, unless it's connected and of the same team and type.
|
||||
if (!character.Submarine.IsEntityFoundOnThisSub(item, includingConnectedSubs: true)) { continue; }
|
||||
if (character.IsItemTakenBySomeoneElse(item)) { continue; }
|
||||
if (item.ParentInventory is ItemInventory itemInventory)
|
||||
{
|
||||
@@ -411,11 +432,14 @@ namespace Barotrauma
|
||||
if (rootInventoryOwner is Item ownerItem)
|
||||
{
|
||||
if (!ownerItem.IsInteractable(character)) { continue; }
|
||||
if (!(ownerItem.GetComponent<ItemContainer>()?.HasRequiredItems(character, addMessage: false) ?? true)) { continue; }
|
||||
//the item is inside an item inside an item (e.g. fuel tank in a welding tool in a cabinet -> reduce priority to prefer items that aren't inside a tool)
|
||||
if (ownerItem != item.Container)
|
||||
if (ownerItem != item)
|
||||
{
|
||||
itemPriority *= 0.1f;
|
||||
if (!(ownerItem.GetComponent<ItemContainer>()?.HasRequiredItems(character, addMessage: false) ?? true)) { continue; }
|
||||
//the item is inside an item inside an item (e.g. fuel tank in a welding tool in a cabinet -> reduce priority to prefer items that aren't inside a tool)
|
||||
if (ownerItem != item.Container)
|
||||
{
|
||||
itemPriority *= 0.1f;
|
||||
}
|
||||
}
|
||||
}
|
||||
Vector2 itemPos = (rootInventoryOwner ?? item).WorldPosition;
|
||||
@@ -463,22 +487,69 @@ namespace Barotrauma
|
||||
{
|
||||
itemPriority *= item.Condition / item.MaxCondition;
|
||||
}
|
||||
if (checkPath)
|
||||
{
|
||||
itemCandidates.Add((item, itemPriority));
|
||||
}
|
||||
// Ignore if the item has a lower priority than the currently selected one
|
||||
if (itemPriority < currItemPriority) { continue; }
|
||||
if (!hasCalledPathFinder && PathSteering != null && checkPath)
|
||||
if (EvaluateCombatPriority && itemPriority <= 0)
|
||||
{
|
||||
hasCalledPathFinder = true;
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, item.SimPosition, character.Submarine, errorMsgStr: $"AIObjectiveGetItem {character.DisplayName}", nodeFilter: node => node.Waypoint.CurrentHull != null);
|
||||
if (path.Unreachable) { continue; }
|
||||
// Not good enough
|
||||
continue;
|
||||
}
|
||||
currItemPriority = itemPriority;
|
||||
targetItem = item;
|
||||
moveToTarget = rootInventoryOwner ?? item;
|
||||
}
|
||||
if (currSearchIndex >= Item.ItemList.Count - 1)
|
||||
if (currentSearchIndex >= itemList.Count - 1)
|
||||
{
|
||||
isDoneSeeking = true;
|
||||
if (targetItem == null)
|
||||
}
|
||||
if (checkedItems > 0)
|
||||
{
|
||||
if (isDoneSeeking && itemCandidates.Any())
|
||||
{
|
||||
itemCandidates.Sort((x, y) => y.priority.CompareTo(x.priority));
|
||||
}
|
||||
if (HumanAIController.DebugAI && targetItem != null && StopWatch.ElapsedMilliseconds > 2)
|
||||
{
|
||||
var msg = $"Went through {checkedItems} of total {itemList.Count} items. Found item {targetItem.Name} in {StopWatch.ElapsedMilliseconds} ms. Completed: {isDoneSeeking}";
|
||||
if (StopWatch.ElapsedMilliseconds > 5)
|
||||
{
|
||||
DebugConsole.ThrowError(msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
// An occasional warning now and then can be ignored, but multiple at the same time might indicate a performance issue.
|
||||
DebugConsole.AddWarning(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isDoneSeeking)
|
||||
{
|
||||
if (PathSteering == null)
|
||||
{
|
||||
itemCandidates.Clear();
|
||||
}
|
||||
if (itemCandidates.Any())
|
||||
{
|
||||
if (itemCandidates.FirstOrDefault() is { } itemCandidate)
|
||||
{
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, itemCandidate.item.SimPosition, character.Submarine, errorMsgStr: $"AIObjectiveGetItem {character.DisplayName}", nodeFilter: node => node.Waypoint.CurrentHull != null);
|
||||
if (path.Unreachable)
|
||||
{
|
||||
// Remove the invalid candidates and continue on the next frame.
|
||||
itemCandidates.Remove(itemCandidate);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The path was valid -> we are done.
|
||||
itemCandidates.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetItem == null && itemCandidates.None())
|
||||
{
|
||||
if (spawnItemIfNotFound)
|
||||
{
|
||||
@@ -569,11 +640,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (!item.HasAccess(character)) { return false; }
|
||||
if (ignoredItems.Contains(item)) { return false; };
|
||||
if (ignoredIdentifiersOrTags != null && CheckItemIdentifiersOrTags(item, ignoredIdentifiersOrTags)) { return false; }
|
||||
if (ignoredIdentifiersOrTags != null && item.HasIdentifierOrTags(ignoredIdentifiersOrTags)) { return false; }
|
||||
if (item.Condition < TargetCondition) { return false; }
|
||||
if (ItemFilter != null && !ItemFilter(item)) { return false; }
|
||||
if (RequireNonEmpty && item.Components.Any(i => !i.IsNotEmpty(character))) { return false; }
|
||||
return CheckItemIdentifiersOrTags(item, IdentifiersOrTags) || (AllowVariants && !item.Prefab.VariantOf.IsEmpty && IdentifiersOrTags.Contains(item.Prefab.VariantOf));
|
||||
if (RequireNonEmpty && item.Components.Any(i => i.IsEmpty(character))) { return false; }
|
||||
return item.HasIdentifierOrTags(IdentifiersOrTags) || (AllowVariants && !item.Prefab.VariantOf.IsEmpty && IdentifiersOrTags.Contains(item.Prefab.VariantOf));
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
@@ -591,7 +662,7 @@ namespace Barotrauma
|
||||
targetItem = originalTarget;
|
||||
moveToTarget = targetItem?.GetRootInventoryOwner();
|
||||
isDoneSeeking = false;
|
||||
currSearchIndex = 0;
|
||||
currentSearchIndex = 0;
|
||||
currItemPriority = 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user