Unstable 0.15.15.0 (and the one before it I forgor)
This commit is contained in:
@@ -93,13 +93,6 @@ namespace Barotrauma
|
||||
_abandon = value;
|
||||
if (_abandon)
|
||||
{
|
||||
#if DEBUG
|
||||
if (HumanAIController.debugai && objectiveManager.IsOrder(this) && !objectiveManager.IsCurrentOrder<AIObjectiveGoTo>() && !objectiveManager.IsCurrentOrder<AIObjectiveReturn>())
|
||||
{
|
||||
// TODO: dismiss
|
||||
throw new Exception("Order abandoned!");
|
||||
}
|
||||
#endif
|
||||
OnAbandon();
|
||||
}
|
||||
}
|
||||
@@ -247,7 +240,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
protected bool IsAllowed
|
||||
public bool IsAllowed
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -271,7 +264,7 @@ namespace Barotrauma
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = !isOrder;
|
||||
Abandon = true;
|
||||
return Priority;
|
||||
}
|
||||
if (isOrder)
|
||||
|
||||
+1
-1
@@ -94,7 +94,7 @@ namespace Barotrauma
|
||||
if (item == null) { return false; }
|
||||
if (item.IgnoreByAI(character)) { return false; }
|
||||
if (!item.IsInteractable(character)) { return false; }
|
||||
if (item.SpawnedInOutpost) { return false; }
|
||||
if ((item.SpawnedInCurrentOutpost && !item.AllowStealing) == character.IsOnPlayerTeam) { return false; }
|
||||
if (item.ParentInventory != null)
|
||||
{
|
||||
if (item.Container == null)
|
||||
|
||||
+30
-34
@@ -294,10 +294,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
if (character.LockHands || Enemy == null)
|
||||
@@ -325,7 +321,7 @@ namespace Barotrauma
|
||||
Weapon = null;
|
||||
continue;
|
||||
}
|
||||
if (IsLoaded(WeaponComponent, checkContainedItems: true))
|
||||
if (WeaponComponent.IsLoaded(character))
|
||||
{
|
||||
// All good, the weapon is loaded
|
||||
break;
|
||||
@@ -380,6 +376,7 @@ namespace Barotrauma
|
||||
constructor: () => new AIObjectiveGetItem(character, "weapon", objectiveManager, equip: true, checkInventory: false)
|
||||
{
|
||||
AllowStealing = HumanAIController.IsMentallyUnstable,
|
||||
EvaluateCombatPriority = false, // Use a custom formula instead
|
||||
GetItemPriority = i =>
|
||||
{
|
||||
if (Weapon != null && (i == Weapon || i.Prefab.Identifier == Weapon.Prefab.Identifier)) { return 0; }
|
||||
@@ -433,7 +430,7 @@ namespace Barotrauma
|
||||
// Not in the inventory anymore or cannot find the weapon component
|
||||
return false;
|
||||
}
|
||||
if (!IsLoaded(WeaponComponent))
|
||||
if (!WeaponComponent.IsLoaded(character))
|
||||
{
|
||||
// Try reloading (and seek ammo)
|
||||
if (!Reload(seekAmmo))
|
||||
@@ -475,7 +472,7 @@ namespace Barotrauma
|
||||
foreach (var weapon in weaponList)
|
||||
{
|
||||
float priority = weapon.CombatPriority;
|
||||
if (!IsLoaded(weapon))
|
||||
if (!weapon.IsLoaded(character))
|
||||
{
|
||||
if (weapon is RangedWeapon && enemyIsClose)
|
||||
{
|
||||
@@ -564,31 +561,6 @@ namespace Barotrauma
|
||||
}
|
||||
return weaponComponent.Item;
|
||||
|
||||
static Attack GetAttackDefinition(ItemComponent weapon)
|
||||
{
|
||||
Attack attack = null;
|
||||
if (weapon is MeleeWeapon meleeWeapon)
|
||||
{
|
||||
attack = meleeWeapon.Attack;
|
||||
}
|
||||
else if (weapon is RangedWeapon rangedWeapon)
|
||||
{
|
||||
attack = rangedWeapon.FindProjectile(triggerOnUseOnContainers: false)?.Attack;
|
||||
}
|
||||
return attack;
|
||||
}
|
||||
|
||||
static float GetLethalDamage(ItemComponent weapon)
|
||||
{
|
||||
float lethalDmg = 0;
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
if (attack != null)
|
||||
{
|
||||
lethalDmg = attack.GetTotalDamage();
|
||||
}
|
||||
return lethalDmg;
|
||||
}
|
||||
|
||||
float ApproximateStunDamage(ItemComponent weapon, Attack attack)
|
||||
{
|
||||
// Try to reduce the priority using the actual damage values and status effects.
|
||||
@@ -628,6 +600,31 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static float GetLethalDamage(ItemComponent weapon)
|
||||
{
|
||||
float lethalDmg = 0;
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
if (attack != null)
|
||||
{
|
||||
lethalDmg = attack.GetTotalDamage();
|
||||
}
|
||||
return lethalDmg;
|
||||
}
|
||||
|
||||
private static Attack GetAttackDefinition(ItemComponent weapon)
|
||||
{
|
||||
Attack attack = null;
|
||||
if (weapon is MeleeWeapon meleeWeapon)
|
||||
{
|
||||
attack = meleeWeapon.Attack;
|
||||
}
|
||||
else if (weapon is RangedWeapon rangedWeapon)
|
||||
{
|
||||
attack = rangedWeapon.FindProjectile(triggerOnUseOnContainers: false)?.Attack;
|
||||
}
|
||||
return attack;
|
||||
}
|
||||
|
||||
private HashSet<ItemComponent> FindWeaponsFromInventory()
|
||||
{
|
||||
weapons.Clear();
|
||||
@@ -788,7 +785,6 @@ namespace Barotrauma
|
||||
{
|
||||
UsePathingOutside = false,
|
||||
IgnoreIfTargetDead = true,
|
||||
DialogueIdentifier = "dialogcannotreachtarget",
|
||||
TargetName = Enemy.DisplayName,
|
||||
AlwaysUseEuclideanDistance = false
|
||||
},
|
||||
@@ -812,7 +808,7 @@ namespace Barotrauma
|
||||
ItemPrefab prefab = ItemPrefab.Find(null, "handcuffs");
|
||||
if (prefab != null)
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(prefab, character.Inventory, onSpawned: (Item i) => i.SpawnedInOutpost = true);
|
||||
Entity.Spawner.AddToSpawnQueue(prefab, character.Inventory, onSpawned: (Item i) => i.SpawnedInCurrentOutpost = true);
|
||||
}
|
||||
}
|
||||
RemoveFollowTarget();
|
||||
|
||||
-1
@@ -144,7 +144,6 @@ namespace Barotrauma
|
||||
{
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(container.Item, character, objectiveManager, getDivingGearIfNeeded: AllowToFindDivingGear)
|
||||
{
|
||||
DialogueIdentifier = "dialogcannotreachtarget",
|
||||
TargetName = container.Item.Name,
|
||||
AbortCondition = obj =>
|
||||
container?.Item == null || container.Item.Removed || container.Item.IsThisOrAnyContainerIgnoredByAI(character) ||
|
||||
|
||||
+9
-4
@@ -19,10 +19,15 @@ namespace Barotrauma
|
||||
private AIObjectiveContainItem getOxygen;
|
||||
private Item targetItem;
|
||||
|
||||
public static float MIN_OXYGEN = 10;
|
||||
public static string HEAVY_DIVING_GEAR = "deepdiving";
|
||||
public static string LIGHT_DIVING_GEAR = "lightdiving";
|
||||
public static string OXYGEN_SOURCE = "oxygensource";
|
||||
public const float MIN_OXYGEN = 10;
|
||||
|
||||
public const string HEAVY_DIVING_GEAR = "deepdiving";
|
||||
public const string LIGHT_DIVING_GEAR = "lightdiving";
|
||||
/// <summary>
|
||||
/// Diving gear that's suitable for wearing indoors (-> the bots don't try to unequip it when they don't need diving gear)
|
||||
/// </summary>
|
||||
public const string DIVING_GEAR_WEARABLE_INDOORS = "divinggear_wearableindoors";
|
||||
public const string OXYGEN_SOURCE = "oxygensource";
|
||||
|
||||
protected override bool CheckObjectiveSpecific() => targetItem != null && character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.Head);
|
||||
|
||||
|
||||
+11
-4
@@ -46,10 +46,17 @@ namespace Barotrauma
|
||||
}
|
||||
if (character.CurrentHull == null)
|
||||
{
|
||||
Priority = (objectiveManager.IsCurrentOrder<AIObjectiveGoTo>() ||
|
||||
objectiveManager.IsCurrentOrder<AIObjectiveReturn>() ||
|
||||
objectiveManager.Objectives.Any(o => o.Priority > 0 && o is AIObjectiveCombat))
|
||||
&& HumanAIController.HasDivingSuit(character) ? 0 : 100;
|
||||
if (!character.NeedsAir)
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Priority = (objectiveManager.IsCurrentOrder<AIObjectiveGoTo>() ||
|
||||
objectiveManager.IsCurrentOrder<AIObjectiveReturn>() ||
|
||||
objectiveManager.Objectives.Any(o => o.Priority > 0 && o is AIObjectiveCombat))
|
||||
&& HumanAIController.HasDivingSuit(character) ? 0 : 100;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+125
-59
@@ -1,6 +1,7 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
@@ -11,6 +12,7 @@ namespace Barotrauma
|
||||
public override string Identifier { get; set; } = "get item";
|
||||
|
||||
public override bool AbandonWhenCannotCompleteSubjectives => false;
|
||||
public override bool AllowMultipleInstances => true;
|
||||
|
||||
public HashSet<Item> ignoredItems = new HashSet<Item>();
|
||||
|
||||
@@ -19,7 +21,7 @@ namespace Barotrauma
|
||||
public float TargetCondition { get; set; } = 1;
|
||||
public bool AllowDangerousPressure { get; set; }
|
||||
|
||||
private readonly string[] identifiersOrTags;
|
||||
private 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;
|
||||
@@ -31,6 +33,7 @@ namespace Barotrauma
|
||||
public Item TargetItem => targetItem;
|
||||
private int currSearchIndex;
|
||||
public string[] ignoredContainerIdentifiers;
|
||||
public string[] ignoredIdentifiersOrTags;
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private float currItemPriority;
|
||||
private readonly bool checkInventory;
|
||||
@@ -51,6 +54,10 @@ namespace Barotrauma
|
||||
public bool AllowVariants { get; set; }
|
||||
public bool Equip { get; set; }
|
||||
public bool Wear { get; set; }
|
||||
public bool RequireLoaded { get; set; }
|
||||
public bool EvaluateCombatPriority { get; set; }
|
||||
public bool CheckPathForEachItem { get; set; }
|
||||
public bool SpeakIfFails { get; set; }
|
||||
|
||||
public InvSlotType? EquipSlotType { get; set; }
|
||||
|
||||
@@ -67,18 +74,41 @@ namespace Barotrauma
|
||||
public AIObjectiveGetItem(Character character, string identifierOrTag, AIObjectiveManager objectiveManager, bool equip = true, bool checkInventory = true, float priorityModifier = 1, bool spawnItemIfNotFound = false)
|
||||
: this(character, new string[] { identifierOrTag }, objectiveManager, equip, checkInventory, priorityModifier, spawnItemIfNotFound) { }
|
||||
|
||||
public AIObjectiveGetItem(Character character, string[] identifiersOrTags, AIObjectiveManager objectiveManager, bool equip = true, bool checkInventory = true, float priorityModifier = 1, bool spawnItemIfNotFound = false)
|
||||
public AIObjectiveGetItem(Character character, IEnumerable<string> identifiersOrTags, AIObjectiveManager objectiveManager, bool equip = true, bool checkInventory = true, float priorityModifier = 1, bool spawnItemIfNotFound = false)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
currSearchIndex = -1;
|
||||
Equip = equip;
|
||||
this.identifiersOrTags = identifiersOrTags;
|
||||
this.spawnItemIfNotFound = spawnItemIfNotFound;
|
||||
for (int i = 0; i < identifiersOrTags.Length; i++)
|
||||
{
|
||||
identifiersOrTags[i] = identifiersOrTags[i].ToLowerInvariant();
|
||||
}
|
||||
this.checkInventory = checkInventory;
|
||||
this.identifiersOrTags = ParseGearTags(identifiersOrTags).ToImmutableArray();
|
||||
ignoredIdentifiersOrTags = ParseIgnoredTags(identifiersOrTags).ToArray();
|
||||
}
|
||||
|
||||
public static IEnumerable<string> ParseGearTags(IEnumerable<string> identifiersOrTags)
|
||||
{
|
||||
var tags = new List<string>();
|
||||
foreach (string tag in identifiersOrTags)
|
||||
{
|
||||
if (!tag.Contains('!'))
|
||||
{
|
||||
tags.Add(tag.ToLowerInvariant());
|
||||
}
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
public static IEnumerable<string> ParseIgnoredTags(IEnumerable<string> identifiersOrTags)
|
||||
{
|
||||
var ignoredTags = new List<string>();
|
||||
foreach (string tag in identifiersOrTags)
|
||||
{
|
||||
if (tag.Contains('!'))
|
||||
{
|
||||
ignoredTags.Add(tag.Remove("!").ToLowerInvariant());
|
||||
}
|
||||
}
|
||||
return ignoredTags;
|
||||
}
|
||||
|
||||
private bool CheckInventory()
|
||||
@@ -219,6 +249,13 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Equip)
|
||||
{
|
||||
// Try equipping and wearing the item
|
||||
Wear = true;
|
||||
Equip = true;
|
||||
return;
|
||||
}
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Failed to equip/move the item '{targetItem.Name}' into the character inventory. Aborting.", Color.Red);
|
||||
#endif
|
||||
@@ -243,6 +280,10 @@ namespace Barotrauma
|
||||
{
|
||||
// Try again
|
||||
ignoredItems.Add(targetItem);
|
||||
if (targetItem != moveToTarget && moveToTarget is Item item)
|
||||
{
|
||||
ignoredItems.Add(item);
|
||||
}
|
||||
ResetInternal();
|
||||
}
|
||||
else
|
||||
@@ -269,7 +310,15 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
float priority = Math.Clamp(objectiveManager.GetCurrentPriority(), 10, 100);
|
||||
bool checkPath = priority >= AIObjectiveManager.LowestOrderPriority && (objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>() || objectiveManager.CurrentOrder is AIObjectiveGoTo gotoOrder && gotoOrder.isFollowOrderObjective);
|
||||
if (!CheckPathForEachItem)
|
||||
{
|
||||
// While following the player, let's ensure that there's a valid path to the target before accepting it.
|
||||
// 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);
|
||||
}
|
||||
bool checkPath = CheckPathForEachItem;
|
||||
bool hasCalledPathFinder = false;
|
||||
int itemsPerFrame = (int)priority;
|
||||
for (int i = 0; i < itemsPerFrame && currSearchIndex < Item.ItemList.Count - 1; i++)
|
||||
@@ -280,14 +329,20 @@ namespace Barotrauma
|
||||
if (itemSub == null) { continue; }
|
||||
Submarine mySub = character.Submarine;
|
||||
if (mySub == null) { continue; }
|
||||
if (!checkInventory)
|
||||
{
|
||||
// Ignore items in the inventory when defined not to check it.
|
||||
if (item.IsOwnedBy(character)) { continue; }
|
||||
}
|
||||
if (!AllowStealing)
|
||||
{
|
||||
if (character.TeamID == CharacterTeamType.FriendlyNPC != item.SpawnedInOutpost) { continue; }
|
||||
if (character.TeamID == CharacterTeamType.FriendlyNPC != item.SpawnedInCurrentOutpost) { continue; }
|
||||
}
|
||||
if (!CheckItem(item)) { continue; }
|
||||
if (item.Container != null)
|
||||
{
|
||||
if (item.Container.HasTag("donttakeitems")) { continue; }
|
||||
if (ignoredItems.Contains(item.Container)) { continue; }
|
||||
if (ignoredContainerIdentifiers != null)
|
||||
{
|
||||
if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; }
|
||||
@@ -315,17 +370,51 @@ namespace Barotrauma
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - itemPos.Y);
|
||||
yDist = yDist > 100 ? yDist * 5 : 0;
|
||||
float dist = Math.Abs(character.WorldPosition.X - itemPos.X) + yDist;
|
||||
float distanceFactor = MathHelper.Lerp(1, 0, MathUtils.InverseLerp(0, 10000, dist));
|
||||
float minDistFactor = EvaluateCombatPriority ? 0.1f : 0;
|
||||
float distanceFactor = MathHelper.Lerp(1, minDistFactor, MathUtils.InverseLerp(100, 10000, dist));
|
||||
itemPriority *= distanceFactor;
|
||||
itemPriority *= item.Condition / item.MaxCondition;
|
||||
if (EvaluateCombatPriority)
|
||||
{
|
||||
var mw = item.GetComponent<MeleeWeapon>();
|
||||
var rw = item.GetComponent<RangedWeapon>();
|
||||
float combatFactor = 0;
|
||||
if (mw != null)
|
||||
{
|
||||
if (mw.CombatPriority > 0)
|
||||
{
|
||||
combatFactor = mw.CombatPriority / 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The combat factor of items with zero combat priority is not allowed to be greater than 0.1f
|
||||
combatFactor = Math.Min(AIObjectiveCombat.GetLethalDamage(mw) / 1000, 0.1f);
|
||||
}
|
||||
}
|
||||
else if (rw != null)
|
||||
{
|
||||
if (rw.CombatPriority > 0)
|
||||
{
|
||||
combatFactor = rw.CombatPriority / 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
combatFactor = Math.Min(AIObjectiveCombat.GetLethalDamage(rw) / 1000, 0.1f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
combatFactor = Math.Min(item.Components.Sum(ic => AIObjectiveCombat.GetLethalDamage(ic)) / 1000, 0.1f);
|
||||
}
|
||||
itemPriority *= combatFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
itemPriority *= item.Condition / item.MaxCondition;
|
||||
}
|
||||
// Ignore if the item has a lower priority than the currently selected one
|
||||
if (itemPriority < currItemPriority) { continue; }
|
||||
if (!hasCalledPathFinder && PathSteering != null && checkPath)
|
||||
{
|
||||
// While following the player, let's ensure that there's a valid path to the target before accepting it.
|
||||
// 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.
|
||||
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; }
|
||||
@@ -355,7 +444,7 @@ namespace Barotrauma
|
||||
targetItem = spawnedItem;
|
||||
if (character.TeamID == CharacterTeamType.FriendlyNPC && (character.Submarine?.Info.IsOutpost ?? false))
|
||||
{
|
||||
spawnedItem.SpawnedInOutpost = true;
|
||||
spawnedItem.SpawnedInCurrentOutpost = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -365,7 +454,6 @@ namespace Barotrauma
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}", Color.Yellow);
|
||||
#endif
|
||||
SpeakCannotFind();
|
||||
Abandon = true;
|
||||
}
|
||||
}
|
||||
@@ -375,34 +463,19 @@ namespace Barotrauma
|
||||
protected override bool CheckObjectiveSpecific()
|
||||
{
|
||||
if (IsCompleted) { return true; }
|
||||
if (targetItem != null)
|
||||
if (targetItem == null)
|
||||
{
|
||||
if (Equip && EquipSlotType.HasValue)
|
||||
{
|
||||
return character.HasEquippedItem(targetItem, EquipSlotType.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return character.HasItem(targetItem, Equip);
|
||||
}
|
||||
}
|
||||
else if (identifiersOrTags != null)
|
||||
{
|
||||
var matchingItem = character.Inventory.FindItem(i => CheckItem(i), recursive: true);
|
||||
if (matchingItem != null)
|
||||
{
|
||||
if (Equip && EquipSlotType.HasValue)
|
||||
{
|
||||
return character.HasEquippedItem(matchingItem, EquipSlotType.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return !Equip || character.HasEquippedItem(matchingItem);
|
||||
}
|
||||
}
|
||||
// Not yet ready
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
if (Equip && EquipSlotType.HasValue)
|
||||
{
|
||||
return character.HasEquippedItem(targetItem, EquipSlotType.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return character.HasItem(targetItem, Equip);
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckItem(Item item)
|
||||
@@ -410,8 +483,10 @@ namespace Barotrauma
|
||||
if (!item.IsInteractable(character)) { return false; }
|
||||
if (item.IsThisOrAnyContainerIgnoredByAI(character)) { return false; }
|
||||
if (ignoredItems.Contains(item)) { return false; };
|
||||
if (ignoredIdentifiersOrTags != null && ignoredIdentifiersOrTags.Any(id => item.prefab.Identifier == id || item.HasTag(id))) { return false; }
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -437,15 +512,20 @@ namespace Barotrauma
|
||||
protected override void OnAbandon()
|
||||
{
|
||||
base.OnAbandon();
|
||||
if (moveToTarget == null) { return; }
|
||||
if (moveToTarget != null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Get item failed to reach {moveToTarget}", Color.Yellow);
|
||||
DebugConsole.NewMessage($"{character.Name}: Get item failed to reach {moveToTarget}", Color.Yellow);
|
||||
#endif
|
||||
}
|
||||
if (SpeakIfFails)
|
||||
{
|
||||
SpeakCannotFind();
|
||||
}
|
||||
}
|
||||
|
||||
private void SpeakCannotFind()
|
||||
{
|
||||
// TODO: Use the item name as the variable here.
|
||||
if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder == objectiveManager.CurrentObjective)
|
||||
{
|
||||
string msg = TextManager.Get("dialogcannotfinditem", true);
|
||||
@@ -455,19 +535,5 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove?
|
||||
private void SpeakCannotReach()
|
||||
{
|
||||
if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder == objectiveManager.CurrentObjective)
|
||||
{
|
||||
string TargetName = (moveToTarget as MapEntity)?.Name ?? (moveToTarget as Character)?.Name ?? moveToTarget.ToString();
|
||||
string msg = TargetName == null ? TextManager.Get("dialogcannotreachtarget", true) : TextManager.GetWithVariable("dialogcannotreachtarget", "[name]", TargetName, formatCapitals: !(moveToTarget is Character));
|
||||
if (msg != null)
|
||||
{
|
||||
character.Speak(msg, identifier: "dialogcannotreachtarget", minDurationBetweenSimilar: 20.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
#nullable enable
|
||||
using Barotrauma.Extensions;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveGetItems : AIObjective
|
||||
{
|
||||
public override string Identifier { get; set; } = "get items";
|
||||
public override string DebugTag => $"{Identifier}";
|
||||
public override bool KeepDivingGearOn => true;
|
||||
|
||||
public bool AllowStealing { get; set; }
|
||||
public bool TakeWholeStack { get; set; }
|
||||
public bool AllowVariants { get; set; }
|
||||
public bool Equip { get; set; }
|
||||
public bool Wear { get; set; }
|
||||
public bool CheckInventory { get; set; }
|
||||
public bool EvaluateCombatPriority { get; set; }
|
||||
public bool CheckPathForEachItem { get; set; }
|
||||
public bool RequireLoaded { get; set; }
|
||||
|
||||
private readonly ImmutableArray<string> gearTags;
|
||||
private readonly string[] ignoredTags;
|
||||
private bool subObjectivesCreated;
|
||||
|
||||
public readonly HashSet<Item> achievedItems = new HashSet<Item>();
|
||||
|
||||
public AIObjectiveGetItems(Character character, AIObjectiveManager objectiveManager, IEnumerable<string> identifiersOrTags, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
gearTags = AIObjectiveGetItem.ParseGearTags(identifiersOrTags).ToImmutableArray();
|
||||
ignoredTags = AIObjectiveGetItem.ParseIgnoredTags(identifiersOrTags).ToArray();
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific() => subObjectivesCreated && subObjectives.None();
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (character.LockHands)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (!subObjectivesCreated)
|
||||
{
|
||||
foreach (string tag in gearTags)
|
||||
{
|
||||
AIObjectiveGetItem? getItem = null;
|
||||
TryAddSubObjective(ref getItem, () =>
|
||||
new AIObjectiveGetItem(character, tag, objectiveManager, Equip, CheckInventory)
|
||||
{
|
||||
AllowVariants = AllowVariants,
|
||||
Wear = Wear,
|
||||
TakeWholeStack = TakeWholeStack,
|
||||
AllowStealing = AllowStealing,
|
||||
ignoredIdentifiersOrTags = ignoredTags,
|
||||
CheckPathForEachItem = CheckPathForEachItem,
|
||||
RequireLoaded = RequireLoaded
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
var item = getItem?.TargetItem;
|
||||
if (item?.IsOwnedBy(character) != null)
|
||||
{
|
||||
achievedItems.Add(item);
|
||||
}
|
||||
},
|
||||
onAbandon: () =>
|
||||
{
|
||||
var item = getItem?.TargetItem;
|
||||
if (item != null)
|
||||
{
|
||||
achievedItems.Remove(item);
|
||||
}
|
||||
RemoveSubObjective(ref getItem);
|
||||
});
|
||||
}
|
||||
subObjectivesCreated = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
subObjectivesCreated = false;
|
||||
achievedItems.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
-25
@@ -27,6 +27,7 @@ namespace Barotrauma
|
||||
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;
|
||||
@@ -61,6 +62,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Currently we never check the visibility (to the end node), which is actually unintentional.
|
||||
// I don't think it has caused any issues so far, so let's keep defaulting to false for now, because the less we do raycasts the better.
|
||||
// However, if there are cases where the bots attempt to go through walls (select the end node that is behind an obstacle), we should set this true.
|
||||
public bool CheckVisibility { get; set; }
|
||||
public bool IgnoreIfTargetDead { get; set; }
|
||||
public bool AllowGoingOutside { get; set; }
|
||||
@@ -77,7 +81,7 @@ namespace Barotrauma
|
||||
public override bool AllowOutsideSubmarine => AllowGoingOutside;
|
||||
public override bool AllowInAnySub => true;
|
||||
|
||||
public string DialogueIdentifier { get; set; }
|
||||
public string DialogueIdentifier { get; set; } = "dialogcannotreachtarget";
|
||||
public string TargetName { get; set; }
|
||||
|
||||
public ISpatialEntity Target { get; private set; }
|
||||
@@ -149,7 +153,10 @@ namespace Barotrauma
|
||||
private void SpeakCannotReach()
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {Target}", Color.Yellow);
|
||||
if (DebugLogWhenFails)
|
||||
{
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {Target}", Color.Yellow);
|
||||
}
|
||||
#endif
|
||||
if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder == objectiveManager.CurrentObjective && DialogueIdentifier != null && SpeakIfFails)
|
||||
{
|
||||
@@ -170,17 +177,12 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (Target == character || character.SelectedBy != null && HumanAIController.IsFriendly(character.SelectedBy))
|
||||
if (cannotFollow || Target == character || character.SelectedBy != null && HumanAIController.IsFriendly(character.SelectedBy))
|
||||
{
|
||||
// Wait
|
||||
character.AIController.SteeringManager.Reset();
|
||||
return;
|
||||
}
|
||||
if (cannotFollow)
|
||||
{
|
||||
// Wait
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
if (!character.IsClimbing)
|
||||
{
|
||||
character.SelectedConstruction = null;
|
||||
@@ -211,26 +213,33 @@ namespace Barotrauma
|
||||
}
|
||||
bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty;
|
||||
bool isInside = character.CurrentHull != null;
|
||||
bool targetIsOutside = (Target != null && targetHull == null) || (insideSteering && PathSteering.CurrentPath.HasOutdoorsNodes);
|
||||
if (isInside && targetIsOutside && !AllowGoingOutside)
|
||||
bool hasOutdoorNodes = insideSteering && PathSteering.CurrentPath.HasOutdoorsNodes;
|
||||
if (isInside && hasOutdoorNodes && !AllowGoingOutside)
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
else if (HumanAIController.IsCurrentPathNullOrUnreachable)
|
||||
else if (HumanAIController.SteeringManager == PathSteering)
|
||||
{
|
||||
waitUntilPathUnreachable -= deltaTime;
|
||||
SteeringManager.Reset();
|
||||
if (waitUntilPathUnreachable < 0)
|
||||
if (HumanAIController.IsCurrentPathNullOrUnreachable)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
if (waitUntilPathUnreachable < 0)
|
||||
{
|
||||
waitUntilPathUnreachable = pathWaitingTime;
|
||||
if (repeat)
|
||||
{
|
||||
SpeakCannotReach();
|
||||
}
|
||||
else
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (HumanAIController.HasValidPath(requireNonDirty: true, requireUnfinished: false))
|
||||
{
|
||||
waitUntilPathUnreachable = pathWaitingTime;
|
||||
if (repeat)
|
||||
{
|
||||
SpeakCannotReach();
|
||||
}
|
||||
else
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!Abandon)
|
||||
@@ -238,16 +247,16 @@ namespace Barotrauma
|
||||
if (getDivingGearIfNeeded && !character.LockHands)
|
||||
{
|
||||
Character followTarget = Target as Character;
|
||||
bool needsDivingSuit = !isInside || targetIsOutside;
|
||||
bool needsDivingGear = needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit);
|
||||
bool needsDivingSuit = (!isInside || hasOutdoorNodes) && character.NeedsAir && !character.HasAbilityFlag(AbilityFlags.ImmuneToPressure);
|
||||
bool needsDivingGear = (needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit)) && character.NeedsAir;
|
||||
if (mimic)
|
||||
{
|
||||
if (HumanAIController.HasDivingSuit(followTarget))
|
||||
if (HumanAIController.HasDivingSuit(followTarget) && character.NeedsAir)
|
||||
{
|
||||
needsDivingGear = true;
|
||||
needsDivingSuit = true;
|
||||
}
|
||||
else if (HumanAIController.HasDivingMask(followTarget))
|
||||
else if (HumanAIController.HasDivingMask(followTarget) && character.NeedsAir)
|
||||
{
|
||||
needsDivingGear = true;
|
||||
}
|
||||
|
||||
+26
-50
@@ -323,7 +323,6 @@ namespace Barotrauma
|
||||
SortObjectives();
|
||||
}
|
||||
|
||||
private CoroutineHandle speakRoutine;
|
||||
public void SetOrder(Order order, string option, int priority, Character orderGiver, bool speak)
|
||||
{
|
||||
if (character.IsDead)
|
||||
@@ -379,6 +378,7 @@ namespace Barotrauma
|
||||
var newCurrentOrder = CreateObjective(order, option, orderGiver);
|
||||
if (newCurrentOrder != null)
|
||||
{
|
||||
newCurrentOrder.Abandoned += () => DismissSelf(order, option);
|
||||
CurrentOrders.Add(new OrderInfo(order, option, priority, newCurrentOrder));
|
||||
}
|
||||
if (!HasOrders())
|
||||
@@ -386,53 +386,12 @@ namespace Barotrauma
|
||||
// Recreate objectives, because some of them may be removed, if impossible to complete (e.g. due to path finding)
|
||||
CreateAutonomousObjectives();
|
||||
}
|
||||
else
|
||||
else if (newCurrentOrder != null)
|
||||
{
|
||||
// This should be redundant, because all the objectives are reset when they are selected as active.
|
||||
newCurrentOrder?.Reset();
|
||||
|
||||
if (speak && character.IsOnPlayerTeam)
|
||||
{
|
||||
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 (newCurrentOrder != null && character.SpeechImpediment < 100.0f)
|
||||
// {
|
||||
// if (newCurrentOrder is AIObjectiveRepairItems repairItems && repairItems.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoRepairTargets"), null, 3.0f, "norepairtargets");
|
||||
// }
|
||||
// else if (newCurrentOrder is AIObjectiveChargeBatteries chargeBatteries && chargeBatteries.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoBatteries"), null, 3.0f, "nobatteries");
|
||||
// }
|
||||
// else if (newCurrentOrder is AIObjectiveExtinguishFires extinguishFires && extinguishFires.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoFire"), null, 3.0f, "nofire");
|
||||
// }
|
||||
// else if (newCurrentOrder is AIObjectiveFixLeaks fixLeaks && fixLeaks.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoLeaks"), null, 3.0f, "noleaks");
|
||||
// }
|
||||
// else if (newCurrentOrder is AIObjectiveFightIntruders fightIntruders && fightIntruders.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoEnemies"), null, 3.0f, "noenemies");
|
||||
// }
|
||||
// else if (newCurrentOrder is AIObjectiveRescueAll rescueAll && rescueAll.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoRescueTargets"), null, 3.0f, "norescuetargets");
|
||||
// }
|
||||
// else if (newCurrentOrder is AIObjectivePumpWater pumpWater && pumpWater.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoPumps"), null, 3.0f, "nopumps");
|
||||
// }
|
||||
// }
|
||||
//}, 3);
|
||||
string msg = newCurrentOrder.IsAllowed ? TextManager.Get("DialogAffirmative") : TextManager.Get("DialogNegative");
|
||||
character.Speak(msg, delay: 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -465,7 +424,6 @@ namespace Barotrauma
|
||||
break;
|
||||
case "return":
|
||||
newObjective = new AIObjectiveReturn(character, orderGiver, this, priorityModifier: priorityModifier);
|
||||
newObjective.Abandoned += () => DismissSelf(order, option);
|
||||
newObjective.Completed += () => DismissSelf(order, option);
|
||||
break;
|
||||
case "fixleaks":
|
||||
@@ -491,12 +449,10 @@ namespace Barotrauma
|
||||
if (!order.TargetItemComponent.Item.IsInteractable(character)) { return null; }
|
||||
newObjective = new AIObjectiveOperateItem(targetPump, character, this, option, false, priorityModifier: priorityModifier)
|
||||
{
|
||||
IsLoop = true,
|
||||
IsLoop = false,
|
||||
Override = orderGiver != null && orderGiver.IsCommanding
|
||||
};
|
||||
// ItemComponent.AIOperate() returns false by default -> We'd have to set IsLoop = false and implement a custom override of AIOperate for the Pump.cs,
|
||||
// if we want that the bot just switches the pump on/off and continues doing something else.
|
||||
// If we want that the bot does the objective and then forgets about it, I think we could do the same plus dismiss when the bot is done.
|
||||
newObjective.Completed += () => DismissSelf(order, option);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -566,6 +522,26 @@ namespace Barotrauma
|
||||
case "escapehandcuffs":
|
||||
newObjective = new AIObjectiveEscapeHandcuffs(character, this, priorityModifier: priorityModifier);
|
||||
break;
|
||||
case "prepareforexpedition":
|
||||
newObjective = new AIObjectivePrepare(character, this, order.TargetItems)
|
||||
{
|
||||
KeepActiveWhenReady = true,
|
||||
CheckInventory = true,
|
||||
Equip = false,
|
||||
FindAllItems = true
|
||||
};
|
||||
break;
|
||||
case "findweapon":
|
||||
newObjective = new AIObjectivePrepare(character, this, order.TargetItems)
|
||||
{
|
||||
KeepActiveWhenReady = false,
|
||||
CheckInventory = false,
|
||||
Equip = true,
|
||||
EvaluateCombatPriority = true,
|
||||
FindAllItems = false
|
||||
};
|
||||
newObjective.Completed += () => DismissSelf(order, option);
|
||||
break;
|
||||
default:
|
||||
if (order.TargetItemComponent == null) { return null; }
|
||||
if (!order.TargetItemComponent.Item.IsInteractable(character)) { return null; }
|
||||
|
||||
-1
@@ -213,7 +213,6 @@ namespace Barotrauma
|
||||
{
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(target.Item, character, objectiveManager, closeEnough: 50)
|
||||
{
|
||||
DialogueIdentifier = "dialogcannotreachtarget",
|
||||
TargetName = target.Item.Name,
|
||||
endNodeFilter = node => node.Waypoint.Ladders == null
|
||||
},
|
||||
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
#nullable enable
|
||||
using Barotrauma.Extensions;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectivePrepare : AIObjective
|
||||
{
|
||||
public override string Identifier { get; set; } = "prepare";
|
||||
public override string DebugTag => $"{Identifier}";
|
||||
public override bool KeepDivingGearOn => true;
|
||||
|
||||
private AIObjectiveGetItem? getSingleItemObjective;
|
||||
private AIObjectiveGetItems? getMultipleItemsObjective;
|
||||
private bool subObjectivesCreated;
|
||||
private readonly ImmutableArray<string> gearTags;
|
||||
private readonly HashSet<Item> items = new HashSet<Item>();
|
||||
public bool KeepActiveWhenReady { get; set; }
|
||||
public bool CheckInventory { get; set; }
|
||||
public bool FindAllItems { get; set; }
|
||||
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)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
gearTags = items.ToImmutableArray();
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific() => IsCompleted;
|
||||
|
||||
protected override float GetPriority()
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
return Priority;
|
||||
}
|
||||
Priority = objectiveManager.GetOrderPriority(this);
|
||||
var subObjective = GetSubObjective();
|
||||
if (subObjective != null && subObjective.IsCompleted)
|
||||
{
|
||||
Priority = 0;
|
||||
items.RemoveWhere(i => i == null || i.Removed || !i.IsOwnedBy(character));
|
||||
if (items.None())
|
||||
{
|
||||
Abandon = true;
|
||||
|
||||
}
|
||||
else if (items.Any(i => i.Components.Any(i => !i.IsLoaded(character))))
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (character.LockHands)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (!subObjectivesCreated)
|
||||
{
|
||||
if (FindAllItems)
|
||||
{
|
||||
if (!TryAddSubObjective(ref getMultipleItemsObjective, () => new AIObjectiveGetItems(character, objectiveManager, gearTags)
|
||||
{
|
||||
CheckInventory = CheckInventory,
|
||||
Equip = Equip,
|
||||
EvaluateCombatPriority = EvaluateCombatPriority,
|
||||
RequireLoaded = true
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
if (KeepActiveWhenReady)
|
||||
{
|
||||
if (getMultipleItemsObjective != null)
|
||||
{
|
||||
foreach (var item in getMultipleItemsObjective.achievedItems)
|
||||
{
|
||||
if (item?.IsOwnedBy(character) != null)
|
||||
{
|
||||
items.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IsCompleted = true;
|
||||
}
|
||||
},
|
||||
onAbandon: () => Abandon = true))
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!TryAddSubObjective(ref getSingleItemObjective, () => new AIObjectiveGetItem(character, gearTags, objectiveManager, equip: Equip, checkInventory: CheckInventory)
|
||||
{
|
||||
EvaluateCombatPriority = EvaluateCombatPriority,
|
||||
SpeakIfFails = true,
|
||||
RequireLoaded = true
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
if (KeepActiveWhenReady)
|
||||
{
|
||||
if (getSingleItemObjective != null)
|
||||
{
|
||||
var item = getSingleItemObjective?.TargetItem;
|
||||
if (item?.IsOwnedBy(character) != null)
|
||||
{
|
||||
items.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IsCompleted = true;
|
||||
}
|
||||
},
|
||||
onAbandon: () => Abandon = true))
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
}
|
||||
subObjectivesCreated = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
items.Clear();
|
||||
subObjectivesCreated = false;
|
||||
RemoveSubObjective(ref getMultipleItemsObjective);
|
||||
RemoveSubObjective(ref getSingleItemObjective);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-3
@@ -107,7 +107,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (RelatedItem requiredItem in kvp.Value)
|
||||
{
|
||||
var getItemObjective = new AIObjectiveGetItem(character, requiredItem.Identifiers, objectiveManager, true)
|
||||
var getItemObjective = new AIObjectiveGetItem(character, requiredItem.Identifiers, objectiveManager, equip: true)
|
||||
{
|
||||
AllowVariants = requiredItem.AllowVariants
|
||||
};
|
||||
@@ -219,8 +219,7 @@ namespace Barotrauma
|
||||
{
|
||||
// Don't stop in ladders, because we can't interact with other items while holding the ladders.
|
||||
endNodeFilter = node => node.Waypoint.Ladders == null,
|
||||
// Allow repairing hatches and airlock doors.
|
||||
AllowGoingOutside = HumanAIController.ObjectiveManager.IsCurrentOrder<AIObjectiveRepairItems>() && Item.GetComponent<Door>() != null
|
||||
TargetName = Item.Name
|
||||
};
|
||||
if (repairTool != null)
|
||||
{
|
||||
|
||||
+1
-2
@@ -91,8 +91,7 @@ namespace Barotrauma
|
||||
|
||||
public static bool NearlyFullCondition(Item item)
|
||||
{
|
||||
float condition = item.ConditionPercentage;
|
||||
return item.Repairables.All(r => condition >= r.RepairThreshold);
|
||||
return item.Repairables.All(r => !r.IsBelowRepairThreshold);
|
||||
}
|
||||
|
||||
protected override float TargetEvaluation()
|
||||
|
||||
+24
-1
@@ -319,9 +319,32 @@ namespace Barotrauma
|
||||
{
|
||||
itemListStr = itemNameList[0];
|
||||
}
|
||||
else if (itemNameList.Count == 2)
|
||||
{
|
||||
//[treatment1] or [treatment2]
|
||||
itemListStr = TextManager.GetWithVariables(
|
||||
"DialogRequiredTreatmentOptionsLast",
|
||||
new string[] { "[treatment1]", "[treatment2]" },
|
||||
new string[] { itemNameList[0], itemNameList[1] });
|
||||
}
|
||||
else
|
||||
{
|
||||
itemListStr = string.Join(" or ", string.Join(", ", itemNameList.Take(itemNameList.Count - 1)), itemNameList.Last());
|
||||
//[treatment1], [treatment2], [treatment3] ... or [treatmentx]
|
||||
itemListStr = TextManager.GetWithVariables(
|
||||
"DialogRequiredTreatmentOptionsFirst",
|
||||
new string[] { "[treatment1]", "[treatment2]" },
|
||||
new string[] { itemNameList[0], itemNameList[1] });
|
||||
for (int i = 2; i < itemNameList.Count - 1; i++)
|
||||
{
|
||||
itemListStr = TextManager.GetWithVariables(
|
||||
"DialogRequiredTreatmentOptionsFirst",
|
||||
new string[] { "[treatment1]", "[treatment2]" },
|
||||
new string[] { itemListStr, itemNameList[i] });
|
||||
}
|
||||
itemListStr = TextManager.GetWithVariables(
|
||||
"DialogRequiredTreatmentOptionsLast",
|
||||
new string[] { "[treatment1]", "[treatment2]" },
|
||||
new string[] { itemListStr, itemNameList.Last() });
|
||||
}
|
||||
if (targetCharacter != character && character.IsOnPlayerTeam)
|
||||
{
|
||||
|
||||
+1
@@ -79,6 +79,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (target == null || target.IsDead || target.Removed) { return false; }
|
||||
if (target.IsInstigator) { return false; }
|
||||
if (target.IsPet) { return false; }
|
||||
if (!HumanAIController.IsFriendly(character, target, onlySameTeam: true)) { return false; }
|
||||
if (character.AIController is HumanAIController humanAI)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user