Unstable 0.17.0.0

This commit is contained in:
Markus Isberg
2022-02-26 02:43:01 +09:00
parent a83f375681
commit 3974067915
913 changed files with 32472 additions and 32364 deletions
@@ -10,8 +10,8 @@ namespace Barotrauma
{
public virtual float Devotion => AIObjectiveManager.baseDevotion;
public abstract string Identifier { get; set; }
public virtual string DebugTag => Identifier;
public abstract Identifier Identifier { get; set; }
public virtual string DebugTag => Identifier.Value;
public virtual bool ForceRun => false;
public virtual bool IgnoreUnsafeHulls => false;
public virtual bool AbandonWhenCannotCompleteSubjectives => true;
@@ -83,7 +83,7 @@ namespace Barotrauma
public readonly Character character;
public readonly AIObjectiveManager objectiveManager;
public string Option { get; private set; }
public readonly Identifier Option;
private bool _abandon;
public bool Abandon
@@ -157,11 +157,11 @@ namespace Barotrauma
return subObjective == null ? this : subObjective.GetActiveObjective();
}
public AIObjective(Character character, AIObjectiveManager objectiveManager, float priorityModifier, string option = null)
public AIObjective(Character character, AIObjectiveManager objectiveManager, float priorityModifier, Identifier option = default)
{
this.objectiveManager = objectiveManager;
this.character = character;
Option = option ?? string.Empty;
Option = option;
PriorityModifier = priorityModifier;
}
@@ -9,11 +9,11 @@ namespace Barotrauma
{
class AIObjectiveChargeBatteries : AIObjectiveLoop<PowerContainer>
{
public override string Identifier { get; set; } = "charge batteries";
public override Identifier Identifier { get; set; } = "charge batteries".ToIdentifier();
public override bool AllowAutomaticItemUnequipping => true;
private IEnumerable<PowerContainer> batteryList;
public AIObjectiveChargeBatteries(Character character, AIObjectiveManager objectiveManager, string option, float priorityModifier)
public AIObjectiveChargeBatteries(Character character, AIObjectiveManager objectiveManager, Identifier option, float priorityModifier)
: base(character, objectiveManager, priorityModifier, option) { }
protected override bool Filter(PowerContainer battery)
@@ -55,7 +55,7 @@ namespace Barotrauma
{
if (character == null || character.Submarine == null)
{
return new PowerContainer[0];
return Array.Empty<PowerContainer>();
}
batteryList = character.Submarine.GetItems(true).Select(i => i.GetComponent<PowerContainer>()).Where(b => b != null);
}
@@ -9,7 +9,7 @@ namespace Barotrauma
{
class AIObjectiveCleanupItem : AIObjective
{
public override string Identifier { get; set; } = "cleanup item";
public override Identifier Identifier { get; set; } = "cleanup item".ToIdentifier();
public override bool KeepDivingGearOn => true;
public override bool AllowAutomaticItemUnequipping => false;
@@ -61,21 +61,6 @@ namespace Barotrauma
protected override void Act(float deltaTime)
{
if (item.IgnoreByAI(character))
{
Abandon = true;
return;
}
if (item.ParentInventory != null)
{
if (item.Container != null && !AIObjectiveCleanupItems.IsValidContainer(item.Container, character, allowUnloading: objectiveManager.HasOrder<AIObjectiveCleanupItems>()))
{
// Target was picked up or moved by someone.
Abandon = true;
return;
}
}
// Only continue when the get item sub objectives have been completed.
if (subObjectives.Any()) { return; }
if (HumanAIController.FindSuitableContainer(character, item, ignoredContainers, ref itemIndex, out Item suitableContainer))
{
@@ -133,7 +118,24 @@ namespace Barotrauma
}
}
protected override bool CheckObjectiveSpecific() => IsCompleted;
protected override bool CheckObjectiveSpecific()
{
if (item.IgnoreByAI(character))
{
Abandon = true;
return false;
}
if (item.ParentInventory != null)
{
if (item.Container != null && !AIObjectiveCleanupItems.IsValidContainer(item.Container, character, allowUnloading: objectiveManager.HasOrder<AIObjectiveCleanupItems>()))
{
// Target was picked up or moved by someone.
Abandon = true;
return false;
}
}
return IsCompleted;
}
public override void Reset()
{
@@ -8,7 +8,7 @@ namespace Barotrauma
{
class AIObjectiveCleanupItems : AIObjectiveLoop<Item>
{
public override string Identifier { get; set; } = "cleanup items";
public override Identifier Identifier { get; set; } = "cleanup items".ToIdentifier();
public override bool KeepDivingGearOn => true;
public override bool AllowAutomaticItemUnequipping => false;
protected override bool ForceOrderPriority => false;
@@ -79,18 +79,16 @@ namespace Barotrauma
public static bool IsValidContainer(Item container, Character character, bool allowUnloading = true) =>
allowUnloading &&
!container.IgnoreByAI(character) &&
container.IsInteractable(character) &&
container.HasAccess(character) &&
container.HasTag("allowcleanup") &&
container.ParentInventory == null && container.OwnInventory != null && container.OwnInventory.AllItems.Any() &&
container.GetComponent<ItemContainer>() is ItemContainer itemContainer && itemContainer.HasAccess(character) &&
container.GetComponent<ItemContainer>() != null &&
IsItemInsideValidSubmarine(container, character);
public static bool IsValidTarget(Item item, Character character, bool checkInventory, bool allowUnloading = true)
{
if (item == null) { return false; }
if (item.IgnoreByAI(character)) { return false; }
if (!item.IsInteractable(character)) { return false; }
if (!item.HasAccess(character)) { return false; }
if ((item.SpawnedInCurrentOutpost && !item.AllowStealing) == character.IsOnPlayerTeam) { return false; }
if (item.ParentInventory != null)
{
@@ -10,7 +10,7 @@ namespace Barotrauma
{
class AIObjectiveCombat : AIObjective
{
public override string Identifier { get; set; } = "combat";
public override Identifier Identifier { get; set; } = "combat".ToIdentifier();
public override bool KeepDivingGearOn => true;
public override bool IgnoreUnsafeHulls => true;
@@ -250,17 +250,17 @@ namespace Barotrauma
case CombatMode.Offensive:
if (TargetEliminated && objectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>())
{
character.Speak(TextManager.Get("DialogTargetDown"), null, 3.0f, "targetdown", 30.0f);
character.Speak(TextManager.Get("DialogTargetDown").Value, null, 3.0f, "targetdown".ToIdentifier(), 30.0f);
}
break;
case CombatMode.Arrest:
if (HumanAIController.HasItem(Enemy, "handlocker", out _, requireEquipped: true))
if (HumanAIController.HasItem(Enemy, "handlocker".ToIdentifier(), out _, requireEquipped: true))
{
IsCompleted = true;
}
else if (Enemy.IsKnockedDown &&
!objectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>() &&
!HumanAIController.HasItem(character, "handlocker", out _, requireEquipped: false))
!HumanAIController.HasItem(character, "handlocker".ToIdentifier(), out _, requireEquipped: false))
{
IsCompleted = true;
}
@@ -399,7 +399,7 @@ namespace Barotrauma
RemoveSubObjective(ref retreatObjective);
RemoveSubObjective(ref followTargetObjective);
TryAddSubObjective(ref seekWeaponObjective,
constructor: () => new AIObjectiveGetItem(character, "weapon", objectiveManager, equip: true, checkInventory: false)
constructor: () => new AIObjectiveGetItem(character, "weapon".ToIdentifier(), objectiveManager, equip: true, checkInventory: false)
{
AllowStealing = HumanAIController.IsMentallyUnstable,
EvaluateCombatPriority = false, // Use a custom formula instead
@@ -636,7 +636,7 @@ namespace Barotrauma
// If there's an item container that takes a battery,
// assume that it's required for the stun effect
// as we can't check the status effect conditions here.
var mobileBatteryTag = "mobilebattery";
var mobileBatteryTag = "mobilebattery".ToIdentifier();
var containers = weapon.Item.Components.Where(ic =>
ic is ItemContainer container &&
container.ContainableItemIdentifiers.Contains(mobileBatteryTag));
@@ -848,7 +848,7 @@ namespace Barotrauma
if (followTargetObjective == null) { return; }
if (Mode == CombatMode.Arrest && Enemy.IsKnockedDown)
{
if (HumanAIController.HasItem(character, "handlocker", out _))
if (HumanAIController.HasItem(character, "handlocker".ToIdentifier(), out _))
{
if (!arrestingRegistered)
{
@@ -861,10 +861,10 @@ namespace Barotrauma
{
if (character.TeamID == CharacterTeamType.FriendlyNPC)
{
ItemPrefab prefab = ItemPrefab.Find(null, "handcuffs");
ItemPrefab prefab = ItemPrefab.Find(null, "handcuffs".ToIdentifier());
if (prefab != null)
{
Entity.Spawner.AddToSpawnQueue(prefab, character.Inventory, onSpawned: (Item i) => i.SpawnedInCurrentOutpost = true);
Entity.Spawner.AddItemToSpawnQueue(prefab, character.Inventory, onSpawned: (Item i) => i.SpawnedInCurrentOutpost = true);
}
}
RemoveFollowTarget();
@@ -914,7 +914,7 @@ namespace Barotrauma
}
}
}
if (HumanAIController.HasItem(character, "handlocker", out IEnumerable<Item> matchingItems) && !Enemy.IsUnconscious && Enemy.IsKnockedDown && character.CanInteractWith(Enemy))
if (HumanAIController.HasItem(character, "handlocker".ToIdentifier(), out IEnumerable<Item> matchingItems) && !Enemy.IsUnconscious && Enemy.IsKnockedDown && character.CanInteractWith(Enemy))
{
var handCuffs = matchingItems.First();
if (!HumanAIController.TakeItem(handCuffs, Enemy.Inventory, equip: true))
@@ -928,7 +928,7 @@ namespace Barotrauma
return;
}
}
character.Speak(TextManager.Get("DialogTargetArrested"), null, 3.0f, "targetarrested", 30.0f);
character.Speak(TextManager.Get("DialogTargetArrested").Value, null, 3.0f, "targetarrested".ToIdentifier(), 30.0f);
}
if (!objectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>())
{
@@ -939,7 +939,7 @@ namespace Barotrauma
/// <summary>
/// Seeks for more ammunition. Creates a new subobjective.
/// </summary>
private void SeekAmmunition(string[] ammunitionIdentifiers)
private void SeekAmmunition(Identifier[] ammunitionIdentifiers)
{
retreatTarget = null;
RemoveSubObjective(ref retreatObjective);
@@ -974,7 +974,7 @@ namespace Barotrauma
HumanAIController.UnequipEmptyItems(Weapon);
RelatedItem item = null;
Item ammunition = null;
string[] ammunitionIdentifiers = null;
Identifier[] ammunitionIdentifiers = null;
if (WeaponComponent.requiredItems.ContainsKey(RelatedItem.RelationType.Contained))
{
foreach (RelatedItem requiredItem in WeaponComponent.requiredItems[RelatedItem.RelationType.Contained])
@@ -1212,17 +1212,17 @@ namespace Barotrauma
retreatTarget = null;
}
private void SpeakNoWeapons() => Speak("dialogcombatnoweapons", delay: 0, minDuration: 30);
private void AskHelp() => Speak("dialogcombatretreating", delay: Rand.Range(0f, 1f), minDuration: 20);
private void SpeakNoWeapons() => Speak("dialogcombatnoweapons".ToIdentifier(), delay: 0, minDuration: 30);
private void AskHelp() => Speak("dialogcombatretreating".ToIdentifier(), delay: Rand.Range(0f, 1f), minDuration: 20);
private void Speak(string textIdentifier, float delay, float minDuration)
private void Speak(Identifier textIdentifier, float delay, float minDuration)
{
if (character.IsOnPlayerTeam && !character.IsInFriendlySub)
{
string msg = TextManager.Get(textIdentifier, true);
if (msg != null)
LocalizedString msg = TextManager.Get(textIdentifier);
if (!msg.IsNullOrEmpty())
{
character.Speak(msg, identifier: textIdentifier, delay: delay, minDurationBetweenSimilar: minDuration);
character.Speak(msg.Value, identifier: textIdentifier, delay: delay, minDurationBetweenSimilar: minDuration);
}
}
}
@@ -7,18 +7,18 @@ namespace Barotrauma
{
class AIObjectiveContainItem: AIObjective
{
public override string Identifier { get; set; } = "contain item";
public override Identifier Identifier { get; set; } = "contain item".ToIdentifier();
public Func<Item, float> GetItemPriority;
public string[] ignoredContainerIdentifiers;
public Identifier[] ignoredContainerIdentifiers;
public bool checkInventory = true;
//if the item can't be found, spawn it in the character's inventory (used by outpost NPCs and in some cases also enemy NPCs, like pirates)
private readonly bool spawnItemIfNotFound;
//can either be a tag or an identifier
public readonly string[] itemIdentifiers;
public readonly Identifier[] itemIdentifiers;
public readonly ItemContainer container;
private readonly Item item;
public Item ItemToContain { get; private set; }
@@ -60,25 +60,21 @@ namespace Barotrauma
this.item = item;
}
public AIObjectiveContainItem(Character character, string itemIdentifier, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool spawnItemIfNotFound = false)
: this(character, new string[] { itemIdentifier }, container, objectiveManager, priorityModifier, spawnItemIfNotFound) { }
public AIObjectiveContainItem(Character character, Identifier itemIdentifier, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool spawnItemIfNotFound = false)
: this(character, new Identifier[] { itemIdentifier }, container, objectiveManager, priorityModifier, spawnItemIfNotFound) { }
public AIObjectiveContainItem(Character character, string[] itemIdentifiers, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool spawnItemIfNotFound = false)
public AIObjectiveContainItem(Character character, Identifier[] itemIdentifiers, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool spawnItemIfNotFound = false)
: base(character, objectiveManager, priorityModifier)
{
this.itemIdentifiers = itemIdentifiers;
this.spawnItemIfNotFound = spawnItemIfNotFound;
for (int i = 0; i < itemIdentifiers.Length; i++)
{
itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant();
}
this.container = container;
}
protected override bool CheckObjectiveSpecific()
{
if (IsCompleted) { return true; }
if (container == null || (container.Item != null && container.Item.IsThisOrAnyContainerIgnoredByAI(character)))
if (container?.Item == null || !container.Item.HasAccess(character))
{
Abandon = true;
return false;
@@ -89,23 +85,28 @@ namespace Barotrauma
}
else
{
int containedItemCount = 0;
foreach (Item it in container.Inventory.AllItems)
{
if (CheckItem(it))
{
containedItemCount++;
}
}
return containedItemCount >= ItemCount;
return CountItems();
}
}
private bool CheckItem(Item i) => itemIdentifiers.Any(id => i.Prefab.Identifier == id || i.HasTag(id)) && i.ConditionPercentage >= ConditionLevel && !i.IsThisOrAnyContainerIgnoredByAI(character);
private bool CountItems()
{
int containedItemCount = 0;
foreach (Item it in container.Inventory.AllItems)
{
if (CheckItem(it))
{
containedItemCount++;
}
}
return containedItemCount >= ItemCount;
}
private bool CheckItem(Item i) => itemIdentifiers.Any(id => i.Prefab.Identifier == id || i.HasTag(id)) && i.ConditionPercentage >= ConditionLevel && i.HasAccess(character);
protected override void Act(float deltaTime)
{
if (container?.Item == null || container.Item.Removed || container.Item.IsThisOrAnyContainerIgnoredByAI(character))
if (container?.Item == null)
{
Abandon = true;
return;
@@ -141,8 +142,8 @@ namespace Barotrauma
container.Inventory.TryPutItem(item, null);
}
}
IsCompleted = true;
}
IsCompleted = item != null || CountItems();
}
else
{
@@ -159,7 +160,7 @@ namespace Barotrauma
{
TargetName = container.Item.Name,
AbortCondition = obj =>
container?.Item == null || container.Item.Removed || container.Item.IsThisOrAnyContainerIgnoredByAI(character) ||
container?.Item == null || container.Item.Removed || !container.Item.HasAccess(character) ||
(container.Item.GetRootContainer()?.OwnInventory?.Locked ?? false) ||
ItemToContain == null || ItemToContain.Removed ||
!ItemToContain.IsOwnedBy(character) || container.Item.GetRootInventoryOwner() is Character c && c != character,
@@ -6,7 +6,7 @@ namespace Barotrauma
{
class AIObjectiveDecontainItem : AIObjective
{
public override string Identifier { get; set; } = "decontain item";
public override Identifier Identifier { get; set; } = "decontain item".ToIdentifier();
public Func<Item, float> GetItemPriority;
@@ -127,7 +127,7 @@ namespace Barotrauma
RemoveExistingPredicate = RemoveExistingPredicate,
RemoveMax = RemoveExistingMax,
GetItemPriority = GetItemPriority,
ignoredContainerIdentifiers = sourceContainer != null ? new string[] { sourceContainer.Item.Prefab.Identifier } : null
ignoredContainerIdentifiers = sourceContainer != null ? new Identifier[] { sourceContainer.Item.Prefab.Identifier } : null
},
onCompleted: () => IsCompleted = true,
onAbandon: () => Abandon = true);
@@ -6,7 +6,7 @@ namespace Barotrauma
class AIObjectiveEscapeHandcuffs : AIObjective
{
// Used for prisoner escorts to allow them to escape their binds
public override string Identifier { get; set; } = "escape handcuffs";
public override Identifier Identifier { get; set; } = "escape handcuffs".ToIdentifier();
public override bool AllowAutomaticItemUnequipping => true;
public override bool AllowOutsideSubmarine => true;
public override bool AllowInAnySub => true;
@@ -88,7 +88,7 @@ namespace Barotrauma
escapeProgress += Rand.Range(2, 5);
if (escapeProgress > 15)
{
Item handcuffs = character.Inventory.FindItemByTag("handlocker");
Item handcuffs = character.Inventory.FindItemByTag("handlocker".ToIdentifier());
if (handcuffs != null)
{
handcuffs.Drop(character);
@@ -8,7 +8,7 @@ namespace Barotrauma
{
class AIObjectiveExtinguishFire : AIObjective
{
public override string Identifier { get; set; } = "extinguish fire";
public override Identifier Identifier { get; set; } = "extinguish fire".ToIdentifier();
public override bool ForceRun => true;
public override bool ConcurrentObjectives => true;
public override bool KeepDivingGearOn => true;
@@ -77,16 +77,16 @@ namespace Barotrauma
private float sinTime;
protected override void Act(float deltaTime)
{
var extinguisherItem = character.Inventory.FindItemByTag("fireextinguisher");
var extinguisherItem = character.Inventory.FindItemByTag("fireextinguisher".ToIdentifier());
if (extinguisherItem == null || extinguisherItem.Condition <= 0.0f || !character.HasEquippedItem(extinguisherItem))
{
TryAddSubObjective(ref getExtinguisherObjective, () =>
{
if (character.IsOnPlayerTeam && !character.HasEquippedItem("fireextinguisher", allowBroken: false))
if (character.IsOnPlayerTeam && !character.HasEquippedItem("fireextinguisher".ToIdentifier(), allowBroken: false))
{
character.Speak(TextManager.Get("DialogFindExtinguisher"), null, 2.0f, "findextinguisher", 30.0f);
character.Speak(TextManager.Get("DialogFindExtinguisher").Value, null, 2.0f, "findextinguisher".ToIdentifier(), 30.0f);
}
var getItemObjective = new AIObjectiveGetItem(character, "fireextinguisher", objectiveManager, equip: true)
var getItemObjective = new AIObjectiveGetItem(character, "fireextinguisher".ToIdentifier(), objectiveManager, equip: true)
{
AllowStealing = true,
// If the item is inside an unsafe hull, decrease the priority
@@ -94,7 +94,7 @@ namespace Barotrauma
};
if (objectiveManager.HasOrder<AIObjectiveExtinguishFires>())
{
getItemObjective.Abandoned += () => character.Speak(TextManager.Get("dialogcannotfindfireextinguisher"), null, 0.0f, "dialogcannotfindfireextinguisher", 10.0f);
getItemObjective.Abandoned += () => character.Speak(TextManager.Get("dialogcannotfindfireextinguisher").Value, null, 0.0f, "dialogcannotfindfireextinguisher".ToIdentifier(), 10.0f);
};
return getItemObjective;
});
@@ -139,7 +139,7 @@ namespace Barotrauma
extinguisher.Use(deltaTime, character);
if (!targetHull.FireSources.Contains(fs))
{
character.Speak(TextManager.GetWithVariable("DialogPutOutFire", "[roomname]", targetHull.DisplayName, true), null, 0, "putoutfire", 10.0f);
character.Speak(TextManager.GetWithVariable("DialogPutOutFire", "[roomname]", targetHull.DisplayName, FormatCapitals.Yes).Value, null, 0, "putoutfire".ToIdentifier(), 10.0f);
}
}
if (move)
@@ -147,7 +147,7 @@ namespace Barotrauma
//go to the first firesource
if (TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(fs, character, objectiveManager, closeEnough: Math.Max(fs.DamageRange, extinguisher.Range * 0.7f))
{
DialogueIdentifier = "dialogcannotreachfire",
DialogueIdentifier = "dialogcannotreachfire".ToIdentifier(),
TargetName = fs.Hull.DisplayName
},
onAbandon: () => Abandon = true,
@@ -8,7 +8,7 @@ namespace Barotrauma
{
class AIObjectiveExtinguishFires : AIObjectiveLoop<Hull>
{
public override string Identifier { get; set; } = "extinguish fires";
public override Identifier Identifier { get; set; } = "extinguish fires".ToIdentifier();
public override bool ForceRun => true;
public override bool AllowInAnySub => true;
@@ -27,7 +27,7 @@ namespace Barotrauma
/// </summary>
public static float GetFireSeverity(Hull hull) => MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, 500, hull.FireSources.Sum(fs => fs.Size.X)));
protected override IEnumerable<Hull> GetList() => Hull.hullList;
protected override IEnumerable<Hull> GetList() => Hull.HullList;
protected override AIObjective ObjectiveConstructor(Hull target)
=> new AIObjectiveExtinguishFire(character, target, objectiveManager, PriorityModifier);
@@ -6,7 +6,7 @@ namespace Barotrauma
{
class AIObjectiveFightIntruders : AIObjectiveLoop<Character>
{
public override string Identifier { get; set; } = "fight intruders";
public override Identifier Identifier { get; set; } = "fight intruders".ToIdentifier();
protected override float IgnoreListClearInterval => 30;
public override bool IgnoreUnsafeHulls => true;
@@ -45,9 +45,9 @@ namespace Barotrauma
{
//hold fire while the enemy is in the airlock (except if they've attacked us)
if (character.GetDamageDoneByAttacker(target) > 0.0f) { return false; }
return target.CurrentHull == null || target.CurrentHull.OutpostModuleTags.Any(t => t.Equals("airlock", System.StringComparison.OrdinalIgnoreCase));
return target.CurrentHull == null || target.CurrentHull.OutpostModuleTags.Any(t => t == "airlock");
};
character.Speak(TextManager.Get("dialogenteroutpostwarning"), null, Rand.Range(0.5f, 1.0f), "leaveoutpostwarning", 30.0f);
character.Speak(TextManager.Get("dialogenteroutpostwarning").Value, null, Rand.Range(0.5f, 1.0f), "leaveoutpostwarning".ToIdentifier(), 30.0f);
}
}
return combatObjective;
@@ -7,13 +7,13 @@ namespace Barotrauma
{
class AIObjectiveFindDivingGear : AIObjective
{
public override string Identifier { get; set; } = "find diving gear";
public override Identifier Identifier { get; set; } = "find diving gear".ToIdentifier();
public override string DebugTag => $"{Identifier} ({gearTag})";
public override bool ForceRun => true;
public override bool KeepDivingGearOn => true;
public override bool AbandonWhenCannotCompleteSubjectives => false;
private readonly string gearTag;
private readonly Identifier gearTag;
private AIObjectiveGetItem getDivingGear;
private AIObjectiveContainItem getOxygen;
@@ -21,13 +21,13 @@ namespace Barotrauma
public const float MIN_OXYGEN = 10;
public const string HEAVY_DIVING_GEAR = "deepdiving";
public const string LIGHT_DIVING_GEAR = "lightdiving";
public static readonly Identifier HEAVY_DIVING_GEAR = "deepdiving".ToIdentifier();
public static readonly Identifier LIGHT_DIVING_GEAR = "lightdiving".ToIdentifier();
/// <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";
public static readonly Identifier DIVING_GEAR_WEARABLE_INDOORS = "divinggear_wearableindoors".ToIdentifier();
public static readonly Identifier OXYGEN_SOURCE = "oxygensource".ToIdentifier();
protected override bool CheckObjectiveSpecific() => targetItem != null && character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.Head);
@@ -54,7 +54,7 @@ namespace Barotrauma
{
if (targetItem == null && character.IsOnPlayerTeam)
{
character.Speak(TextManager.Get("DialogGetDivingGear"), null, 0.0f, "getdivinggear", 30.0f);
character.Speak(TextManager.Get("DialogGetDivingGear").Value, null, 0.0f, "getdivinggear".ToIdentifier(), 30.0f);
}
return new AIObjectiveGetItem(character, gearTag, objectiveManager, equip: true)
{
@@ -92,15 +92,15 @@ namespace Barotrauma
{
if (HumanAIController.HasItem(character, OXYGEN_SOURCE, out _, conditionPercentage: min))
{
character.Speak(TextManager.Get("dialogswappingoxygentank"), null, 0, "swappingoxygentank", 30.0f);
character.Speak(TextManager.Get("dialogswappingoxygentank").Value, null, 0, "swappingoxygentank".ToIdentifier(), 30.0f);
if (character.Inventory.FindAllItems(i => i.HasTag(OXYGEN_SOURCE) && i.Condition > min).Count == 1)
{
character.Speak(TextManager.Get("dialoglastoxygentank"), null, 0.0f, "dialoglastoxygentank", 30.0f);
character.Speak(TextManager.Get("dialoglastoxygentank").Value, null, 0.0f, "dialoglastoxygentank".ToIdentifier(), 30.0f);
}
}
else
{
character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f);
character.Speak(TextManager.Get("DialogGetOxygenTank").Value, null, 0, "getoxygentank".ToIdentifier(), 30.0f);
}
}
return new AIObjectiveContainItem(character, OXYGEN_SOURCE, targetItem.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
@@ -130,7 +130,7 @@ namespace Barotrauma
Abandon = true;
if (remainingTanks > 0 && !HumanAIController.HasItem(character, OXYGEN_SOURCE, out _, conditionPercentage: 0.01f))
{
character.Speak(TextManager.Get("dialogcantfindtoxygen"), null, 0, "cantfindoxygen", 30.0f);
character.Speak(TextManager.Get("dialogcantfindtoxygen").Value, null, 0, "cantfindoxygen".ToIdentifier(), 30.0f);
}
},
onCompleted: () => RemoveSubObjective(ref getOxygen));
@@ -147,11 +147,11 @@ namespace Barotrauma
int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag(OXYGEN_SOURCE) && i.Condition > 1);
if (remainingOxygenTanks == 0)
{
character.Speak(TextManager.Get("DialogOutOfOxygenTanks"), null, 0.0f, "outofoxygentanks", 30.0f);
character.Speak(TextManager.Get("DialogOutOfOxygenTanks").Value, null, 0.0f, "outofoxygentanks".ToIdentifier(), 30.0f);
}
else if (remainingOxygenTanks < 10)
{
character.Speak(TextManager.Get("DialogLowOnOxygenTanks"), null, 0.0f, "lowonoxygentanks", 30.0f);
character.Speak(TextManager.Get("DialogLowOnOxygenTanks").Value, null, 0.0f, "lowonoxygentanks".ToIdentifier(), 30.0f);
}
return remainingOxygenTanks;
}
@@ -8,7 +8,7 @@ namespace Barotrauma
{
class AIObjectiveFindSafety : AIObjective
{
public override string Identifier { get; set; } = "find safety";
public override Identifier Identifier { get; set; } = "find safety".ToIdentifier();
public override bool ForceRun => true;
public override bool KeepDivingGearOn => true;
public override bool IgnoreUnsafeHulls => true;
@@ -317,7 +317,7 @@ namespace Barotrauma
Hull bestHull = null;
float bestValue = 0;
bool bestIsAirlock = false;
foreach (Hull hull in Hull.hullList.OrderByDescending(h => EstimateHullSuitability(h)))
foreach (Hull hull in Hull.HullList.OrderByDescending(h => EstimateHullSuitability(h)))
{
if (hull.Submarine == null) { continue; }
// Ruins are mazes filled with water. There's no safe hulls and we don't want to use the resources on it.
@@ -342,7 +342,7 @@ namespace Barotrauma
//(no need to do the expensive pathfinding if we already know we're not going to choose this hull)
if (hullSafety < bestValue) { continue; }
//avoid airlock modules if not allowed to change the sub
if (!allowChangingTheSubmarine && hull.OutpostModuleTags.Any(t => t.Equals("airlock", StringComparison.OrdinalIgnoreCase)))
if (!allowChangingTheSubmarine && hull.OutpostModuleTags.Any(t => t == "airlock"))
{
continue;
}
@@ -9,7 +9,7 @@ namespace Barotrauma
{
class AIObjectiveFixLeak : AIObjective
{
public override string Identifier { get; set; } = "fix leak";
public override Identifier Identifier { get; set; } = "fix leak".ToIdentifier();
public override bool ForceRun => true;
public override bool KeepDivingGearOn => true;
public override bool AllowInAnySub => true;
@@ -64,15 +64,15 @@ namespace Barotrauma
protected override void Act(float deltaTime)
{
var weldingTool = character.Inventory.FindItemByTag("weldingequipment", true);
var weldingTool = character.Inventory.FindItemByTag("weldingequipment".ToIdentifier(), true);
if (weldingTool == null)
{
TryAddSubObjective(ref getWeldingTool, () => new AIObjectiveGetItem(character, "weldingequipment", objectiveManager, equip: true, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC),
TryAddSubObjective(ref getWeldingTool, () => new AIObjectiveGetItem(character, "weldingequipment".ToIdentifier(), objectiveManager, equip: true, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC),
onAbandon: () =>
{
if (character.IsOnPlayerTeam && objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>())
{
character.Speak(TextManager.Get("dialogcannotfindweldingequipment"), null, 0.0f, "dialogcannotfindweldingequipment", 10.0f);
character.Speak(TextManager.Get("dialogcannotfindweldingequipment").Value, null, 0.0f, "dialogcannotfindweldingequipment".ToIdentifier(), 10.0f);
}
Abandon = true;
},
@@ -91,7 +91,7 @@ namespace Barotrauma
}
if (weldingTool.OwnInventory != null && 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 == CharacterTeamType.FriendlyNPC)
TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, "weldingfuel".ToIdentifier(), weldingTool.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
{
RemoveExisting = true
},
@@ -112,11 +112,11 @@ namespace Barotrauma
int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag("weldingfuel") && i.Condition > 1);
if (remainingOxygenTanks == 0)
{
character.Speak(TextManager.Get("DialogOutOfWeldingFuel"), null, 0.0f, "outofweldingfuel", 30.0f);
character.Speak(TextManager.Get("DialogOutOfWeldingFuel").Value, null, 0.0f, "outofweldingfuel".ToIdentifier(), 30.0f);
}
else if (remainingOxygenTanks < 4)
{
character.Speak(TextManager.Get("DialogLowOnWeldingFuel"), null, 0.0f, "lowonweldingfuel", 30.0f);
character.Speak(TextManager.Get("DialogLowOnWeldingFuel").Value, null, 0.0f, "lowonweldingfuel".ToIdentifier(), 30.0f);
}
}
return;
@@ -142,7 +142,7 @@ namespace Barotrauma
bool canOperate = toLeak.LengthSquared() < reach * reach;
if (canOperate)
{
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak),
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: Identifier.Empty, requireEquip: true, operateTarget: Leak),
onAbandon: () => Abandon = true,
onCompleted: () =>
{
@@ -160,7 +160,7 @@ namespace Barotrauma
{
UseDistanceRelativeToAimSourcePos = true,
CloseEnough = reach,
DialogueIdentifier = Leak.FlowTargetHull != null ? "dialogcannotreachleak" : null,
DialogueIdentifier = Leak.FlowTargetHull != null ? "dialogcannotreachleak".ToIdentifier() : Identifier.Empty,
TargetName = Leak.FlowTargetHull?.DisplayName,
CheckVisibility = false,
requiredCondition = () => Leak.Submarine == character.Submarine,
@@ -6,7 +6,7 @@ namespace Barotrauma
{
class AIObjectiveFixLeaks : AIObjectiveLoop<Gap>
{
public override string Identifier { get; set; } = "fix leaks";
public override Identifier Identifier { get; set; } = "fix leaks".ToIdentifier();
public override bool ForceRun => true;
public override bool KeepDivingGearOn => true;
public override bool AllowInAnySub => true;
@@ -9,7 +9,7 @@ namespace Barotrauma
{
class AIObjectiveGetItem : AIObjective
{
public override string Identifier { get; set; } = "get item";
public override Identifier Identifier { get; set; } = "get item".ToIdentifier();
public override bool AbandonWhenCannotCompleteSubjectives => false;
public override bool AllowMultipleInstances => true;
@@ -21,7 +21,7 @@ namespace Barotrauma
public float TargetCondition { get; set; } = 1;
public bool AllowDangerousPressure { get; set; }
public readonly ImmutableArray<string> IdentifiersOrTags;
public readonly ImmutableArray<Identifier> IdentifiersOrTags;
//if the item can't be found, spawn it in the character's inventory (used by outpost NPCs)
private bool spawnItemIfNotFound = false;
@@ -32,8 +32,8 @@ namespace Barotrauma
private bool isDoneSeeking;
public Item TargetItem => targetItem;
private int currSearchIndex;
public string[] ignoredContainerIdentifiers;
public string[] ignoredIdentifiersOrTags;
public Identifier[] ignoredContainerIdentifiers;
public Identifier[] ignoredIdentifiersOrTags;
private AIObjectiveGoTo goToObjective;
private float currItemPriority;
private readonly bool checkInventory;
@@ -83,10 +83,10 @@ namespace Barotrauma
moveToTarget = targetItem?.GetRootInventoryOwner();
}
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, Identifier identifierOrTag, AIObjectiveManager objectiveManager, bool equip = true, bool checkInventory = true, float priorityModifier = 1, bool spawnItemIfNotFound = false)
: this(character, new Identifier[] { identifierOrTag }, objectiveManager, equip, checkInventory, priorityModifier, spawnItemIfNotFound) { }
public AIObjectiveGetItem(Character character, IEnumerable<string> identifiersOrTags, AIObjectiveManager objectiveManager, bool equip = true, bool checkInventory = true, float priorityModifier = 1, bool spawnItemIfNotFound = false)
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;
@@ -97,27 +97,27 @@ namespace Barotrauma
ignoredIdentifiersOrTags = ParseIgnoredTags(identifiersOrTags).ToArray();
}
public static IEnumerable<string> ParseGearTags(IEnumerable<string> identifiersOrTags)
public static IEnumerable<Identifier> ParseGearTags(IEnumerable<Identifier> identifiersOrTags)
{
var tags = new List<string>();
foreach (string tag in identifiersOrTags)
var tags = new List<Identifier>();
foreach (Identifier tag in identifiersOrTags)
{
if (!tag.Contains('!'))
if (!tag.Contains("!"))
{
tags.Add(tag.ToLowerInvariant());
tags.Add(tag);
}
}
return tags;
}
public static IEnumerable<string> ParseIgnoredTags(IEnumerable<string> identifiersOrTags)
public static IEnumerable<Identifier> ParseIgnoredTags(IEnumerable<Identifier> identifiersOrTags)
{
var ignoredTags = new List<string>();
foreach (string tag in identifiersOrTags)
var ignoredTags = new List<Identifier>();
foreach (Identifier tag in identifiersOrTags)
{
if (tag.Contains('!'))
if (tag.Contains("!"))
{
ignoredTags.Add(tag.Remove("!").ToLowerInvariant());
ignoredTags.Add(tag.Remove("!"));
}
}
return ignoredTags;
@@ -177,7 +177,7 @@ namespace Barotrauma
if (dangerousPressure)
{
#if DEBUG
string itemName = targetItem != null ? targetItem.Name : IdentifiersOrTags.FirstOrDefault();
string itemName = targetItem != null ? targetItem.Name : IdentifiersOrTags.FirstOrDefault().Value;
DebugConsole.NewMessage($"{character.Name}: Seeking item ({itemName}) aborted, because the pressure is dangerous.", Color.Yellow);
#endif
Abandon = true;
@@ -480,7 +480,7 @@ namespace Barotrauma
}
else
{
Entity.Spawner.AddToSpawnQueue(prefab, character.Inventory, onSpawned: (Item spawnedItem) =>
Entity.Spawner.AddItemToSpawnQueue(prefab, character.Inventory, onSpawned: (Item spawnedItem) =>
{
targetItem = spawnedItem;
if (character.TeamID == CharacterTeamType.FriendlyNPC && (character.Submarine?.Info.IsOutpost ?? false))
@@ -528,14 +528,13 @@ namespace Barotrauma
private bool CheckItem(Item item)
{
if (!item.IsInteractable(character)) { return false; }
if (item.IsThisOrAnyContainerIgnoredByAI(character)) { return false; }
if (!item.HasAccess(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 (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));
return IdentifiersOrTags.Any(id => id == item.Prefab.Identifier || item.HasTag(id) || (AllowVariants && !item.Prefab.VariantOf.IsEmpty && item.Prefab.VariantOf == id));
}
public override void Reset()
@@ -575,9 +574,9 @@ namespace Barotrauma
if (!character.IsOnPlayerTeam) { return; }
if (objectiveManager.CurrentOrder != objectiveManager.CurrentObjective) { return; }
if (CannotFindDialogueCondition != null && !CannotFindDialogueCondition()) { return; }
string msg = TextManager.Get(CannotFindDialogueIdentifierOverride, returnNull: true) ?? TextManager.Get("dialogcannotfinditem", returnNull: true);
if (msg == null) { return; }
character.Speak(msg, identifier: "dialogcannotfinditem", minDurationBetweenSimilar: 20.0f);
LocalizedString msg = TextManager.Get(CannotFindDialogueIdentifierOverride, "dialogcannotfinditem");
if (msg.IsNullOrEmpty() || !msg.Loaded) { return; }
character.Speak(msg.Value, identifier: "dialogcannotfinditem".ToIdentifier(), minDurationBetweenSimilar: 20.0f);
}
}
}
@@ -8,7 +8,7 @@ namespace Barotrauma
{
class AIObjectiveGetItems : AIObjective
{
public override string Identifier { get; set; } = "get items";
public override Identifier Identifier { get; set; } = "get items".ToIdentifier();
public override string DebugTag => $"{Identifier}";
public override bool KeepDivingGearOn => true;
public override bool AllowMultipleInstances => true;
@@ -24,13 +24,13 @@ namespace Barotrauma
public bool RequireLoaded { get; set; }
public bool RequireAllItems { get; set; }
private readonly ImmutableArray<string> gearTags;
private readonly string[] ignoredTags;
private readonly ImmutableArray<Identifier> gearTags;
private readonly Identifier[] 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)
public AIObjectiveGetItems(Character character, AIObjectiveManager objectiveManager, IEnumerable<Identifier> identifiersOrTags, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier)
{
gearTags = AIObjectiveGetItem.ParseGearTags(identifiersOrTags).ToImmutableArray();
ignoredTags = AIObjectiveGetItem.ParseIgnoredTags(identifiersOrTags).ToArray();
@@ -47,7 +47,7 @@ namespace Barotrauma
}
if (!subObjectivesCreated)
{
foreach (string tag in gearTags)
foreach (Identifier tag in gearTags)
{
if (subObjectives.Any(so => so is AIObjectiveGetItem getItem && getItem.IdentifiersOrTags.Contains(tag))) { continue; }
int count = gearTags.Count(t => t == tag);
@@ -1,6 +1,7 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Barotrauma.Extensions;
@@ -8,7 +9,7 @@ namespace Barotrauma
{
class AIObjectiveGoTo : AIObjective
{
public override string Identifier { get; set; } = "go to";
public override Identifier Identifier { get; set; } = "go to".ToIdentifier();
private AIObjectiveFindDivingGear findDivingGear;
private readonly bool repeat;
@@ -96,8 +97,8 @@ namespace Barotrauma
public override bool AllowOutsideSubmarine => AllowGoingOutside;
public override bool AllowInAnySub => true;
public string DialogueIdentifier { get; set; } = "dialogcannotreachtarget";
public string TargetName { get; set; }
public Identifier DialogueIdentifier { get; set; } = "dialogcannotreachtarget".ToIdentifier();
public LocalizedString TargetName { get; set; }
public ISpatialEntity Target { get; private set; }
@@ -180,9 +181,11 @@ namespace Barotrauma
if (DialogueIdentifier == null) { return; }
if (!SpeakIfFails) { return; }
if (SpeakCannotReachCondition != null && !SpeakCannotReachCondition()) { return; }
string msg = TargetName == null ? TextManager.Get(DialogueIdentifier, true) : TextManager.GetWithVariable(DialogueIdentifier, "[name]", TargetName, formatCapitals: !(Target is Character));
if (msg == null) { return; }
character.Speak(msg, identifier: DialogueIdentifier, minDurationBetweenSimilar: 20.0f);
LocalizedString msg = TargetName == null ?
TextManager.Get(DialogueIdentifier) :
TextManager.GetWithVariable(DialogueIdentifier, "[name]".ToIdentifier(), TargetName, formatCapitals: Target is Character ? FormatCapitals.No : FormatCapitals.Yes);
if (msg.IsNullOrEmpty() || !msg.Loaded) { return; }
character.Speak(msg.Value, identifier: DialogueIdentifier, minDurationBetweenSimilar: 20.0f);
}
public void ForceAct(float deltaTime) => Act(deltaTime);
@@ -382,13 +385,23 @@ namespace Barotrauma
{
useScooter = false;
checkScooterTimer = checkScooterTime * Rand.Range(0.75f, 1.25f);
string scooterTag = "scooter";
string batteryTag = "mobilebattery";
Identifier scooterTag = "scooter".ToIdentifier();
Identifier batteryTag = "mobilebattery".ToIdentifier();
Item scooter = null;
float closeEnough = 250;
float squaredDistance = Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition);
bool shouldUseScooter = squaredDistance > closeEnough * closeEnough && (!Mimic ||
(targetCharacter != null && targetCharacter.HasEquippedItem(scooterTag, allowBroken: false)) || squaredDistance > Math.Pow(closeEnough * 2, 2));
bool shouldUseScooter = Mimic && targetCharacter != null && targetCharacter.HasEquippedItem(scooterTag, allowBroken: false);
if (!shouldUseScooter)
{
float threshold = 500;
if (isInside)
{
Vector2 diff = Target.WorldPosition - character.WorldPosition;
shouldUseScooter = Math.Abs(diff.X) > threshold || Math.Abs(diff.Y) > 150;
}
else
{
shouldUseScooter = Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition) > threshold * threshold;
}
}
if (HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> equippedScooters, recursive: false, requireEquipped: true))
{
// Currently equipped scooter
@@ -424,8 +437,7 @@ namespace Barotrauma
}
}
}
bool isScooterEquipped = scooter != null && character.HasEquippedItem(scooter);
if (scooter != null && isScooterEquipped)
if (scooter != null && character.HasEquippedItem(scooter))
{
if (shouldUseScooter)
{
@@ -534,6 +546,7 @@ namespace Barotrauma
void UseScooter(Vector2 targetWorldPos)
{
if (!character.HasEquippedItem("scooter".ToIdentifier())) { return; }
SteeringManager.Reset();
character.CursorPosition = targetWorldPos;
if (character.Submarine != null)
@@ -542,19 +555,26 @@ namespace Barotrauma
}
Vector2 diff = character.CursorPosition - character.Position;
Vector2 dir = Vector2.Normalize(diff);
float sqrDist = diff.LengthSquared();
if (sqrDist > MathUtils.Pow2(CloseEnough * 1.5f))
if (character.CurrentHull == null && IsFollowOrderObjective)
{
SteeringManager.SteeringManual(1.0f, dir);
}
else
{
float dot = Vector2.Dot(dir, VectorExtensions.Forward(character.AnimController.Collider.Rotation + MathHelper.PiOver2));
bool isFacing = dot > 0.9f;
if (!isFacing && sqrDist > MathUtils.Pow2(CloseEnough))
float sqrDist = diff.LengthSquared();
if (sqrDist > MathUtils.Pow2(CloseEnough * 1.5f))
{
SteeringManager.SteeringManual(1.0f, dir);
}
else
{
float dot = Vector2.Dot(dir, VectorExtensions.Forward(character.AnimController.Collider.Rotation + MathHelper.PiOver2));
bool isFacing = dot > 0.9f;
if (!isFacing && sqrDist > MathUtils.Pow2(CloseEnough))
{
SteeringManager.SteeringManual(1.0f, dir);
}
}
}
else
{
SteeringManager.SteeringManual(1.0f, dir);
}
character.SetInput(InputType.Aim, false, true);
character.SetInput(InputType.Shoot, false, true);
@@ -10,7 +10,7 @@ namespace Barotrauma
{
class AIObjectiveIdle : AIObjective
{
public override string Identifier { get; set; } = "idle";
public override Identifier Identifier { get; set; } = "idle".ToIdentifier();
public override bool AllowAutomaticItemUnequipping => true;
public override bool AllowInAnySub => true;
@@ -93,7 +93,7 @@ namespace Barotrauma
public override bool IsLoop { get => true; set => throw new Exception("Trying to set the value for IsLoop from: " + Environment.StackTrace.CleanupStackTrace()); }
public readonly HashSet<string> PreferredOutpostModuleTypes = new HashSet<string>();
public readonly HashSet<Identifier> PreferredOutpostModuleTypes = new HashSet<Identifier>();
public void CalculatePriority(float max = 0)
{
@@ -391,7 +391,7 @@ namespace Barotrauma
{
targetHulls.Clear();
hullWeights.Clear();
foreach (var hull in Hull.hullList)
foreach (var hull in Hull.HullList)
{
if (character.Submarine == null) { break; }
if (HumanAIController.UnsafeHulls.Contains(hull)) { continue; }
@@ -10,7 +10,7 @@ namespace Barotrauma
{
class AIObjectiveLoadItem : AIObjective
{
public override string Identifier { get; set; } = "load item";
public override Identifier Identifier { get; set; } = "load item".ToIdentifier();
public override bool IsLoop
{
get => true;
@@ -20,9 +20,9 @@ namespace Barotrauma
private AIObjectiveLoadItems.ItemCondition TargetItemCondition { get; }
private Item Container { get; }
private ItemContainer ItemContainer { get; }
private ImmutableArray<string> TargetContainerTags { get; }
private ImmutableHashSet<string> ValidContainableItemIdentifiers { get; }
private static Dictionary<ItemPrefab, ImmutableHashSet<string>> AllValidContainableItemIdentifiers { get; } = new Dictionary<ItemPrefab, ImmutableHashSet<string>>();
private ImmutableArray<Identifier> TargetContainerTags { get; }
private ImmutableHashSet<Identifier> ValidContainableItemIdentifiers { get; }
private static Dictionary<ItemPrefab, ImmutableHashSet<Identifier>> AllValidContainableItemIdentifiers { get; } = new Dictionary<ItemPrefab, ImmutableHashSet<Identifier>>();
private int itemIndex = 0;
private AIObjectiveDecontainItem decontainObjective;
@@ -30,7 +30,7 @@ namespace Barotrauma
private Item targetItem;
private readonly string abandonGetItemDialogueIdentifier = "dialogcannotfindloadable";
public AIObjectiveLoadItem(Item container, ImmutableArray<string> targetTags, AIObjectiveLoadItems.ItemCondition targetCondition, string option, Character character, AIObjectiveManager objectiveManager, float priorityModifier)
public AIObjectiveLoadItem(Item container, ImmutableArray<Identifier> targetTags, AIObjectiveLoadItems.ItemCondition targetCondition, Identifier option, Character character, AIObjectiveManager objectiveManager, float priorityModifier)
: base(character, objectiveManager, priorityModifier)
{
Container = container;
@@ -42,7 +42,7 @@ namespace Barotrauma
}
TargetContainerTags = targetTags;
TargetItemCondition = targetCondition;
if (!string.IsNullOrEmpty(option))
if (!option.IsEmpty)
{
string optionSpecificDialogueIdentifier = $"{abandonGetItemDialogueIdentifier}.{option}";
if (TextManager.ContainsTag(optionSpecificDialogueIdentifier))
@@ -63,7 +63,7 @@ namespace Barotrauma
private enum CheckStatus { Unfinished, Finished }
private ImmutableHashSet<string> GetValidContainableItemIdentifiers()
private ImmutableHashSet<Identifier> GetValidContainableItemIdentifiers()
{
if (AllValidContainableItemIdentifiers.TryGetValue(Container.Prefab, out var existingIdentifiers))
{
@@ -75,7 +75,7 @@ namespace Barotrauma
var potentialContainablePrefabs = MapEntityPrefab.List
.Where(mep => mep is ItemPrefab ip && ItemContainer.ContainableItemIdentifiers.Any(i => i == ip.Identifier || ip.Tags.Contains(i)))
.Cast<ItemPrefab>();
var validContainableItemIdentifiers = new HashSet<string>();
var validContainableItemIdentifiers = new HashSet<Identifier>();
foreach (var component in Container.Components)
{
if (CheckComponent() == CheckStatus.Finished)
@@ -125,7 +125,7 @@ namespace Barotrauma
useDefaultContainableItemIdentifiers = false;
if (statusEffect.TargetIdentifiers != null)
{
foreach (string target in statusEffect.TargetIdentifiers)
foreach (Identifier target in statusEffect.TargetIdentifiers)
{
foreach (var prefab in potentialContainablePrefabs)
{
@@ -308,11 +308,9 @@ namespace Barotrauma
if (rootInventoryOwner is Item parentItem)
{
if (parentItem.HasTag("donttakeitems")) { return false; }
if (!(parentItem.GetComponent<ItemContainer>()?.HasAccess(character) ?? true)) { return false; }
}
if (item.IsThisOrAnyContainerIgnoredByAI(character)) { return false; }
if (!item.HasAccess(character)) { return false; }
if (!character.HasItem(item) && !CanEquip(item)) { return false; }
if (!ItemContainer.HasAccess(character)) { return false; }
if (!ItemContainer.CanBeContained(item)) { return false; }
if (AIObjectiveLoadItems.ItemMatchesTargetCondition(item, TargetItemCondition)) { return false; }
if (TargetItemCondition == AIObjectiveLoadItems.ItemCondition.Full)
@@ -9,11 +9,11 @@ namespace Barotrauma
{
class AIObjectiveLoadItems : AIObjectiveLoop<Item>
{
public override string Identifier { get; set; } = "load items";
public override Identifier Identifier { get; set; } = "load items".ToIdentifier();
protected override float IgnoreListClearInterval => 20.0f;
protected override bool ResetWhenClearingIgnoreList => false;
private ImmutableArray<string> TargetContainerTags { get; }
private ImmutableArray<Identifier> TargetContainerTags { get; }
private List<Item> TargetContainers { get; } = new List<Item>();
private ItemCondition TargetCondition { get; }
@@ -23,7 +23,7 @@ namespace Barotrauma
Full
}
public AIObjectiveLoadItems(Character character, AIObjectiveManager objectiveManager, string option, ImmutableArray<string> containerTags, Item targetContainer = null, float priorityModifier = 1)
public AIObjectiveLoadItems(Character character, AIObjectiveManager objectiveManager, Identifier option, ImmutableArray<Identifier> containerTags, Item targetContainer = null, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier, option)
{
if ((containerTags == null || containerTags.None()) && targetContainer == null)
@@ -50,19 +50,17 @@ namespace Barotrauma
return true;
}
public static bool IsValidTarget(Item item, Character character, ImmutableArray<string>? targetContainerTags = null, ItemCondition? targetCondition = null)
public static bool IsValidTarget(Item item, Character character, ImmutableArray<Identifier>? targetContainerTags = null, ItemCondition? targetCondition = null)
{
if (item == null) { return false; }
if (item.Removed) { return false; }
if (targetContainerTags.HasValue && !Order.TargetItemsMatchItem(targetContainerTags.Value, item)) { return false; }
if (targetContainerTags.HasValue && !OrderPrefab.TargetItemsMatchItem(targetContainerTags.Value, item)) { return false; }
if (!(item.GetComponent<ItemContainer>() is ItemContainer container)) { return false; }
if (container.Inventory == null) { return false; }
if (targetCondition.HasValue && container.Inventory.IsFull() && container.Inventory.AllItems.None(i => ItemMatchesTargetCondition(i, targetCondition.Value))) { return false; }
if (!AIObjectiveCleanupItems.IsItemInsideValidSubmarine(item, character)) { return false; }
if (item.GetRootInventoryOwner() is Character owner && owner != character) { return false; }
if (!item.IsInteractable(character)) { return false; }
if (item.IsThisOrAnyContainerIgnoredByAI(character)) { return false; }
if (!container.HasAccess(character)) { return false; }
if (!item.HasAccess(character)) { return false; }
// Ignore items that require power but don't have it
if (item.GetComponent<Powered>() is Powered powered && powered.PowerConsumption > 0 && powered.Voltage < powered.MinVoltage) { return false; }
return true;
@@ -36,7 +36,7 @@ namespace Barotrauma
return false;
}
public AIObjectiveLoop(Character character, AIObjectiveManager objectiveManager, float priorityModifier, string option = null)
public AIObjectiveLoop(Character character, AIObjectiveManager objectiveManager, float priorityModifier, Identifier option = default)
: base(character, objectiveManager, priorityModifier, option) { }
protected override void Act(float deltaTime) { }
@@ -38,7 +38,7 @@ namespace Barotrauma
}
}
public List<OrderInfo> CurrentOrders { get; } = new List<OrderInfo>();
public List<Order> CurrentOrders { get; } = new List<Order>();
/// <summary>
/// The AIObjective in <see cref="CurrentOrders"/> with the highest <see cref="AIObjective.Priority"/>
/// </summary>
@@ -123,23 +123,23 @@ namespace Barotrauma
int objectiveCount = Objectives.Count;
foreach (var autonomousObjective in character.Info.Job.Prefab.AutonomousObjectives)
{
var orderPrefab = Order.GetPrefab(autonomousObjective.identifier);
if (orderPrefab == null) { throw new Exception($"Could not find a matching prefab by the identifier: '{autonomousObjective.identifier}'"); }
var orderPrefab = OrderPrefab.Prefabs[autonomousObjective.Identifier];
if (orderPrefab == null) { throw new Exception($"Could not find a matching prefab by the identifier: '{autonomousObjective.Identifier}'"); }
Item item = null;
if (orderPrefab.MustSetTarget)
{
item = orderPrefab.GetMatchingItems(character.Submarine, mustBelongToPlayerSub: false, requiredTeam: character.Info.TeamID, interactableFor: character, orderOption: autonomousObjective.option)?.GetRandom();
item = orderPrefab.GetMatchingItems(character.Submarine, mustBelongToPlayerSub: false, requiredTeam: character.Info.TeamID, interactableFor: character)?.GetRandomUnsynced();
}
var order = new Order(orderPrefab, item ?? character.CurrentHull as Entity, orderPrefab.GetTargetItemComponent(item), orderGiver: character);
var order = new Order(orderPrefab, autonomousObjective.Option, item ?? character.CurrentHull as Entity, orderPrefab.GetTargetItemComponent(item), orderGiver: character);
if (order == null) { continue; }
if ((order.IgnoreAtOutpost || autonomousObjective.ignoreAtOutpost) && Level.IsLoadedOutpost && character.TeamID != CharacterTeamType.FriendlyNPC)
if ((order.IgnoreAtOutpost || autonomousObjective.IgnoreAtOutpost) && Level.IsLoadedOutpost && character.TeamID != CharacterTeamType.FriendlyNPC)
{
if (Submarine.MainSub != null && Submarine.MainSub.DockedTo.None(s => s.TeamID != CharacterTeamType.FriendlyNPC && s.TeamID != character.TeamID))
{
continue;
}
}
var objective = CreateObjective(order, autonomousObjective.option, character, autonomousObjective.priorityModifier);
var objective = CreateObjective(order, autonomousObjective.PriorityModifier);
if (objective != null && objective.CanBeCompleted)
{
AddObjective(objective, delay: Rand.Value() / 2);
@@ -324,7 +324,7 @@ namespace Barotrauma
SortObjectives();
}
public void SetOrder(Order order, string option, int priority, Character orderGiver, bool speak)
public void SetOrder(Order order, bool speak)
{
if (character.IsDead)
{
@@ -336,13 +336,13 @@ namespace Barotrauma
}
ClearIgnored();
if (order == null || order.Identifier == "dismissed")
if (order == null || order.IsDismissal)
{
if (!string.IsNullOrEmpty(option))
if (order.Option != Identifier.Empty)
{
if (CurrentOrders.Any(o => o.MatchesDismissedOrder(option)))
if (CurrentOrders.Any(o => o.MatchesDismissedOrder(order.Option)))
{
var dismissedOrderInfo = CurrentOrders.First(o => o.MatchesDismissedOrder(option));
var dismissedOrderInfo = CurrentOrders.First(o => o.MatchesDismissedOrder(order.Option));
CurrentOrders.Remove(dismissedOrderInfo);
}
}
@@ -357,18 +357,18 @@ namespace Barotrauma
{
if (CurrentOrders.Count <= i) { break; }
var currentOrder = CurrentOrders[i];
if (currentOrder.Objective == null || currentOrder.MatchesOrder(order, option))
if (currentOrder.Objective == null || currentOrder.MatchesOrder(order))
{
CurrentOrders.RemoveAt(i);
continue;
}
var currentOrderInfo = character.GetCurrentOrder(currentOrder.Order, currentOrder.OrderOption);
if (currentOrderInfo.HasValue)
var currentOrderInfo = character.GetCurrentOrder(currentOrder);
if (currentOrderInfo is Order)
{
int currentPriority = currentOrderInfo.Value.ManualPriority;
int currentPriority = currentOrderInfo.ManualPriority;
if (currentOrder.ManualPriority != currentPriority)
{
CurrentOrders[i] = new OrderInfo(currentOrder, currentPriority);
CurrentOrders[i] = currentOrder.WithManualPriority(currentPriority);
}
}
else
@@ -377,46 +377,46 @@ namespace Barotrauma
}
}
var newCurrentOrder = CreateObjective(order, option, orderGiver);
if (newCurrentOrder != null)
var newCurrentObjective = CreateObjective(order);
if (newCurrentObjective != null)
{
newCurrentOrder.Abandoned += () => DismissSelf(order, option);
CurrentOrders.Add(new OrderInfo(order, option, priority, newCurrentOrder));
newCurrentObjective.Abandoned += () => DismissSelf(order);
CurrentOrders.Add(order.WithObjective(newCurrentObjective));
}
if (!HasOrders())
{
// Recreate objectives, because some of them may be removed, if impossible to complete (e.g. due to path finding)
CreateAutonomousObjectives();
}
else if (newCurrentOrder != null)
else if (newCurrentObjective != null)
{
if (speak && character.IsOnPlayerTeam)
{
string msg = newCurrentOrder.IsAllowed ? TextManager.Get("DialogAffirmative") : TextManager.Get("DialogNegative");
character.Speak(msg, delay: 1.0f);
LocalizedString msg = newCurrentObjective.IsAllowed ? TextManager.Get("DialogAffirmative") : TextManager.Get("DialogNegative");
character.Speak(msg.Value, delay: 1.0f);
}
}
}
public AIObjective CreateObjective(Order order, string option, Character orderGiver, float priorityModifier = 1)
public AIObjective CreateObjective(Order order, float priorityModifier = 1)
{
if (order == null || order.Identifier == "dismissed") { return null; }
if (order == null || order.IsDismissal) { return null; }
AIObjective newObjective;
switch (order.Identifier.ToLowerInvariant())
switch (order.Identifier.Value.ToLowerInvariant())
{
case "follow":
if (orderGiver == null) { return null; }
newObjective = new AIObjectiveGoTo(orderGiver, character, this, repeat: true, priorityModifier: priorityModifier)
if (order.OrderGiver == null) { return null; }
newObjective = new AIObjectiveGoTo(order.OrderGiver, character, this, repeat: true, priorityModifier: priorityModifier)
{
CloseEnough = Rand.Range(80f, 100f),
CloseEnoughMultiplier = Math.Min(1 + HumanAIController.CountCrew(c => c.ObjectiveManager.HasOrder<AIObjectiveGoTo>(o => o.Target == orderGiver), onlyBots: true) * Rand.Range(0.8f, 1f), 4),
CloseEnoughMultiplier = Math.Min(1 + HumanAIController.CountCrew(c => c.ObjectiveManager.HasOrder<AIObjectiveGoTo>(o => o.Target == order.OrderGiver), onlyBots: true) * Rand.Range(0.8f, 1f), 4),
ExtraDistanceOutsideSub = 100,
ExtraDistanceWhileSwimming = 100,
AllowGoingOutside = true,
IgnoreIfTargetDead = true,
IsFollowOrderObjective = true,
Mimic = character.IsOnPlayerTeam,
DialogueIdentifier = "dialogcannotreachplace"
DialogueIdentifier = "dialogcannotreachplace".ToIdentifier()
};
break;
case "wait":
@@ -426,14 +426,14 @@ namespace Barotrauma
};
break;
case "return":
newObjective = new AIObjectiveReturn(character, orderGiver, this, priorityModifier: priorityModifier);
newObjective.Completed += () => DismissSelf(order, option);
newObjective = new AIObjectiveReturn(character, order.OrderGiver, this, priorityModifier: priorityModifier);
newObjective.Completed += () => DismissSelf(order);
break;
case "fixleaks":
newObjective = new AIObjectiveFixLeaks(character, this, priorityModifier: priorityModifier, prioritizedHull: order.TargetEntity as Hull);
break;
case "chargebatteries":
newObjective = new AIObjectiveChargeBatteries(character, this, option, priorityModifier);
newObjective = new AIObjectiveChargeBatteries(character, this, order.Option, priorityModifier);
break;
case "rescue":
newObjective = new AIObjectiveRescueAll(character, this, priorityModifier);
@@ -450,16 +450,16 @@ namespace Barotrauma
if (order.TargetItemComponent is Pump targetPump)
{
if (!order.TargetItemComponent.Item.IsInteractable(character)) { return null; }
newObjective = new AIObjectiveOperateItem(targetPump, character, this, option, false, priorityModifier: priorityModifier)
newObjective = new AIObjectiveOperateItem(targetPump, character, this, order.Option, false, priorityModifier: priorityModifier)
{
IsLoop = false,
Override = orderGiver != null && orderGiver.IsCommanding
Override = order.OrderGiver is { IsCommanding: true }
};
newObjective.Completed += () => DismissSelf(order, option);
newObjective.Completed += () => DismissSelf(order);
}
else
{
newObjective = new AIObjectivePumpWater(character, this, option, priorityModifier: priorityModifier);
newObjective = new AIObjectivePumpWater(character, this, order.Option, priorityModifier: priorityModifier);
}
break;
case "extinguishfires":
@@ -479,22 +479,22 @@ namespace Barotrauma
if (steering != null) { steering.PosToMaintain = steering.Item.Submarine?.WorldPosition; }
if (order.TargetItemComponent == null) { return null; }
if (!order.TargetItemComponent.Item.IsInteractable(character)) { return null; }
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option,
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, order.Option,
requireEquip: false, useController: order.UseController, controller: order.ConnectedController, priorityModifier: priorityModifier)
{
IsLoop = true,
// Don't override unless it's an order by a player
Override = orderGiver != null && orderGiver.IsCommanding
Override = order.OrderGiver != null && order.OrderGiver.IsCommanding
};
break;
case "setchargepct":
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, false, priorityModifier: priorityModifier)
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, order.Option, false, priorityModifier: priorityModifier)
{
IsLoop = false,
Override = !character.IsDismissed,
completionCondition = () =>
{
if (float.TryParse(option, out float pct))
if (float.TryParse(order.Option.Value, out float pct))
{
var targetRatio = Math.Clamp(pct, 0f, 1f);
var currentRatio = (order.TargetItemComponent as PowerContainer).RechargeRatio;
@@ -532,7 +532,7 @@ namespace Barotrauma
newObjective = new AIObjectiveEscapeHandcuffs(character, this, priorityModifier: priorityModifier);
break;
case "prepareforexpedition":
newObjective = new AIObjectivePrepare(character, this, order.GetTargetItems(option), order.RequireItems)
newObjective = new AIObjectivePrepare(character, this, order.GetTargetItems(order.Option), order.RequireItems)
{
KeepActiveWhenReady = true,
CheckInventory = true,
@@ -548,7 +548,7 @@ namespace Barotrauma
}
else
{
prepareObjective = new AIObjectivePrepare(character, this, order.GetTargetItems(option), order.RequireItems)
prepareObjective = new AIObjectivePrepare(character, this, order.GetTargetItems(order.Option), order.RequireItems)
{
KeepActiveWhenReady = false,
CheckInventory = false,
@@ -559,20 +559,20 @@ namespace Barotrauma
prepareObjective.KeepActiveWhenReady = false;
prepareObjective.Equip = true;
newObjective = prepareObjective;
newObjective.Completed += () => DismissSelf(order, option);
newObjective.Completed += () => DismissSelf(order);
break;
case "loaditems":
newObjective = new AIObjectiveLoadItems(character, this, option, order.GetTargetItems(option), order.TargetEntity as Item, priorityModifier);
newObjective = new AIObjectiveLoadItems(character, this, order.Option, order.GetTargetItems(order.Option), order.TargetEntity as Item, priorityModifier);
break;
default:
if (order.TargetItemComponent == null) { return null; }
if (!order.TargetItemComponent.Item.IsInteractable(character)) { return null; }
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option,
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, order.Option,
requireEquip: false, useController: order.UseController, controller: order.ConnectedController, priorityModifier: priorityModifier)
{
IsLoop = true,
// Don't override unless it's an order by a player
Override = orderGiver != null && orderGiver.IsCommanding
Override = order.OrderGiver != null && order.OrderGiver.IsCommanding
};
if (newObjective.Abandon) { return null; }
break;
@@ -585,27 +585,26 @@ namespace Barotrauma
return newObjective;
}
private void DismissSelf(Order order, string option)
private void DismissSelf(Order order)
{
var currentOrder = CurrentOrders.FirstOrDefault(oi => oi.MatchesOrder(order, option));
if (currentOrder.Order == null)
var currentOrder = CurrentOrders.FirstOrDefault(oi => oi.MatchesOrder(order.Identifier, order.Option));
if (currentOrder == null)
{
#if DEBUG
DebugConsole.ThrowError("Tried to self-dismiss an order, but no matching current order was found");
#endif
return;
}
Order dismissOrder = Order.GetPrefab("dismissed");
var orderOption = Order.GetDismissOrderOption(currentOrder);
int priority = currentOrder.ManualPriority;
Order dismissOrder = currentOrder.GetDismissal();
#if CLIENT
if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.IsSinglePlayer)
{
GameMain.GameSession.CrewManager.SetCharacterOrder(character, dismissOrder, orderOption, priority, character);
GameMain.GameSession.CrewManager.SetCharacterOrder(character, dismissOrder);
}
#else
GameMain.Server?.SendOrderChatMessage(new OrderChatMessage(dismissOrder, orderOption, priority, currentOrder.Order.TargetSpatialEntity, character, character));
SetOrder(dismissOrder, orderOption, priority, character, speak: false);
GameMain.Server?.SendOrderChatMessage(new OrderChatMessage(dismissOrder, character, character));
SetOrder(dismissOrder, speak: false);
#endif
}
@@ -695,7 +694,7 @@ namespace Barotrauma
return 0;
}
public OrderInfo? GetCurrentOrderInfo()
public Order GetCurrentOrderInfo()
{
if (currentOrder == null) { return null; }
return CurrentOrders.FirstOrDefault(o => o.Objective == CurrentOrder);
@@ -8,7 +8,7 @@ namespace Barotrauma
{
class AIObjectiveOperateItem : AIObjective
{
public override string Identifier { get; set; } = "operate item";
public override Identifier Identifier { get; set; } = "operate item".ToIdentifier();
public override string DebugTag => $"{Identifier} {component.Name}";
public override bool AllowAutomaticItemUnequipping => true;
@@ -79,7 +79,7 @@ namespace Barotrauma
return Priority;
}
}
switch (Option)
switch (Option.Value.ToLowerInvariant())
{
case "shutdown":
if (!reactor.PowerOn)
@@ -146,7 +146,7 @@ namespace Barotrauma
return Priority;
}
public AIObjectiveOperateItem(ItemComponent item, Character character, AIObjectiveManager objectiveManager, string option, bool requireEquip,
public AIObjectiveOperateItem(ItemComponent item, Character character, AIObjectiveManager objectiveManager, Identifier option, bool requireEquip,
Entity operateTarget = null, bool useController = false, ItemComponent controller = null, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier, option)
{
@@ -181,7 +181,7 @@ namespace Barotrauma
{
if (character.IsOnPlayerTeam)
{
character.Speak(TextManager.GetWithVariable("DialogCantFindController", "[item]", component.Item.Name, true), null, 2.0f, "cantfindcontroller", 30.0f);
character.Speak(TextManager.GetWithVariable("DialogCantFindController", "[item]", component.Item.Name).Value, delay: 2.0f, identifier: "cantfindcontroller".ToIdentifier(), minDurationBetweenSimilar: 30.0f);
}
Abandon = true;
return;
@@ -8,7 +8,7 @@ namespace Barotrauma
{
class AIObjectivePrepare : AIObjective
{
public override string Identifier { get; set; } = "prepare";
public override Identifier Identifier { get; set; } = "prepare".ToIdentifier();
public override string DebugTag => $"{Identifier}";
public override bool KeepDivingGearOn => true;
public override bool KeepDivingGearOnAlsoWhenInactive => true;
@@ -19,8 +19,8 @@ namespace Barotrauma
private AIObjectiveGetItems getMultipleItemsObjective;
private bool subObjectivesCreated;
private readonly Item targetItem;
private readonly ImmutableArray<string> requiredItems;
private readonly ImmutableArray<string> optionalItems;
private readonly ImmutableArray<Identifier> requiredItems;
private readonly ImmutableArray<Identifier> optionalItems;
private readonly HashSet<Item> items = new HashSet<Item>();
public bool KeepActiveWhenReady { get; set; }
public bool CheckInventory { get; set; }
@@ -43,7 +43,7 @@ namespace Barotrauma
this.targetItem = targetItem;
}
public AIObjectivePrepare(Character character, AIObjectiveManager objectiveManager, IEnumerable<string> optionalItems, IEnumerable<string> requiredItems = null, float priorityModifier = 1)
public AIObjectivePrepare(Character character, AIObjectiveManager objectiveManager, IEnumerable<Identifier> optionalItems, IEnumerable<Identifier> requiredItems = null, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier)
{
this.optionalItems = optionalItems.ToImmutableArray();
@@ -98,7 +98,7 @@ namespace Barotrauma
{
getAllItemsObjective = CreateObjectives(requiredItems, requireAll: true);
}
AIObjectiveGetItems CreateObjectives(IEnumerable<string> itemTags, bool requireAll)
AIObjectiveGetItems CreateObjectives(IEnumerable<Identifier> itemTags, bool requireAll)
{
AIObjectiveGetItems objectiveReference = null;
if (!TryAddSubObjective(ref objectiveReference, () => new AIObjectiveGetItems(character, objectiveManager, itemTags)
@@ -148,7 +148,7 @@ namespace Barotrauma
}
else
{
IEnumerable<string> allItems = optionalItems;
IEnumerable<Identifier> allItems = optionalItems;
if (requiredItems != null && requiredItems.Any())
{
allItems = requiredItems;
@@ -9,13 +9,13 @@ namespace Barotrauma
{
class AIObjectivePumpWater : AIObjectiveLoop<Pump>
{
public override string Identifier { get; set; } = "pump water";
public override Identifier Identifier { get; set; } = "pump water".ToIdentifier();
public override bool KeepDivingGearOn => true;
public override bool AllowAutomaticItemUnequipping => true;
private IEnumerable<Pump> pumpList;
public AIObjectivePumpWater(Character character, AIObjectiveManager objectiveManager, string option, float priorityModifier = 1)
public AIObjectivePumpWater(Character character, AIObjectiveManager objectiveManager, Identifier option, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier, option) { }
protected override void FindTargets()
@@ -48,7 +48,7 @@ namespace Barotrauma
{
if (pumpList == null)
{
if (character == null || character.Submarine == null) { return new Pump[0]; }
if (character == null || character.Submarine == null) { return Array.Empty<Pump>(); }
pumpList = character.Submarine.GetItems(true).Select(i => i.GetComponent<Pump>()).Where(p => p != null);
}
return pumpList;
@@ -9,7 +9,7 @@ namespace Barotrauma
{
class AIObjectiveRepairItem : AIObjective
{
public override string Identifier { get; set; } = "repair item";
public override Identifier Identifier { get; set; } = "repair item".ToIdentifier();
public override bool AllowInAnySub => true;
@@ -70,7 +70,7 @@ namespace Barotrauma
float reduction = isPriority ? 1 : isSelected ? 2 : 3;
float max = AIObjectiveManager.LowestOrderPriority - reduction;
float highestWeight = -1;
foreach (string tag in Item.Prefab.Tags)
foreach (Identifier tag in Item.Prefab.Tags)
{
if (JobPrefab.ItemRepairPriorities.TryGetValue(tag, out float weight) && weight > highestWeight)
{
@@ -92,7 +92,7 @@ namespace Barotrauma
IsCompleted = Item.IsFullCondition;
if (character.IsOnPlayerTeam && IsCompleted && IsRepairing())
{
character.Speak(TextManager.GetWithVariable("DialogItemRepaired", "[itemname]", Item.Name, true), null, 0.0f, "itemrepaired", 10.0f);
character.Speak(TextManager.GetWithVariable("DialogItemRepaired", "[itemname]", Item.Name, FormatCapitals.Yes).Value, null, 0.0f, "itemrepaired".ToIdentifier(), 10.0f);
}
return IsCompleted;
}
@@ -118,7 +118,7 @@ namespace Barotrauma
{
if (character.IsOnPlayerTeam)
{
getItemObjective.Abandoned += () => character.Speak(TextManager.Get("dialogcannotfindrequireditemtorepair"), null, 0.0f, "dialogcannotfindrequireditemtorepair", 10.0f);
getItemObjective.Abandoned += () => character.Speak(TextManager.Get("dialogcannotfindrequireditemtorepair").Value, null, 0.0f, "dialogcannotfindrequireditemtorepair".ToIdentifier(), 10.0f);
}
}
subObjectives.Add(getItemObjective);
@@ -206,7 +206,7 @@ namespace Barotrauma
{
if (character.IsOnPlayerTeam && IsRepairing())
{
character.Speak(TextManager.GetWithVariable("DialogCannotRepair", "[itemname]", Item.Name, true), null, 0.0f, "cannotrepair", 10.0f);
character.Speak(TextManager.GetWithVariable("DialogCannotRepair", "[itemname]", Item.Name, FormatCapitals.Yes).Value, null, 0.0f, "cannotrepair".ToIdentifier(), 10.0f);
}
repairable.StopRepairing(character);
}
@@ -243,7 +243,7 @@ namespace Barotrauma
Abandon = true;
if (character.IsOnPlayerTeam && IsRepairing())
{
character.Speak(TextManager.GetWithVariable("DialogCannotRepair", "[itemname]", Item.Name, true), null, 0.0f, "cannotrepair", 10.0f);
character.Speak(TextManager.GetWithVariable("DialogCannotRepair", "[itemname]", Item.Name, FormatCapitals.Yes).Value, null, 0.0f, "cannotrepair".ToIdentifier(), 10.0f);
}
});
}
@@ -9,12 +9,12 @@ namespace Barotrauma
{
class AIObjectiveRepairItems : AIObjectiveLoop<Item>
{
public override string Identifier { get; set; } = "repair items";
public override Identifier Identifier { get; set; } = "repair items".ToIdentifier();
/// <summary>
/// If set, only fix items where required skill matches this.
/// </summary>
public string RelevantSkill;
public Identifier RelevantSkill;
public Item PrioritizedItem { get; private set; }
@@ -72,9 +72,9 @@ namespace Barotrauma
if (NearlyFullCondition(item)) { return false; }
}
}
if (!string.IsNullOrWhiteSpace(RelevantSkill))
if (!RelevantSkill.IsEmpty)
{
if (item.Repairables.None(r => r.requiredSkills.Any(s => s.Identifier.Equals(RelevantSkill, StringComparison.OrdinalIgnoreCase)))) { return false; }
if (item.Repairables.None(r => r.requiredSkills.Any(s => s.Identifier == RelevantSkill))) { return false; }
}
return !HumanAIController.IsItemRepairedByAnother(item, out _);
}
@@ -9,7 +9,7 @@ namespace Barotrauma
{
class AIObjectiveRescue : AIObjective
{
public override string Identifier { get; set; } = "rescue";
public override Identifier Identifier { get; set; } = "rescue".ToIdentifier();
public override bool ForceRun => true;
public override bool KeepDivingGearOn => true;
@@ -146,9 +146,10 @@ namespace Barotrauma
{
if (targetCharacter.CurrentHull != null && HumanAIController.VisibleHulls.Contains(targetCharacter.CurrentHull) && targetCharacter.CurrentHull.DisplayName != null)
{
character.Speak(TextManager.GetWithVariables("DialogFoundUnconsciousTarget", new string[2] { "[targetname]", "[roomname]" },
new string[2] { targetCharacter.Name, targetCharacter.CurrentHull.DisplayName }, new bool[2] { false, true }),
null, 1.0f, "foundunconscioustarget" + targetCharacter.Name, 60.0f);
character.Speak(TextManager.GetWithVariables("DialogFoundUnconsciousTarget",
("[targetname]", targetCharacter.Name, FormatCapitals.No),
("[roomname]", targetCharacter.CurrentHull.DisplayName, FormatCapitals.Yes)).Value,
null, 1.0f, $"foundunconscioustarget{targetCharacter.Name}".ToIdentifier(), 60.0f);
}
// Go to the target and select it
if (!character.CanInteractWith(targetCharacter))
@@ -158,7 +159,7 @@ namespace Barotrauma
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager)
{
CloseEnough = CloseEnoughToTreat,
DialogueIdentifier = "dialogcannotreachpatient",
DialogueIdentifier = "dialogcannotreachpatient".ToIdentifier(),
TargetName = targetCharacter.DisplayName
},
onCompleted: () => RemoveSubObjective(ref goToObjective),
@@ -216,7 +217,7 @@ namespace Barotrauma
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager)
{
CloseEnough = CloseEnoughToTreat,
DialogueIdentifier = "dialogcannotreachpatient",
DialogueIdentifier = "dialogcannotreachpatient".ToIdentifier(),
TargetName = targetCharacter.DisplayName
},
onCompleted: () => RemoveSubObjective(ref goToObjective),
@@ -233,18 +234,19 @@ namespace Barotrauma
{
if (targetCharacter.CurrentHull?.DisplayName != null)
{
character.Speak(TextManager.GetWithVariables("DialogFoundWoundedTarget", new string[2] { "[targetname]", "[roomname]" },
new string[2] { targetCharacter.Name, targetCharacter.CurrentHull.DisplayName }, new bool[2] { false, true }),
null, 1.0f, "foundwoundedtarget" + targetCharacter.Name, 60.0f);
character.Speak(TextManager.GetWithVariables("DialogFoundWoundedTarget",
("[targetname]", targetCharacter.Name, FormatCapitals.No),
("[roomname]", targetCharacter.CurrentHull.DisplayName, FormatCapitals.Yes)).Value,
null, 1.0f, $"foundwoundedtarget{targetCharacter.Name}".ToIdentifier(), 60.0f);
}
}
GiveTreatment(deltaTime);
}
}
private readonly List<string> suitableItemIdentifiers = new List<string>();
private readonly List<string> itemNameList = new List<string>();
private readonly Dictionary<string, float> currentTreatmentSuitabilities = new Dictionary<string, float>();
private readonly List<Identifier> suitableItemIdentifiers = new List<Identifier>();
private readonly List<LocalizedString> itemNameList = new List<LocalizedString>();
private readonly Dictionary<Identifier, float> currentTreatmentSuitabilities = new Dictionary<Identifier, float>();
private void GiveTreatment(float deltaTime)
{
if (targetCharacter == null)
@@ -281,7 +283,7 @@ namespace Barotrauma
if (affliction.Prefab == null) { throw new Exception("Affliction prefab was null"); }
float bestSuitability = 0.0f;
Item bestItem = null;
foreach (KeyValuePair<string, float> treatmentSuitability in affliction.Prefab.TreatmentSuitability)
foreach (KeyValuePair<Identifier, float> treatmentSuitability in affliction.Prefab.TreatmentSuitability)
{
if (currentTreatmentSuitabilities.ContainsKey(treatmentSuitability.Key) &&
currentTreatmentSuitabilities[treatmentSuitability.Key] > bestSuitability)
@@ -311,12 +313,12 @@ namespace Barotrauma
{
itemNameList.Clear();
suitableItemIdentifiers.Clear();
foreach (KeyValuePair<string, float> treatmentSuitability in currentTreatmentSuitabilities)
foreach (KeyValuePair<Identifier, float> treatmentSuitability in currentTreatmentSuitabilities)
{
if (treatmentSuitability.Value <= cprSuitability) { continue; }
if (MapEntityPrefab.Find(null, treatmentSuitability.Key, showErrorMessages: false) is ItemPrefab itemPrefab)
{
if (!Item.ItemList.Any(it => it.prefab.Identifier == treatmentSuitability.Key)) { continue; }
if (!Item.ItemList.Any(it => ((MapEntity)it).Prefab.Identifier == treatmentSuitability.Key)) { continue; }
suitableItemIdentifiers.Add(treatmentSuitability.Key);
//only list the first 4 items
if (itemNameList.Count < 4)
@@ -327,7 +329,7 @@ namespace Barotrauma
}
if (itemNameList.Any())
{
string itemListStr = "";
LocalizedString itemListStr = "";
if (itemNameList.Count == 1)
{
itemListStr = itemNameList[0];
@@ -337,33 +339,34 @@ namespace Barotrauma
//[treatment1] or [treatment2]
itemListStr = TextManager.GetWithVariables(
"DialogRequiredTreatmentOptionsLast",
new string[] { "[treatment1]", "[treatment2]" },
new string[] { itemNameList[0], itemNameList[1] });
("[treatment1]", itemNameList[0]),
("[treatment2]", itemNameList[1]));
}
else
{
//[treatment1], [treatment2], [treatment3] ... or [treatmentx]
itemListStr = TextManager.GetWithVariables(
"DialogRequiredTreatmentOptionsFirst",
new string[] { "[treatment1]", "[treatment2]" },
new string[] { itemNameList[0], itemNameList[1] });
("[treatment1]", itemNameList[0]),
("[treatment2]", itemNameList[1]));
for (int i = 2; i < itemNameList.Count - 1; i++)
{
itemListStr = TextManager.GetWithVariables(
"DialogRequiredTreatmentOptionsFirst",
new string[] { "[treatment1]", "[treatment2]" },
new string[] { itemListStr, itemNameList[i] });
("[treatment1]", itemListStr),
("[treatment2]", itemNameList[i]));
}
itemListStr = TextManager.GetWithVariables(
"DialogRequiredTreatmentOptionsLast",
new string[] { "[treatment1]", "[treatment2]" },
new string[] { itemListStr, itemNameList.Last() });
("[treatment1]", itemListStr),
("[treatment2]", itemNameList.Last()));
}
if (targetCharacter != character && character.IsOnPlayerTeam)
{
character.Speak(TextManager.GetWithVariables("DialogListRequiredTreatments", new string[2] { "[targetname]", "[treatmentlist]" },
new string[2] { targetCharacter.Name, itemListStr }, new bool[2] { false, true }),
null, 2.0f, "listrequiredtreatments" + targetCharacter.Name, 60.0f);
character.Speak(TextManager.GetWithVariables("DialogListRequiredTreatments",
("[targetname]", targetCharacter.Name, FormatCapitals.No),
("[treatmentlist]", itemListStr, FormatCapitals.Yes)).Value,
null, 2.0f, $"listrequiredtreatments{targetCharacter.Name}".ToIdentifier(), 60.0f);
}
RemoveSubObjective(ref getItemObjective);
TryAddSubObjective(ref getItemObjective,
@@ -374,13 +377,13 @@ namespace Barotrauma
Abandon = true;
if (character != targetCharacter && character.IsOnPlayerTeam)
{
character.Speak(TextManager.GetWithVariable("dialogcannottreatpatient", "[name]", targetCharacter.DisplayName, formatCapitals: false), identifier: "cannottreatpatient", minDurationBetweenSimilar: 20.0f);
character.Speak(TextManager.GetWithVariable("dialogcannottreatpatient", "[name]", targetCharacter.DisplayName, FormatCapitals.No).Value, identifier: "cannottreatpatient".ToIdentifier(), minDurationBetweenSimilar: 20.0f);
}
});
}
else if (cprSuitability <= 0)
{
character.Speak(TextManager.GetWithVariable("dialogcannottreatpatient", "[name]", targetCharacter.DisplayName, formatCapitals: false), identifier: "cannottreatpatient", minDurationBetweenSimilar: 20.0f);
character.Speak(TextManager.GetWithVariable("dialogcannottreatpatient", "[name]", targetCharacter.DisplayName, formatCapitals: FormatCapitals.No).Value, identifier: "cannottreatpatient".ToIdentifier(), minDurationBetweenSimilar: 20.0f);
Abandon = true;
}
}
@@ -388,7 +391,7 @@ namespace Barotrauma
else if (!targetCharacter.IsUnconscious)
{
//no suitable treatments found, not inside our own sub (= can't search for more treatments), the target isn't unconscious (= can't give CPR)
character.Speak(TextManager.GetWithVariable("dialogcannottreatpatient", "[name]", targetCharacter.DisplayName, formatCapitals: false), identifier: "cannottreatpatient", minDurationBetweenSimilar: 20.0f);
character.Speak(TextManager.GetWithVariable("dialogcannottreatpatient", "[name]", targetCharacter.DisplayName, formatCapitals: FormatCapitals.No).Value, identifier: "cannottreatpatient".ToIdentifier(), minDurationBetweenSimilar: 20.0f);
Abandon = true;
return;
}
@@ -425,7 +428,7 @@ namespace Barotrauma
}
if (remove)
{
Entity.Spawner?.AddToRemoveQueue(item);
Entity.Spawner?.AddItemToRemoveQueue(item);
}
}
@@ -434,8 +437,8 @@ namespace Barotrauma
bool isCompleted = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter);
if (isCompleted && targetCharacter != character && character.IsOnPlayerTeam)
{
character.Speak(TextManager.GetWithVariable("DialogTargetHealed", "[targetname]", targetCharacter.Name),
null, 1.0f, "targethealed" + targetCharacter.Name, 60.0f);
character.Speak(TextManager.GetWithVariable("DialogTargetHealed", "[targetname]", targetCharacter.Name).Value,
null, 1.0f, $"targethealed{targetCharacter.Name}".ToIdentifier(), 60.0f);
}
return isCompleted;
}
@@ -7,7 +7,7 @@ namespace Barotrauma
{
class AIObjectiveRescueAll : AIObjectiveLoop<Character>
{
public override string Identifier { get; set; } = "rescue all";
public override Identifier Identifier { get; set; } = "rescue all".ToIdentifier();
public override bool ForceRun => true;
public override bool InverseTargetEvaluation => true;
public override bool AllowOutsideSubmarine => true;
@@ -112,12 +112,15 @@ namespace Barotrauma
{
if (GetVitalityFactor(target) >= vitalityThreshold) { return false; }
}
if (target.Submarine != character.Submarine) { return false; }
if (character.Submarine != null)
{
// Don't allow going into another sub, unless it's connected and of the same team and type.
if (!character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, includingConnectedSubs: true)) { return false; }
}
else
{
return target.Submarine == null;
}
if (target != character && target.IsBot && HumanAIController.IsActive(target) && target.AIController is HumanAIController targetAI)
{
// Ignore all concious targets that are currently fighting, fleeing, fixing, or treating characters
@@ -6,7 +6,7 @@ namespace Barotrauma
{
class AIObjectiveReturn : AIObjective
{
public override string Identifier { get; set; } = "return";
public override Identifier Identifier { get; set; } = "return".ToIdentifier();
public Submarine ReturnTarget { get; }
private AIObjectiveGoTo moveInsideObjective, moveOutsideObjective;
@@ -93,7 +93,7 @@ namespace Barotrauma
// Target the closest airlock
float closestDist = 0;
Hull airlock = null;
foreach (Hull hull in Hull.hullList)
foreach (Hull hull in Hull.HullList)
{
if (hull.Submarine != targetHull.Submarine) { continue; }
if (!hull.IsTaggedAirlock()) { continue; }
@@ -210,10 +210,10 @@ namespace Barotrauma
SteeringManager?.Reset();
if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder == objectiveManager.CurrentObjective)
{
string msg = TextManager.Get("dialogcannotreturn", returnNull: true);
if (msg != null)
string msg = TextManager.Get("dialogcannotreturn").Value;
if (!msg.IsNullOrEmpty())
{
character.Speak(msg, identifier: "dialogcannotreturn", minDurationBetweenSimilar: 5.0f);
character.Speak(msg, identifier: "dialogcannotreturn".ToIdentifier(), minDurationBetweenSimilar: 5.0f);
}
}
}