v0.10.5.1

This commit is contained in:
Juan Pablo Arce
2020-09-22 11:31:56 -03:00
parent 44032d0ae0
commit 0002ad2c50
343 changed files with 12276 additions and 5023 deletions
@@ -17,8 +17,7 @@ namespace Barotrauma
public virtual bool AllowSubObjectiveSorting => false;
/// <summary>
/// Can there be multiple objective instaces of the same type? Currently multiple instances allowed only for main objectives and the subobjectives of objetive loops.
/// In theory, there could be multiple subobjectives of same type for concurrent objectives, but that would make things more complex -> potential issues
/// Can there be multiple objective instaces of the same type?
/// </summary>
public virtual bool AllowMultipleInstances => false;
@@ -29,7 +28,10 @@ namespace Barotrauma
public virtual bool ConcurrentObjectives => false;
public virtual bool KeepDivingGearOn => false;
public virtual bool UnequipItems => false;
/// <summary>
/// There's a separate property for diving suit and mask: KeepDivingGearOn.
/// </summary>
public virtual bool AllowAutomaticItemUnequipping => false;
public virtual bool AllowOutsideSubmarine => false;
public virtual bool AllowInFriendlySubs => false;
@@ -173,6 +175,7 @@ namespace Barotrauma
{
if (!AllowSubObjectiveSorting) { return; }
if (subObjectives.None()) { return; }
var previousSubObjective = subObjectives.First();
subObjectives.ForEach(so => so.GetPriority());
subObjectives.Sort((x, y) => y.Priority.CompareTo(x.Priority));
if (ConcurrentObjectives)
@@ -181,7 +184,13 @@ namespace Barotrauma
}
else
{
subObjectives.First().SortSubObjectives();
var currentSubObjective = subObjectives.First();
if (previousSubObjective != currentSubObjective)
{
previousSubObjective.OnDeselected();
currentSubObjective.OnSelected();
}
currentSubObjective.SortSubObjectives();
}
}
@@ -222,7 +231,7 @@ namespace Barotrauma
private void UpdateDevotion(float deltaTime)
{
var currentObjective = objectiveManager.CurrentObjective;
if (currentObjective != null && (currentObjective == this || currentObjective.subObjectives.Any(so => so == this)))
if (currentObjective != null && (currentObjective == this || currentObjective.subObjectives.FirstOrDefault() == this))
{
CumulatedDevotion += Devotion * deltaTime;
}
@@ -327,6 +336,7 @@ namespace Barotrauma
public virtual void Reset()
{
subObjectives.Clear();
isCompleted = false;
hasBeenChecked = false;
_abandon = false;
@@ -369,8 +379,7 @@ namespace Barotrauma
{
if (Check())
{
isCompleted = true;
OnCompleted();
IsCompleted = true;
}
}
return isCompleted;
@@ -1,16 +1,16 @@
using Barotrauma.Items.Components;
using Barotrauma.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Barotrauma.Extensions;
namespace Barotrauma
{
class AIObjectiveChargeBatteries : AIObjectiveLoop<PowerContainer>
{
public override string DebugTag => "charge batteries";
public override bool UnequipItems => true;
public override bool AllowAutomaticItemUnequipping => true;
private IEnumerable<PowerContainer> batteryList;
public AIObjectiveChargeBatteries(Character character, AIObjectiveManager objectiveManager, string option, float priorityModifier)
@@ -26,8 +26,7 @@ namespace Barotrauma
if (item.Submarine.TeamID != character.TeamID) { return false; }
if (character.Submarine != null)
{
if (item.Submarine.Info.Type != character.Submarine.Info.Type) { return false; }
if (!character.Submarine.IsEntityFoundOnThisSub(item, true)) { return false; }
if (!character.Submarine.IsConnectedTo(item.Submarine)) { return false; }
}
if (item.ConditionPercentage <= 0) { return false; }
if (Character.CharacterList.Any(c => c.CurrentHull == item.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return false; }
@@ -37,6 +36,7 @@ namespace Barotrauma
protected override float TargetEvaluation()
{
if (Targets.None()) { return 0; }
if (Option == "charge")
{
return Targets.Max(t => MathHelper.Lerp(100, 0, Math.Abs(PowerContainer.aiRechargeTargetRatio - t.RechargeRatio)));
@@ -4,6 +4,7 @@ using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using FarseerPhysics.Dynamics;
namespace Barotrauma
{
@@ -17,12 +18,11 @@ namespace Barotrauma
private readonly CombatMode initialMode;
private float seekWeaponsTimer;
private readonly float seekWeaponsInterval = 1;
private float checkWeaponsTimer;
private readonly float checkWeaponsInterval = 1;
private float ignoreWeaponTimer;
private readonly float ignoredWeaponsClearTime = 10;
// Won't (by default) start the offensive with weapons that have lower priority than this
private readonly float goodWeaponPriority = 30;
private readonly float arrestHoldFireTime = 8;
@@ -42,7 +42,7 @@ namespace Barotrauma
_weapon = value;
_weaponComponent = null;
hasAimed = false;
RemoveSubObjective(ref seekAmmunition);
RemoveSubObjective(ref seekAmmunitionObjective);
}
}
private ItemComponent _weaponComponent;
@@ -69,13 +69,14 @@ namespace Barotrauma
private readonly HashSet<ItemComponent> weapons = new HashSet<ItemComponent>();
private readonly HashSet<Item> ignoredWeapons = new HashSet<Item>();
private AIObjectiveContainItem seekAmmunition;
private AIObjectiveContainItem seekAmmunitionObjective;
private AIObjectiveGoTo retreatObjective;
private AIObjectiveGoTo followTargetObjective;
private AIObjectiveGetItem seekWeaponObjective;
private Hull retreatTarget;
private float coolDownTimer;
private IEnumerable<FarseerPhysics.Dynamics.Body> myBodies;
private IEnumerable<Body> myBodies;
private float aimTimer;
private bool canSeeTarget;
@@ -99,17 +100,27 @@ namespace Barotrauma
Defensive,
Offensive,
Arrest,
Retreat
Retreat,
None
}
public CombatMode Mode { get; private set; }
private bool IsOffensiveOrArrest => initialMode == CombatMode.Offensive || initialMode == CombatMode.Arrest;
private bool TargetEliminated => Enemy == null || Enemy.Removed || Enemy.IsUnconscious;
private bool IsEnemyDisabled => Enemy == null || Enemy.Removed || Enemy.IsDead;
private bool EnemyIsClose() => Enemy != null && character.CurrentHull == Enemy.CurrentHull || Vector2.DistanceSquared(character.Position, Enemy.Position) < 500;
public AIObjectiveCombat(Character character, Character enemy, CombatMode mode, AIObjectiveManager objectiveManager, float priorityModifier = 1, float coolDown = 10.0f)
: base(character, objectiveManager, priorityModifier)
{
if (mode == CombatMode.None)
{
#if DEBUG
DebugConsole.ThrowError("Combat mode == None");
#endif
return;
}
Enemy = enemy;
coolDownTimer = coolDown;
findSafety = objectiveManager.GetObjective<AIObjectiveFindSafety>();
@@ -145,12 +156,16 @@ namespace Barotrauma
{
base.Update(deltaTime);
ignoreWeaponTimer -= deltaTime;
seekWeaponsTimer -= deltaTime;
checkWeaponsTimer -= deltaTime;
if (ignoreWeaponTimer < 0)
{
ignoredWeapons.Clear();
ignoreWeaponTimer = ignoredWeaponsClearTime;
}
if (findSafety != null)
{
findSafety.Priority = 0;
}
}
protected override bool Check()
@@ -164,8 +179,6 @@ namespace Barotrauma
return IsEnemyDisabled || (!IsOffensiveOrArrest && coolDownTimer <= 0);
}
private bool IsEnemyDisabled => Enemy == null || Enemy.Removed || Enemy.IsDead;
protected override void Act(float deltaTime)
{
if (abortCondition != null && abortCondition())
@@ -178,13 +191,13 @@ namespace Barotrauma
{
coolDownTimer -= deltaTime;
}
if (seekAmmunition == null)
if (seekAmmunitionObjective == null && seekWeaponObjective == null)
{
if (Mode != CombatMode.Retreat && TryArm() && !IsEnemyDisabled)
{
OperateWeapon(deltaTime);
}
if (!HoldPosition)
if (!HoldPosition && seekAmmunitionObjective == null && seekWeaponObjective == null)
{
Move(deltaTime);
}
@@ -193,8 +206,7 @@ namespace Barotrauma
case CombatMode.Offensive:
if (TargetEliminated && objectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>())
{
// TODO: enable
//character.Speak(TextManager.Get("DialogTargetDown"), null, 3.0f, "targetdown", 30.0f);
character.Speak(TextManager.Get("DialogTargetDown"), null, 3.0f, "targetdown", 30.0f);
}
break;
case CombatMode.Arrest:
@@ -233,11 +245,11 @@ namespace Barotrauma
Weapon = null;
return false;
}
if (seekWeaponsTimer < 0)
if (checkWeaponsTimer < 0)
{
seekWeaponsTimer = seekWeaponsInterval;
checkWeaponsTimer = checkWeaponsInterval;
// First go through all weapons and try to reload without seeking ammunition
var allWeapons = GetAllWeapons();
var allWeapons = FindWeaponsFromInventory();
while (allWeapons.Any())
{
Weapon = GetWeapon(allWeapons, out _weaponComponent);
@@ -273,12 +285,12 @@ namespace Barotrauma
if (Weapon == null)
{
// No weapon found with the conditions above. Try again, now let's try to seek ammunition too
Weapon = GetWeapon(out _weaponComponent);
Weapon = FindWeapon(out _weaponComponent);
if (Weapon != null)
{
if (!CheckWeapon(seekAmmo: true))
{
if (seekAmmunition != null)
if (seekAmmunitionObjective != null)
{
// No loaded weapon, but we are trying to seek ammunition.
return false;
@@ -290,9 +302,58 @@ namespace Barotrauma
}
}
}
if (Weapon == null)
bool isAllowedToSeekWeapons = !EnemyIsClose() && character.TeamID != Character.TeamType.FriendlyNPC && IsOffensiveOrArrest;
if (!isAllowedToSeekWeapons)
{
Mode = CombatMode.Retreat;
if (WeaponComponent == null)
{
Mode = CombatMode.Retreat;
}
}
else if (seekAmmunitionObjective == null && (WeaponComponent == null || WeaponComponent.CombatPriority < goodWeaponPriority))
{
// Poor weapon equipped -> try to find better.
RemoveSubObjective(ref seekAmmunitionObjective);
RemoveSubObjective(ref retreatObjective);
RemoveSubObjective(ref followTargetObjective);
TryAddSubObjective(ref seekWeaponObjective,
constructor: () => new AIObjectiveGetItem(character, "weapon", objectiveManager, equip: true, checkInventory: false)
{
GetItemPriority = i =>
{
if (Weapon != null && (i == Weapon || i.Prefab.Identifier == Weapon.Prefab.Identifier)) { return 0; }
if (i.IsOwnedBy(character)) { return 0; }
var mw = i.GetComponent<MeleeWeapon>();
var rw = i.GetComponent<RangedWeapon>();
float priority = 0;
if (mw != null)
{
priority = mw.CombatPriority / 100;
}
else if (rw != null)
{
priority = rw.CombatPriority / 100;
}
if (i.HasTag("stunner"))
{
if (Mode == CombatMode.Arrest)
{
priority *= 2;
}
else
{
priority /= 2;
}
}
return priority;
}
},
onCompleted: () => RemoveSubObjective(ref seekWeaponObjective),
onAbandon: () =>
{
RemoveSubObjective(ref seekWeaponObjective);
Mode = CombatMode.Retreat;
});
}
}
else
@@ -342,32 +403,20 @@ namespace Barotrauma
}
}
private Item GetWeapon(out ItemComponent weaponComponent) => GetWeapon(GetAllWeapons(), out weaponComponent);
private Item FindWeapon(out ItemComponent weaponComponent) => GetWeapon(FindWeaponsFromInventory(), out weaponComponent);
private Item GetWeapon(IEnumerable<ItemComponent> weaponList, out ItemComponent weaponComponent)
{
weaponComponent = null;
float bestPriority = 0;
float lethalDmg = -1;
bool enemyIsClose = EnemyIsClose();
foreach (var weapon in weaponList)
{
// By default, the bots won't go offensive with bad weapons, unless they are close to the enemy or ordered to fight enemies.
// NPC characters ignore this check.
if ((initialMode == CombatMode.Offensive || initialMode == CombatMode.Arrest) && character.TeamID != Character.TeamType.FriendlyNPC)
{
if (!objectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() && !EnemyIsClose())
{
if (weapon.CombatPriority < goodWeaponPriority)
{
continue;
}
}
}
float priority = weapon.CombatPriority;
if (!IsLoaded(weapon))
{
if (weapon is RangedWeapon && EnemyIsClose())
if (weapon is RangedWeapon && enemyIsClose)
{
// Close to the enemy. Ignore weapons that don't have any ammunition (-> Don't seek ammo).
continue;
@@ -420,6 +469,11 @@ namespace Barotrauma
}
}
}
else if (weapon is MeleeWeapon && weapon.Item.HasTag("stunner") && !CanMeleeStunnerStun(weapon))
{
Attack attack = GetAttackDefinition(weapon);
priority = attack?.GetTotalDamage() ?? priority / 2;
}
if (priority > bestPriority)
{
weaponComponent = weapon;
@@ -449,9 +503,7 @@ namespace Barotrauma
}
return weaponComponent.Item;
bool EnemyIsClose() => character.CurrentHull == Enemy.CurrentHull || Vector2.DistanceSquared(character.Position, Enemy.Position) < 500;
Attack GetAttackDefinition(ItemComponent weapon)
static Attack GetAttackDefinition(ItemComponent weapon)
{
Attack attack = null;
if (weapon is MeleeWeapon meleeWeapon)
@@ -465,7 +517,7 @@ namespace Barotrauma
return attack;
}
float GetLethalDamage(ItemComponent weapon)
static float GetLethalDamage(ItemComponent weapon)
{
float lethalDmg = 0;
Attack attack = GetAttackDefinition(weapon);
@@ -499,25 +551,38 @@ namespace Barotrauma
});
return attack.Stun + afflictionsStun + effectsStun;
}
bool CanMeleeStunnerStun(ItemComponent weapon)
{
// 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 containers = weapon.Item.Components.Where(ic => ic is ItemContainer container &&
container.ContainableItems.Any(containable => containable.Identifiers.Any(id => id.Equals(mobileBatteryTag))));
// If there's no such container, assume that the melee weapon can stun without a battery.
return containers.None() || containers.Any(container =>
(container as ItemContainer)?.Inventory.Items.Any(i => i != null && i.HasTag(mobileBatteryTag) && i.Condition > 0.0f) ?? false);
}
}
private HashSet<ItemComponent> GetAllWeapons()
private HashSet<ItemComponent> FindWeaponsFromInventory()
{
weapons.Clear();
foreach (var item in character.Inventory.Items)
{
if (item == null) { continue; }
if (ignoredWeapons.Contains(item)) { continue; }
SeekWeapons(item, weapons);
GetWeapons(item, weapons);
if (item.OwnInventory != null)
{
item.OwnInventory.Items.ForEach(i => SeekWeapons(i, weapons));
item.OwnInventory.Items.ForEach(i => GetWeapons(i, weapons));
}
}
return weapons;
}
private void SeekWeapons(Item item, ICollection<ItemComponent> weaponList)
private void GetWeapons(Item item, ICollection<ItemComponent> weaponList)
{
if (item == null) { return; }
foreach (var component in item.Components)
@@ -571,7 +636,7 @@ namespace Barotrauma
private void Retreat(float deltaTime)
{
RemoveFollowTarget();
RemoveSubObjective(ref seekAmmunition);
RemoveSubObjective(ref seekAmmunitionObjective);
if (retreatObjective != null && retreatObjective.Target != retreatTarget)
{
RemoveSubObjective(ref retreatObjective);
@@ -611,6 +676,12 @@ namespace Barotrauma
private void Engage()
{
if (WeaponComponent == null)
{
RemoveFollowTarget();
SteeringManager.Reset();
return;
}
if (character.LockHands || Enemy == null)
{
Mode = CombatMode.Retreat;
@@ -619,7 +690,8 @@ namespace Barotrauma
}
retreatTarget = null;
RemoveSubObjective(ref retreatObjective);
RemoveSubObjective(ref seekAmmunition);
RemoveSubObjective(ref seekAmmunitionObjective);
RemoveSubObjective(ref seekWeaponObjective);
if (followTargetObjective != null && followTargetObjective.Target != Enemy)
{
RemoveFollowTarget();
@@ -639,7 +711,7 @@ namespace Barotrauma
if (followTargetObjective == null) { return; }
if (Mode == CombatMode.Arrest && Enemy.Stun > 2)
{
if (HumanAIController.HasItem(character, "handlocker", out Item handCuffs))
if (HumanAIController.HasItem(character, "handlocker", out _))
{
if (!arrestingRegistered)
{
@@ -650,16 +722,19 @@ namespace Barotrauma
}
else
{
if (character.TeamID == Character.TeamType.FriendlyNPC)
{
ItemPrefab prefab = ItemPrefab.Find(null, "handcuffs");
if (prefab != null)
{
Entity.Spawner.AddToSpawnQueue(prefab, character.Inventory, onSpawned: (Item i) => i.SpawnedInOutpost = true);
}
}
RemoveFollowTarget();
SteeringManager.Reset();
}
}
else if (WeaponComponent == null)
{
RemoveFollowTarget();
SteeringManager.Reset();
}
else
if (followTargetObjective != null)
{
followTargetObjective.CloseEnough =
WeaponComponent is RangedWeapon ? 1000 :
@@ -682,8 +757,9 @@ namespace Barotrauma
private void OnArrestTargetReached()
{
if (HumanAIController.HasItem(character, "handlocker", out Item handCuffs) && Enemy.Stun > 0 && character.CanInteractWith(Enemy))
if (HumanAIController.HasItem(character, "handlocker", out IEnumerable<Item> matchingItems) && Enemy.Stun > 0 && character.CanInteractWith(Enemy))
{
var handCuffs = matchingItems.First();
if (HumanAIController.TryToMoveItem(handCuffs, Enemy.Inventory))
{
handCuffs.Equip(Enemy);
@@ -704,8 +780,7 @@ namespace Barotrauma
character.Inventory.TryPutItem(item, character, new List<InvSlotType>() { InvSlotType.Any });
}
}
// TODO: enable
//character.Speak(TextManager.Get("DialogTargetArrested"), null, 3.0f, "targetarrested", 30.0f);
character.Speak(TextManager.Get("DialogTargetArrested"), null, 3.0f, "targetarrested", 30.0f);
IsCompleted = true;
}
}
@@ -717,18 +792,19 @@ namespace Barotrauma
{
retreatTarget = null;
RemoveSubObjective(ref retreatObjective);
RemoveSubObjective(ref seekWeaponObjective);
RemoveFollowTarget();
TryAddSubObjective(ref seekAmmunition,
TryAddSubObjective(ref seekAmmunitionObjective,
constructor: () => new AIObjectiveContainItem(character, ammunitionIdentifiers, Weapon.GetComponent<ItemContainer>(), objectiveManager)
{
targetItemCount = Weapon.GetComponent<ItemContainer>().Capacity,
checkInventory = false
},
onCompleted: () => RemoveSubObjective(ref seekAmmunition),
onCompleted: () => RemoveSubObjective(ref seekAmmunitionObjective),
onAbandon: () =>
{
SteeringManager.Reset();
RemoveSubObjective(ref seekAmmunition);
RemoveSubObjective(ref seekAmmunitionObjective);
ignoredWeapons.Add(Weapon);
Weapon = null;
});
@@ -742,7 +818,8 @@ namespace Barotrauma
{
if (WeaponComponent == null) { return false; }
if (!WeaponComponent.requiredItems.ContainsKey(RelatedItem.RelationType.Contained)) { return false; }
var containedItems = Weapon.ContainedItems;
var containedItems = Weapon.OwnInventory?.Items;
if (containedItems == null) { return true; }
// Drop empty ammo
foreach (Item containedItem in containedItems)
{
@@ -757,7 +834,7 @@ namespace Barotrauma
string[] ammunitionIdentifiers = null;
foreach (RelatedItem requiredItem in WeaponComponent.requiredItems[RelatedItem.RelationType.Contained])
{
ammunition = containedItems.FirstOrDefault(it => it.Condition > 0 && requiredItem.MatchesItem(it));
ammunition = containedItems.FirstOrDefault(it => it != null && it.Condition > 0 && requiredItem.MatchesItem(it));
if (ammunition != null)
{
// Ammunition still remaining
@@ -831,36 +908,50 @@ namespace Barotrauma
aimTimer -= deltaTime;
return;
}
if (Mode == CombatMode.Arrest && isLethalWeapon && Enemy.Stun > 0) { return; }
if (Mode == CombatMode.Arrest && isLethalWeapon && Enemy.Stun > 1) { return; }
if (holdFireCondition != null && holdFireCondition()) { return; }
float sqrDist = Vector2.DistanceSquared(character.Position, Enemy.Position);
if (!character.IsFacing(Enemy.WorldPosition))
{
aimTimer = Rand.Range(1f, 1.5f);
return;
}
if (WeaponComponent is MeleeWeapon meleeWeapon)
{
bool closeEnough = true;
float sqrRange = meleeWeapon.Range * meleeWeapon.Range;
if (character.AnimController.InWater)
{
if (sqrDist > sqrRange) { return; }
if (sqrDist > sqrRange)
{
closeEnough = false;
}
}
else
{
// It's possible that the center point of the creature is out of reach, but we could still hit the character.
float xDiff = Math.Abs(Enemy.WorldPosition.X - character.WorldPosition.X);
if (xDiff > meleeWeapon.Range) { return; }
if (xDiff > meleeWeapon.Range)
{
closeEnough = false;
}
float yDiff = Math.Abs(Enemy.WorldPosition.Y - character.WorldPosition.Y);
if (yDiff > Math.Max(meleeWeapon.Range, 100)) { return; }
if (Enemy.WorldPosition.Y < character.WorldPosition.Y && yDiff > 25)
if (yDiff > Math.Max(meleeWeapon.Range, 100))
{
closeEnough = false;
}
if (closeEnough && Enemy.WorldPosition.Y < character.WorldPosition.Y && yDiff > 25)
{
// The target is probably knocked down? -> try to reach it by crouching.
HumanAIController.AnimController.Crouching = true;
}
}
character.SetInput(InputType.Shoot, false, true);
Weapon.Use(deltaTime, character);
if (closeEnough)
{
SteeringManager.Reset();
character.SetInput(InputType.Shoot, false, true);
Weapon.Use(deltaTime, character);
}
else if (!character.IsFacing(Enemy.WorldPosition))
{
// Don't do the facing check if we are close to the target, because it easily causes the character to get stuck here when it flips around.
aimTimer = Rand.Range(1f, 1.5f);
}
}
else
{
@@ -916,6 +1007,19 @@ namespace Barotrauma
}
}
public override void Reset()
{
base.Reset();
hasAimed = false;
isLethalWeapon = false;
canSeeTarget = false;
seekWeaponObjective = null;
seekAmmunitionObjective = null;
retreatObjective = null;
followTargetObjective = null;
retreatTarget = null;
}
//private float CalculateEnemyStrength()
//{
// float enemyStrength = 0;
@@ -1,5 +1,4 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -30,6 +29,7 @@ namespace Barotrauma
private readonly HashSet<Item> containedItems = new HashSet<Item>();
public bool AllowToFindDivingGear { get; set; } = true;
public bool AllowDangerousPressure { get; set; }
public float ConditionLevel { get; set; }
public bool Equip { get; set; }
public bool RemoveEmpty { get; set; } = true;
@@ -53,13 +53,17 @@ namespace Barotrauma
{
itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant();
}
this.container = container;
}
protected override bool Check()
{
if (IsCompleted) { return true; }
if (container == null)
{
Abandon = true;
return false;
}
if (item != null)
{
return container.Inventory.Items.Contains(item);
@@ -143,8 +147,8 @@ namespace Barotrauma
DialogueIdentifier = "dialogcannotreachtarget",
TargetName = container.Item.Name
},
onAbandon: () => Abandon = true,
onCompleted: () => RemoveSubObjective(ref goToObjective));
onAbandon: () => Abandon = true,
onCompleted: () => RemoveSubObjective(ref goToObjective));
}
}
else
@@ -156,7 +160,9 @@ namespace Barotrauma
GetItemPriority = GetItemPriority,
ignoredContainerIdentifiers = ignoredContainerIdentifiers,
ignoredItems = containedItems,
AllowToFindDivingGear = this.AllowToFindDivingGear
AllowToFindDivingGear = AllowToFindDivingGear,
AllowDangerousPressure = AllowDangerousPressure,
TargetCondition = ConditionLevel
}, onAbandon: () =>
{
Abandon = true;
@@ -166,20 +172,17 @@ namespace Barotrauma
{
containedItems.Add(getItemObjective.TargetItem);
}
else
{
if (container.Inventory.FindItem(i => CheckItem(i), recursive: false) != null)
{
IsCompleted = true;
}
else
{
Abandon = true;
}
}
RemoveSubObjective(ref getItemObjective);
});
}
}
}
public override void Reset()
{
base.Reset();
getItemObjective = null;
goToObjective = null;
containedItems.Clear();
}
}
}
@@ -1,5 +1,4 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
@@ -130,5 +129,12 @@ namespace Barotrauma
IsCompleted = true;
}
}
public override void Reset()
{
base.Reset();
goToObjective = null;
containObjective = null;
}
}
}
@@ -60,7 +60,7 @@ namespace Barotrauma
private float sinTime;
protected override void Act(float deltaTime)
{
var extinguisherItem = character.Inventory.FindItemByIdentifier("fireextinguisher") ?? character.Inventory.FindItemByTag("fireextinguisher");
var extinguisherItem = character.Inventory.FindItemByTag("fireextinguisher");
if (extinguisherItem == null || extinguisherItem.Condition <= 0.0f || !character.HasEquippedItem(extinguisherItem))
{
TryAddSubObjective(ref getExtinguisherObjective, () =>
@@ -147,5 +147,14 @@ namespace Barotrauma
}
}
}
public override void Reset()
{
base.Reset();
getExtinguisherObjective = null;
gotoObjective = null;
useExtinquisherTimer = 0;
sinTime = 0;
}
}
}
@@ -1,7 +1,6 @@
using System.Linq;
using System.Collections.Generic;
using Barotrauma.Extensions;
using System;
namespace Barotrauma
{
@@ -14,7 +13,7 @@ namespace Barotrauma
protected override bool Filter(Hull hull) => IsValidTarget(hull, character);
protected override float TargetEvaluation() => objectiveManager.CurrentObjective == this ? 100 : Targets.Sum(t => GetFireSeverity(t));
protected override float TargetEvaluation() => Targets.Sum(t => GetFireSeverity(t));
public static float GetFireSeverity(Hull hull) => hull.FireSources.Sum(fs => fs.Size.X);
@@ -31,11 +30,22 @@ namespace Barotrauma
if (hull == null) { return false; }
if (hull.FireSources.None()) { return false; }
if (hull.Submarine == null) { return false; }
if (hull.Submarine.TeamID != character.TeamID) { return false; }
if (character.Submarine != null)
if (character.Submarine == null) { return false; }
if (!character.Submarine.IsConnectedTo(hull.Submarine)) { return false; }
if (character.AIController is HumanAIController humanAI)
{
if (hull.Submarine.Info.Type != character.Submarine.Info.Type) { return false; }
if (!character.Submarine.IsEntityFoundOnThisSub(hull, true)) { return false; }
if (hull.Submarine.TeamID != character.TeamID)
{
if (humanAI.ObjectiveManager.IsCurrentOrder<AIObjectiveExtinguishFires>())
{
// For orders, allow targets in the current sub (for example if the bot is inside an outpost or a wreck)
if (hull.Submarine != character.Submarine) { return false; }
}
else
{
return false;
}
}
}
return true;
}
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -9,7 +10,6 @@ namespace Barotrauma
protected override float IgnoreListClearInterval => 30;
public override bool IgnoreUnsafeHulls => true;
public AIObjectiveFightIntruders(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier) { }
@@ -20,7 +20,7 @@ namespace Barotrauma
protected override float TargetEvaluation()
{
// TODO: sorting criteria
return 100;
return Targets.None() ? 0 : 100;
}
protected override AIObjective ObjectiveConstructor(Character target)
@@ -56,8 +56,7 @@ namespace Barotrauma
if (target.CurrentHull == null) { return false; }
if (character.Submarine != null)
{
if (target.Submarine.Info.Type != character.Submarine.Info.Type) { return false; }
if (!character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, true)) { return false; }
if (!character.Submarine.IsConnectedTo(target.Submarine)) { return false; }
}
return true;
}
@@ -1,5 +1,4 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using Barotrauma.Extensions;
namespace Barotrauma
@@ -9,21 +8,24 @@ namespace Barotrauma
public override string DebugTag => $"find diving gear ({gearTag})";
public override bool ForceRun => true;
public override bool KeepDivingGearOn => true;
public override bool AbandonWhenCannotCompleteSubjectives => false;
private readonly string gearTag;
private readonly string fallbackTag;
private AIObjectiveGetItem getDivingGear;
private AIObjectiveContainItem getOxygen;
private Item targetItem;
public static float lowOxygenThreshold = 10;
public static float MIN_OXYGEN = 10;
public static string HEAVY_DIVING_GEAR = "heavydiving";
public static string LIGHT_DIVING_GEAR = "lightdiving";
public static string OXYGEN_SOURCE = "oxygensource";
protected override bool Check() => HumanAIController.HasItem(character, gearTag, out _, "oxygensource", requireEquipped: true) || HumanAIController.HasItem(character, fallbackTag, out _, "oxygensource", requireEquipped: true);
protected override bool Check() => targetItem != null && character.HasEquippedItem(targetItem);
public AIObjectiveFindDivingGear(Character character, bool needDivingSuit, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier)
public AIObjectiveFindDivingGear(Character character, bool needsDivingSuit, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier)
{
gearTag = needDivingSuit ? "divingsuit" : "divingmask";
fallbackTag = needDivingSuit ? "divingsuit" : "diving";
gearTag = needsDivingSuit ? HEAVY_DIVING_GEAR : LIGHT_DIVING_GEAR;
}
protected override void Act(float deltaTime)
@@ -33,98 +35,95 @@ namespace Barotrauma
Abandon = true;
return;
}
var item = character.Inventory.FindItemByIdentifier(gearTag, true) ?? character.Inventory.FindItemByTag(gearTag, true);
if (item == null && fallbackTag != gearTag)
{
item = character.Inventory.FindItemByTag(fallbackTag, true);
}
if (item == null || !character.HasEquippedItem(item))
targetItem = character.Inventory.FindItemByTag(gearTag, true);
if (targetItem == null || !character.HasEquippedItem(targetItem))
{
TryAddSubObjective(ref getDivingGear, () =>
{
if (item == null)
if (targetItem == null)
{
character.Speak(TextManager.Get("DialogGetDivingGear"), null, 0.0f, "getdivinggear", 30.0f);
}
return new AIObjectiveGetItem(character, gearTag, objectiveManager, equip: true) { AllowToFindDivingGear = false };
return new AIObjectiveGetItem(character, gearTag, objectiveManager, equip: true)
{
AllowToFindDivingGear = false,
AllowDangerousPressure = true
};
},
onAbandon: () => Abandon = true,
onCompleted: () => RemoveSubObjective(ref getDivingGear));
}
else
{
var containedItems = item.ContainedItems;
if (containedItems == null)
if (!DropEmptyTanks(character, targetItem, out Item[] containedItems))
{
#if DEBUG
DebugConsole.ThrowError($"{character.Name}: AIObjectiveFindDivingGear failed - the item \"" + item + "\" has no proper inventory");
DebugConsole.ThrowError($"{character.Name}: AIObjectiveFindDivingGear failed - the item \"" + targetItem + "\" has no proper inventory");
#endif
Abandon = true;
return;
}
// Drop empty tanks
foreach (Item containedItem in containedItems)
if (containedItems.None(it => it != null && it.HasTag(OXYGEN_SOURCE) && it.Condition > MIN_OXYGEN))
{
if (containedItem == null) { continue; }
if (containedItem.Condition <= 0.0f)
// No valid oxygen source loaded.
// Seek oxygen that has min 10% condition left.
TryAddSubObjective(ref getOxygen, () =>
{
containedItem.Drop(character);
}
}
if (containedItems.None(it => it.HasTag("oxygensource") && it.Condition > lowOxygenThreshold))
{
var oxygenTank = character.Inventory.FindItemByTag("oxygensource", true);
if (oxygenTank != null)
{
var container = item.GetComponent<ItemContainer>();
if (container.Item.ParentInventory == character.Inventory)
character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f);
return new AIObjectiveContainItem(character, OXYGEN_SOURCE, targetItem.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == Character.TeamType.FriendlyNPC)
{
if (!container.Inventory.CanBePut(oxygenTank))
{
Abandon = true;
}
character.Inventory.RemoveItem(oxygenTank);
if (!container.Inventory.TryPutItem(oxygenTank, null))
{
oxygenTank.Drop(character);
Abandon = true;
}
}
else
{
container.Combine(oxygenTank, character);
}
}
else
AllowToFindDivingGear = false,
AllowDangerousPressure = true,
ConditionLevel = MIN_OXYGEN
};
},
onAbandon: () =>
{
// Seek oxygen that has min 10% condition left
// Try to seek any oxygen sources.
TryAddSubObjective(ref getOxygen, () =>
{
character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f);
return new AIObjectiveContainItem(character, new string[] { "oxygensource" }, item.GetComponent<ItemContainer>(), objectiveManager)
return new AIObjectiveContainItem(character, OXYGEN_SOURCE, targetItem.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == Character.TeamType.FriendlyNPC)
{
AllowToFindDivingGear = false,
ConditionLevel = lowOxygenThreshold
AllowDangerousPressure = true,
ConditionLevel = 0
};
},
onAbandon: () =>
{
// Try to seek any oxygen sources
TryAddSubObjective(ref getOxygen, () =>
{
return new AIObjectiveContainItem(character, new string[] { "oxygensource" }, item.GetComponent<ItemContainer>(), objectiveManager)
{
AllowToFindDivingGear = false,
ConditionLevel = 0
};
},
onAbandon: () => Abandon = true,
onCompleted: () => RemoveSubObjective(ref getOxygen));
},
onAbandon: () => Abandon = true,
onCompleted: () => RemoveSubObjective(ref getOxygen));
}
},
onCompleted: () => RemoveSubObjective(ref getOxygen));
}
}
}
/// <summary>
/// Returns false only when no inventory can be found from the item.
/// </summary>
public static bool DropEmptyTanks(Character actor, Item target, out Item[] containedItems)
{
containedItems = target.OwnInventory?.Items;
if (containedItems == null)
{
return false;
}
foreach (Item containedItem in containedItems)
{
if (containedItem == null) { continue; }
if (containedItem.Condition <= 0.0f)
{
containedItem.Drop(actor);
}
}
return true;
}
public override void Reset()
{
base.Reset();
getDivingGear = null;
getOxygen = null;
targetItem = null;
}
}
}
@@ -14,7 +14,8 @@ namespace Barotrauma
public override bool IgnoreUnsafeHulls => true;
public override bool ConcurrentObjectives => true;
public override bool AllowOutsideSubmarine => true;
public override bool IsLoop { get => true; set => throw new System.Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace); }
public override bool AbandonWhenCannotCompleteSubjectives => false;
public override bool IsLoop { get => true; set => throw new Exception("Trying to set the value for IsLoop from: " + Environment.StackTrace); }
// TODO: expose?
const float priorityIncrease = 100;
@@ -48,7 +49,7 @@ namespace Barotrauma
}
else
{
if (HumanAIController.NeedsDivingGear(character, character.CurrentHull, out _) && !HumanAIController.HasDivingGear(character))
if (HumanAIController.NeedsDivingGear(character.CurrentHull, out _) && !HumanAIController.HasDivingGear(character))
{
Priority = 100;
}
@@ -64,6 +65,14 @@ namespace Barotrauma
public override void Update(float deltaTime)
{
if (retryTimer > 0)
{
retryTimer -= deltaTime;
if (retryTimer <= 0)
{
retryCounter = 0;
}
}
if (resetPriority)
{
Priority = 0;
@@ -92,39 +101,56 @@ namespace Barotrauma
private Hull currentSafeHull;
private Hull previousSafeHull;
private bool cannotFindSafeHull;
private bool cannotFindDivingGear;
private readonly int findDivingGearAttempts = 2;
private int retryCounter;
private readonly float retryResetTime = 5;
private float retryTimer;
protected override void Act(float deltaTime)
{
var currentHull = character.CurrentHull;
bool dangerousPressure = currentHull == null || currentHull.LethalPressure > 0;
if (!dangerousPressure)
bool dangerousPressure = currentHull == null || currentHull.LethalPressure > 0 && character.PressureProtection <= 0;
if (!character.LockHands && (!dangerousPressure || cannotFindSafeHull))
{
// Don't try to seek diving gear if the pressure is dangerous. Just get out.
bool needsDivingGear = HumanAIController.NeedsDivingGear(character, currentHull, out bool needsDivingSuit);
bool needsDivingGear = HumanAIController.NeedsDivingGear(currentHull, out bool needsDivingSuit);
bool needsEquipment = false;
if (needsDivingSuit)
{
needsEquipment = !HumanAIController.HasDivingSuit(character, AIObjectiveFindDivingGear.lowOxygenThreshold);
needsEquipment = !HumanAIController.HasDivingSuit(character, AIObjectiveFindDivingGear.MIN_OXYGEN);
}
else if (needsDivingGear)
{
needsEquipment = !HumanAIController.HasDivingGear(character, AIObjectiveFindDivingGear.lowOxygenThreshold);
needsEquipment = !HumanAIController.HasDivingGear(character, AIObjectiveFindDivingGear.MIN_OXYGEN);
}
if (needsEquipment && divingGearObjective == null && !character.LockHands)
if (needsEquipment)
{
RemoveSubObjective(ref goToObjective);
TryAddSubObjective(ref divingGearObjective,
if (cannotFindDivingGear && retryCounter < findDivingGearAttempts)
{
retryTimer = retryResetTime;
retryCounter++;
needsDivingSuit = !needsDivingSuit;
RemoveSubObjective(ref divingGearObjective);
}
if (divingGearObjective == null)
{
cannotFindDivingGear = false;
RemoveSubObjective(ref goToObjective);
TryAddSubObjective(ref divingGearObjective,
constructor: () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager),
onAbandon: () =>
{
searchHullTimer = Math.Min(1, searchHullTimer);
// Don't reset the diving gear objective, because it's possible that there is no diving gear -> seek a safe hull and then reset so that we can check again.
},
cannotFindDivingGear = true;
// Don't reset the diving gear objective, because it's possible that there is no diving gear -> seek a safe hull and then reset so that we can check again.
},
onCompleted: () =>
{
resetPriority = true;
searchHullTimer = Math.Min(1, searchHullTimer);
RemoveSubObjective(ref divingGearObjective);
});
}
}
}
if (divingGearObjective == null || !divingGearObjective.CanBeCompleted)
@@ -142,6 +168,7 @@ namespace Barotrauma
searchHullTimer = SearchHullInterval * Rand.Range(0.9f, 1.1f);
previousSafeHull = currentSafeHull;
currentSafeHull = FindBestHull(allowChangingTheSubmarine: character.TeamID != Character.TeamType.FriendlyNPC);
cannotFindSafeHull = currentSafeHull == null || HumanAIController.NeedsDivingGear(currentSafeHull, out _);
if (currentSafeHull == null)
{
currentSafeHull = previousSafeHull;
@@ -153,35 +180,38 @@ namespace Barotrauma
RemoveSubObjective(ref goToObjective);
}
TryAddSubObjective(ref goToObjective,
constructor: () => new AIObjectiveGoTo(currentSafeHull, character, objectiveManager, getDivingGearIfNeeded: true)
constructor: () => new AIObjectiveGoTo(currentSafeHull, character, objectiveManager, getDivingGearIfNeeded: true)
{
AllowGoingOutside = HumanAIController.HasDivingSuit(character, conditionPercentage: 50)
},
onCompleted: () =>
{
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD ||
HumanAIController.NeedsDivingGear(currentHull, out bool needsSuit) && (needsSuit ? HumanAIController.HasDivingSuit(character) : HumanAIController.HasDivingMask(character)))
{
AllowGoingOutside = HumanAIController.HasDivingSuit(character, conditionPercentage: 50)
},
onCompleted: () =>
resetPriority = true;
searchHullTimer = Math.Min(1, searchHullTimer);
}
RemoveSubObjective(ref goToObjective);
if (cannotFindDivingGear)
{
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD ||
HumanAIController.NeedsDivingGear(character, currentHull, out bool needsSuit) && (needsSuit ? HumanAIController.HasDivingSuit(character) : HumanAIController.HasDivingMask(character)))
{
resetPriority = true;
searchHullTimer = Math.Min(1, searchHullTimer);
}
RemoveSubObjective(ref goToObjective);
// If diving gear objective failed, let's reset it here.
RemoveSubObjective(ref divingGearObjective);
},
onAbandon: () =>
}
},
onAbandon: () =>
{
// Don't ignore any hulls if outside, because apparently it happens that we can't find a path, in which case we just want to try again.
// If we ignore the hull, it might be the only airlock in the target sub, which ignores the whole sub.
if (currentHull != null && goToObjective != null)
{
// Don't ignore any hulls if outside, because apparently it happens that we can't find a path, in which case we just want to try again.
// If we ignore the hull, it might be the only airlock in the target sub, which ignores the whole sub.
if (currentHull != null && goToObjective != null)
if (goToObjective.Target is Hull hull)
{
if (goToObjective.Target is Hull hull)
{
HumanAIController.UnreachableHulls.Add(hull);
}
HumanAIController.UnreachableHulls.Add(hull);
}
RemoveSubObjective(ref goToObjective);
});
}
RemoveSubObjective(ref goToObjective);
});
}
else
{
@@ -194,12 +224,14 @@ namespace Barotrauma
//goto objective doesn't exist (a safe hull not found, or a path to a safe hull not found)
// -> attempt to manually steer away from hazards
Vector2 escapeVel = Vector2.Zero;
// TODO: optimize
foreach (FireSource fireSource in HumanAIController.VisibleHulls.SelectMany(h => h.FireSources))
foreach (Hull hull in HumanAIController.VisibleHulls)
{
Vector2 dir = character.Position - fireSource.Position;
float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(fireSource.Position, character.Position), 0.1f, 10.0f);
escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
foreach (FireSource fireSource in hull.FireSources)
{
Vector2 dir = character.Position - fireSource.Position;
float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(fireSource.Position, character.Position), 0.1f, 10.0f);
escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
}
}
foreach (Character enemy in Character.CharacterList)
{
@@ -335,5 +367,17 @@ namespace Barotrauma
}
return bestHull;
}
public override void Reset()
{
base.Reset();
goToObjective = null;
divingGearObjective = null;
currentSafeHull = null;
previousSafeHull = null;
retryCounter = 0;
cannotFindDivingGear = false;
cannotFindSafeHull = false;
}
}
}
@@ -20,12 +20,12 @@ namespace Barotrauma
private AIObjectiveGoTo gotoObjective;
private AIObjectiveOperateItem operateObjective;
public bool IgnoreSeverityAndDistance { get; private set; }
public readonly bool isPriority;
public AIObjectiveFixLeak(Gap leak, Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool ignoreSeverityAndDistance = false) : base (character, objectiveManager, priorityModifier)
public AIObjectiveFixLeak(Gap leak, Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool isPriority = false) : base (character, objectiveManager, priorityModifier)
{
Leak = leak;
IgnoreSeverityAndDistance = ignoreSeverityAndDistance;
this.isPriority = isPriority;
}
protected override bool Check() => Leak.Open <= 0 || Leak.Removed;
@@ -41,15 +41,21 @@ namespace Barotrauma
{
Priority = 0;
}
else if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.ObjectiveManager.GetActiveObjective<AIObjectiveFixLeak>()?.Leak == Leak))
{
Priority = 0;
Abandon = true;
}
else
{
float xDist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X);
float yDist = Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y);
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally).
// If the target is close, ignore the distance factor alltogether so that we keep fixing the leaks that are nearby.
float distanceFactor = IgnoreSeverityAndDistance || xDist < 200 && yDist < 100 ? 1 : MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, xDist + yDist * 3.0f));
float severity = IgnoreSeverityAndDistance ? 1 : AIObjectiveFixLeaks.GetLeakSeverity(Leak) / 100;
float max = Math.Min((AIObjectiveManager.OrderPriority - 1), 90);
float distanceFactor = isPriority || xDist < 200 && yDist < 100 ? 1 : MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 3000, xDist + yDist * 3.0f));
float severity = isPriority ? 1 : AIObjectiveFixLeaks.GetLeakSeverity(Leak) / 100;
float reduction = isPriority ? 1 : 2;
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - reduction, 90);
float devotion = CumulatedDevotion / 100;
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
}
@@ -68,7 +74,7 @@ namespace Barotrauma
}
else
{
var containedItems = weldingTool.ContainedItems;
var containedItems = weldingTool.OwnInventory?.Items;
if (containedItems == null)
{
#if DEBUG
@@ -86,7 +92,7 @@ namespace Barotrauma
containedItem.Drop(character);
}
}
if (containedItems.None(i => i.HasTag("weldingfuel") && i.Condition > 0.0f))
if (containedItems.None(i => i != null && i.HasTag("weldingfuel") && i.Condition > 0.0f))
{
TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, "weldingfuel", weldingTool.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == Character.TeamType.FriendlyNPC),
onAbandon: () => Abandon = true,
@@ -130,11 +136,10 @@ namespace Barotrauma
{
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(Leak, character, objectiveManager)
{
// Disabled for now
//AllowGoingOutside = !Leak.IsRoomToRoom && objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>() && HumanAIController.HasDivingSuit(character, conditionPercentage: 50),
CloseEnough = reach,
DialogueIdentifier = Leak.FlowTargetHull != null ? "dialogcannotreachleak" : null,
TargetName = Leak.FlowTargetHull?.DisplayName
TargetName = Leak.FlowTargetHull?.DisplayName,
CheckVisibility = false
},
onAbandon: () =>
{
@@ -153,5 +158,14 @@ namespace Barotrauma
onCompleted: () => RemoveSubObjective(ref gotoObjective));
}
}
public override void Reset()
{
base.Reset();
getWeldingTool = null;
refuelObjective = null;
gotoObjective = null;
operateObjective = null;
}
}
}
@@ -37,11 +37,9 @@ namespace Barotrauma
protected override float TargetEvaluation()
{
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveFixLeaks>() && !c.Character.IsIncapacitated, onlyBots: true);
int totalLeaks = Targets.Count();
if (totalLeaks == 0) { return 0; }
int secondaryLeaks = Targets.Count(l => l.IsRoomToRoom);
int leaks = totalLeaks - secondaryLeaks;
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveFixLeaks>() && !c.Character.IsIncapacitated, onlyBots: true);
bool anyFixers = otherFixers > 0;
if (objectiveManager.CurrentOrder == this)
{
@@ -50,6 +48,8 @@ namespace Barotrauma
}
else
{
int secondaryLeaks = Targets.Count(l => l.IsRoomToRoom);
int leaks = totalLeaks - secondaryLeaks;
float ratio = leaks == 0 ? 1 : anyFixers ? leaks / otherFixers : 1;
if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / (float)HumanAIController.CountCrew(onlyBots: true) > 0.75f))
{
@@ -62,7 +62,7 @@ namespace Barotrauma
protected override IEnumerable<Gap> GetList() => Gap.GapList;
protected override AIObjective ObjectiveConstructor(Gap gap)
=> new AIObjectiveFixLeak(gap, character, objectiveManager, priorityModifier: PriorityModifier, ignoreSeverityAndDistance: gap.FlowTargetHull == PrioritizedHull);
=> new AIObjectiveFixLeak(gap, character, objectiveManager, priorityModifier: PriorityModifier, isPriority: gap.FlowTargetHull == PrioritizedHull);
protected override void OnObjectiveCompleted(AIObjective objective, Gap target)
=> HumanAIController.RemoveTargets<AIObjectiveFixLeaks, Gap>(character, target);
@@ -75,8 +75,7 @@ namespace Barotrauma
if (gap.Submarine.TeamID != character.TeamID) { return false; }
if (character.Submarine != null)
{
if (gap.Submarine.Info.Type != character.Submarine.Info.Type) { return false; }
if (!character.Submarine.IsEntityFoundOnThisSub(gap, true)) { return false; }
if (!character.Submarine.IsConnectedTo(gap.Submarine)) { return false; }
}
return true;
}
@@ -3,7 +3,6 @@ using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -17,10 +16,9 @@ namespace Barotrauma
public Func<Item, float> GetItemPriority;
public Func<Item, bool> ItemFilter;
public float TargetCondition { get; set; } = 1;
public bool AllowDangerousPressure { get; set; }
//can be either tags or identifiers
private string[] itemIdentifiers;
public IEnumerable<string> Identifiers => itemIdentifiers;
private string[] identifiersOrTags;
//if the item can't be found, spawn it in the character's inventory (used by outpost NPCs)
private bool spawnItemIfNotFound = false;
@@ -50,26 +48,26 @@ namespace Barotrauma
moveToTarget = targetItem?.GetRootInventoryOwner();
}
public AIObjectiveGetItem(Character character, string itemIdentifier, AIObjectiveManager objectiveManager, bool equip = true, bool checkInventory = true, float priorityModifier = 1, bool spawnItemIfNotFound = false)
: this(character, new string[] { itemIdentifier }, objectiveManager, equip, checkInventory, priorityModifier, spawnItemIfNotFound) { }
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[] itemIdentifiers, AIObjectiveManager objectiveManager, bool equip = true, bool checkInventory = true, float priorityModifier = 1, bool spawnItemIfNotFound = false)
public AIObjectiveGetItem(Character character, string[] identifiersOrTags, AIObjectiveManager objectiveManager, bool equip = true, bool checkInventory = true, float priorityModifier = 1, bool spawnItemIfNotFound = false)
: base(character, objectiveManager, priorityModifier)
{
currSearchIndex = -1;
this.equip = equip;
this.itemIdentifiers = itemIdentifiers;
this.identifiersOrTags = identifiersOrTags;
this.spawnItemIfNotFound = spawnItemIfNotFound;
for (int i = 0; i < itemIdentifiers.Length; i++)
for (int i = 0; i < identifiersOrTags.Length; i++)
{
itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant();
identifiersOrTags[i] = identifiersOrTags[i].ToLowerInvariant();
}
this.checkInventory = checkInventory;
}
private bool CheckInventory()
{
if (itemIdentifiers == null) { return false; }
if (identifiersOrTags == null) { return false; }
var item = character.Inventory.FindItem(i => CheckItem(i), recursive: true);
if (item != null)
{
@@ -86,7 +84,7 @@ namespace Barotrauma
Abandon = true;
return;
}
if (itemIdentifiers != null && !isDoneSeeking)
if (identifiersOrTags != null && !isDoneSeeking)
{
if (checkInventory)
{
@@ -97,14 +95,18 @@ namespace Barotrauma
}
if (!isDoneSeeking)
{
bool dangerousPressure = character.CurrentHull == null || character.CurrentHull.LethalPressure > 0;
if (dangerousPressure)
if (!AllowDangerousPressure)
{
bool dangerousPressure = character.CurrentHull == null || character.CurrentHull.LethalPressure > 0 && character.PressureProtection <= 0;
if (dangerousPressure)
{
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Seeking item aborted, because the pressure is dangerous.", Color.Yellow);
string itemName = targetItem != null ? targetItem.Name : identifiersOrTags.FirstOrDefault();
DebugConsole.NewMessage($"{character.Name}: Seeking item ({itemName}) aborted, because the pressure is dangerous.", Color.Yellow);
#endif
Abandon = true;
return;
Abandon = true;
return;
}
}
FindTargetItem();
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
@@ -174,34 +176,20 @@ namespace Barotrauma
return;
}
if (equip)
if (HumanAIController.TryToMoveItem(targetItem, character.Inventory))
{
if (HumanAIController.TryToMoveItem(targetItem, character.Inventory))
if (equip)
{
targetItem.Equip(character);
IsCompleted = true;
}
else
{
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Failed to equip/move the item '{targetItem.Name}' into the character inventory. Aborting.", Color.Red);
#endif
Abandon = true;
}
IsCompleted = true;
}
else
{
if (character.Inventory.TryPutItem(targetItem, character, new List<InvSlotType>() { InvSlotType.Any }))
{
IsCompleted = true;
}
else
{
Abandon = true;
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Failed to equip/move the item '{targetItem.Name}' into the character inventory. Aborting.", Color.Red);
DebugConsole.NewMessage($"{character.Name}: Failed to equip/move the item '{targetItem.Name}' into the character inventory. Aborting.", Color.Red);
#endif
}
Abandon = true;
}
}
else if (moveToTarget != null)
@@ -228,7 +216,7 @@ namespace Barotrauma
private void FindTargetItem()
{
if (itemIdentifiers == null)
if (identifiersOrTags == null)
{
if (targetItem == null)
{
@@ -244,18 +232,16 @@ namespace Barotrauma
currSearchIndex++;
var item = Item.ItemList[currSearchIndex];
Submarine itemSub = item.Submarine ?? item.ParentInventory?.Owner?.Submarine;
Submarine mySub = character.Submarine;
if (itemSub == null) { continue; }
if (itemSub.TeamID != character.TeamID) { continue; }
if (mySub == null) { continue; }
if (itemSub.TeamID != mySub.TeamID && itemSub.TeamID != character.TeamID) { continue; }
if (!CheckItem(item)) { continue; }
if (ignoredContainerIdentifiers != null && item.Container != null)
{
if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; }
}
if (character.Submarine != null)
{
if (itemSub.Info.Type != character.Submarine.Info.Type) { continue; }
if (character.Submarine.GetConnectedSubs().None(s => s == itemSub && itemSub.TeamID == character.TeamID && itemSub.Info.Type == character.Submarine.Info.Type)) { continue; }
}
if (!mySub.IsConnectedTo(itemSub)) { continue; }
if (character.IsItemTakenBySomeoneElse(item)) { continue; }
float itemPriority = 1;
if (GetItemPriority != null)
@@ -283,10 +269,10 @@ namespace Barotrauma
{
if (spawnItemIfNotFound)
{
if (!(MapEntityPrefab.List.FirstOrDefault(me => me is ItemPrefab ip && itemIdentifiers.Any(id => id == ip.Identifier || ip.Tags.Contains(id))) is ItemPrefab prefab))
if (!(MapEntityPrefab.List.FirstOrDefault(me => me is ItemPrefab ip && identifiersOrTags.Any(id => id == ip.Identifier || ip.Tags.Contains(id))) is ItemPrefab prefab))
{
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s): {string.Join(", ", itemIdentifiers)}, tried to spawn the item but no matching item prefabs were found.", Color.Yellow);
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s): {string.Join(", ", identifiersOrTags)}, tried to spawn the item but no matching item prefabs were found.", Color.Yellow);
#endif
Abandon = true;
}
@@ -305,7 +291,7 @@ namespace Barotrauma
else
{
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s): {string.Join(", ", itemIdentifiers)}", Color.Yellow);
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s): {string.Join(", ", identifiersOrTags)}", Color.Yellow);
#endif
Abandon = true;
}
@@ -320,7 +306,7 @@ namespace Barotrauma
{
return character.HasItem(targetItem, equip);
}
else if (itemIdentifiers != null)
else if (identifiersOrTags != null)
{
var matchingItem = character.Inventory.FindItem(i => CheckItem(i), recursive: true);
if (matchingItem != null)
@@ -338,13 +324,13 @@ namespace Barotrauma
if (ignoredItems.Contains(item)) { return false; };
if (item.Condition < TargetCondition) { return false; }
if (ItemFilter != null && !ItemFilter(item)) { return false; }
return itemIdentifiers.Any(id => id == item.Prefab.Identifier || item.HasTag(id));
return identifiersOrTags.Any(id => id == item.Prefab.Identifier || item.HasTag(id));
}
public override void Reset()
{
base.Reset();
RemoveSubObjective(ref goToObjective);
goToObjective = null;
targetItem = originalTarget;
moveToTarget = targetItem?.GetRootInventoryOwner();
isDoneSeeking = false;
@@ -1,7 +1,6 @@
using Microsoft.Xna.Framework;
using System;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -31,7 +30,7 @@ namespace Barotrauma
public bool mimic;
private float _closeEnough = 50;
private readonly float minDistance = 25;
private readonly float minDistance = 50;
/// <summary>
/// Display units
/// </summary>
@@ -43,6 +42,8 @@ namespace Barotrauma
_closeEnough = Math.Max(minDistance, value);
}
}
public bool CheckVisibility { get; set; }
public bool IgnoreIfTargetDead { get; set; }
public bool AllowGoingOutside { get; set; }
@@ -103,7 +104,7 @@ namespace Barotrauma
else if (Target is Character)
{
//if closeEnough value is given, allow setting CloseEnough as low as 50, otherwise above AIObjectiveGetItem.DefaultReach
CloseEnough = Math.Max(closeEnough, MathUtils.NearlyEqual(closeEnough, 0.0f) ? AIObjectiveGetItem.DefaultReach : 50);
CloseEnough = Math.Max(closeEnough, MathUtils.NearlyEqual(closeEnough, 0.0f) ? AIObjectiveGetItem.DefaultReach : minDistance);
}
else
{
@@ -138,7 +139,7 @@ namespace Barotrauma
}
Target = Character.Controlled;
}
if (Target == character)
if (Target == character || character.SelectedBy != null && HumanAIController.IsFriendly(character.SelectedBy))
{
// Wait
character.AIController.SteeringManager.Reset();
@@ -207,7 +208,7 @@ namespace Barotrauma
{
Character followTarget = Target as Character;
bool needsDivingSuit = targetIsOutside;
bool needsDivingGear = needsDivingSuit || HumanAIController.NeedsDivingGear(character, targetHull, out needsDivingSuit);
bool needsDivingGear = needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit);
if (!needsDivingGear && mimic)
{
if (HumanAIController.HasDivingSuit(followTarget))
@@ -223,17 +224,25 @@ namespace Barotrauma
bool needsEquipment = false;
if (needsDivingSuit)
{
needsEquipment = !HumanAIController.HasDivingSuit(character, AIObjectiveFindDivingGear.lowOxygenThreshold);
needsEquipment = !HumanAIController.HasDivingSuit(character, AIObjectiveFindDivingGear.MIN_OXYGEN);
}
else if (needsDivingGear)
{
needsEquipment = !HumanAIController.HasDivingGear(character, AIObjectiveFindDivingGear.lowOxygenThreshold);
needsEquipment = !HumanAIController.HasDivingGear(character, AIObjectiveFindDivingGear.MIN_OXYGEN);
}
if (needsEquipment)
{
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager),
onAbandon: () => Abandon = true,
onCompleted: () => RemoveSubObjective(ref findDivingGear));
if (findDivingGear != null && !findDivingGear.CanBeCompleted)
{
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: false, objectiveManager),
onAbandon: () => Abandon = true,
onCompleted: () => RemoveSubObjective(ref findDivingGear));
}
else
{
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager),
onCompleted: () => RemoveSubObjective(ref findDivingGear));
}
return;
}
}
@@ -262,11 +271,14 @@ namespace Barotrauma
{
if (n.Waypoint.isObstructed) { return false; }
return (n.Waypoint.CurrentHull == null) == (character.CurrentHull == null);
}, endNodeFilter, nodeFilter);
}, endNodeFilter, nodeFilter, CheckVisibility);
if (!isInside && PathSteering.CurrentPath == null || PathSteering.IsPathDirty || PathSteering.CurrentPath.Unreachable)
{
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(Target.WorldPosition - character.WorldPosition));
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 15);
if (character.AnimController.InWater)
{
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 15);
}
}
}
else
@@ -392,5 +404,11 @@ namespace Barotrauma
HumanAIController.FaceTarget(Target);
base.OnCompleted();
}
public override void Reset()
{
base.Reset();
findDivingGear = null;
}
}
}
@@ -10,7 +10,7 @@ namespace Barotrauma
class AIObjectiveIdle : AIObjective
{
public override string DebugTag => "idle";
public override bool UnequipItems => true;
public override bool AllowAutomaticItemUnequipping => true;
public override bool AllowOutsideSubmarine => true;
private BehaviorType behavior;
@@ -69,6 +69,8 @@ namespace Barotrauma
const float chairCheckInterval = 5.0f;
private float chairCheckTimer;
private float autonomousObjectiveRetryTimer = 10;
private readonly List<Hull> targetHulls = new List<Hull>(20);
private readonly List<float> hullWeights = new List<float>(20);
@@ -88,11 +90,6 @@ namespace Barotrauma
public readonly HashSet<string> PreferredOutpostModuleTypes = new HashSet<string>();
private bool IsInWrongSub() =>
character.Submarine == null ||
currentTarget != null && currentTarget.Submarine != character.Submarine ||
character.TeamID == Character.TeamType.FriendlyNPC && character.Submarine.TeamID != character.TeamID;
public void CalculatePriority(float max = 0)
{
//Random = Rand.Range(0.5f, 1.5f);
@@ -149,6 +146,18 @@ namespace Barotrauma
{
if (PathSteering == null) { return; }
if (objectiveManager.FailedAutonomousObjectives)
{
if (autonomousObjectiveRetryTimer > 0)
{
autonomousObjectiveRetryTimer -= deltaTime;
}
else
{
objectiveManager.CreateAutonomousObjectives();
}
}
//don't keep dragging others when idling
if (character.SelectedCharacter != null)
{
@@ -160,13 +169,34 @@ namespace Barotrauma
bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) ||
(PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)));
bool IsSteeringFinished() => PathSteering.CurrentPath != null && PathSteering.CurrentPath.Finished;
bool IsSteeringFinished() => PathSteering.CurrentPath != null && (PathSteering.CurrentPath.Finished || PathSteering.CurrentPath.Unreachable);
if (currentTargetIsInvalid || currentTarget == null || IsSteeringFinished() && (IsForbidden(character.CurrentHull) || IsInWrongSub()))
if (currentTarget != null && !currentTargetIsInvalid)
{
//don't reset to zero, otherwise the character will keep calling FindTargetHulls
//almost constantly when there's a small number of potential hulls to move to
SetTargetTimerLow();
if (character.TeamID == Character.TeamType.FriendlyNPC)
{
if (currentTarget.Submarine.TeamID != character.TeamID)
{
currentTargetIsInvalid = true;
}
}
else
{
if (currentTarget.Submarine != character.Submarine)
{
currentTargetIsInvalid = true;
}
}
}
if (currentTargetIsInvalid || currentTarget == null || IsForbidden(character.CurrentHull) && IsSteeringFinished())
{
if (newTargetTimer > timerMargin)
{
//don't reset to zero, otherwise the character will keep calling FindTargetHulls
//almost constantly when there's a small number of potential hulls to move to
SetTargetTimerLow();
}
}
else if (character.IsClimbing)
{
@@ -200,7 +230,8 @@ namespace Barotrauma
{
//choose a random available hull
currentTarget = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced);
bool isCurrentHullAllowed = !IsInWrongSub() && !IsForbidden(character.CurrentHull);
bool isInWrongSub = character.TeamID == Character.TeamType.FriendlyNPC && character.Submarine.TeamID != character.TeamID;
bool isCurrentHullAllowed = !isInWrongSub && !IsForbidden(character.CurrentHull);
var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsgStr: $"AIObjectiveIdle {character.DisplayName}", nodeFilter: node =>
{
if (node.Waypoint.CurrentHull == null) { return false; }
@@ -285,12 +316,12 @@ namespace Barotrauma
if (standStillTimer > 0.0f)
{
walkDuration = Rand.Range(walkDurationMin, walkDurationMax);
if (character.CurrentHull != null && character.CurrentHull.Rect.Width > 150 && tooCloseCharacter == null)
var currentHull = character.CurrentHull;
if (currentHull != null && currentHull.Rect.Width > IndoorsSteeringManager.smallRoomSize / 2 && tooCloseCharacter == null)
{
foreach (Character c in Character.CharacterList)
{
if (c == character || !c.IsBot || c.CurrentHull != character.CurrentHull || !(c.AIController is HumanAIController humanAI)) { continue; }
if (c == character || !c.IsBot || c.CurrentHull != currentHull || !(c.AIController is HumanAIController humanAI)) { continue; }
if (Vector2.DistanceSquared(c.WorldPosition, character.WorldPosition) > 60.0f * 60.0f) { continue; }
if ((humanAI.ObjectiveManager.CurrentObjective is AIObjectiveIdle idleObjective && idleObjective.standStillTimer > 0.0f) ||
(humanAI.ObjectiveManager.CurrentObjective is AIObjectiveGoTo gotoObjective && gotoObjective.IsCloseEnough))
@@ -303,7 +334,7 @@ namespace Barotrauma
tooCloseCharacter = null;
break;
}
tooCloseCharacter = c;
tooCloseCharacter = c;
}
HumanAIController.FaceTarget(c);
}
@@ -313,9 +344,24 @@ namespace Barotrauma
{
Vector2 diff = character.WorldPosition - tooCloseCharacter.WorldPosition;
if (diff.LengthSquared() < 0.0001f) { diff = Rand.Vector(1.0f); }
if (diff.X > 0 && character.WorldPosition.X > character.CurrentHull.WorldRect.Right - 50) { diff.X = -diff.X; }
if (diff.X < 0 && character.WorldPosition.X < character.CurrentHull.WorldRect.X + 50) { diff.X = -diff.X; }
PathSteering.SteeringManual(deltaTime, Vector2.Normalize(diff));
if (Math.Abs(diff.X) > 0 &&
(character.WorldPosition.X > currentHull.WorldRect.Right - 50 || character.WorldPosition.X < currentHull.WorldRect.Left + 50))
{
// Between a wall and a character -> move away
tooCloseCharacter = null;
PathSteering.Reset();
standStillTimer = 0;
walkDuration = Math.Min(walkDuration, walkDurationMin);
if (Behavior != BehaviorType.StayInHull && (currentHull.Size.X < IndoorsSteeringManager.smallRoomSize || currentHull.Size.X < (IndoorsSteeringManager.smallRoomSize / 2 * Character.CharacterList.Count(c => c.CurrentHull == currentHull))))
{
// Small room -> find another
newTargetTimer = Math.Min(newTargetTimer, 1);
}
}
else
{
PathSteering.SteeringManual(deltaTime, Vector2.Normalize(diff));
}
return;
}
else
@@ -329,14 +375,13 @@ namespace Barotrauma
{
foreach (Item item in Item.ItemList)
{
if (item.CurrentHull != character.CurrentHull || !item.HasTag("chair")) { continue; }
if (item.CurrentHull != currentHull || !item.HasTag("chair")) { continue; }
var controller = item.GetComponent<Controller>();
if (controller == null || controller.User != null) { continue; }
item.TryInteract(character, forceSelectKey: true);
}
chairCheckTimer = chairCheckInterval;
}
return;
}
if (standStillTimer < -walkDuration)
@@ -376,7 +421,9 @@ namespace Barotrauma
}
if (IsForbidden(hull)) { continue; }
// Check that the hull is linked
if (!character.Submarine.GetConnectedSubs().Contains(hull.Submarine)) { continue; }
if (!character.Submarine.IsConnectedTo(hull.Submarine)) { continue; }
// Ignore very narrow hulls.
if (hull.RectWidth < 200) { continue; }
// Ignore hulls that are too low to stand inside.
if (character.AnimController is HumanoidAnimController animController)
{
@@ -388,13 +435,14 @@ namespace Barotrauma
if (!targetHulls.Contains(hull))
{
targetHulls.Add(hull);
float weight = hull.Volume;
float weight = hull.RectWidth;
// Prefer rooms that are closer. Avoid rooms that are not in the same level.
float yDist = Math.Abs(character.WorldPosition.Y - hull.WorldPosition.Y);
yDist = yDist > 100 ? yDist * 5 : 0;
float dist = Math.Abs(character.WorldPosition.X - hull.WorldPosition.X) + yDist;
float distanceFactor = MathHelper.Lerp(1, 0, MathUtils.InverseLerp(0, 2500, dist));
weight *= distanceFactor;
float waterFactor = MathHelper.Lerp(1, 0, MathUtils.InverseLerp(0, 100, hull.WaterPercentage * 2));
weight *= distanceFactor * waterFactor;
hullWeights.Add(weight);
}
}
@@ -418,5 +466,16 @@ namespace Barotrauma
if (hullName == null) { return false; }
return hullName.Contains("ballast", StringComparison.OrdinalIgnoreCase) || hullName.Contains("airlock", StringComparison.OrdinalIgnoreCase);
}
public override void Reset()
{
base.Reset();
currentTarget = null;
searchingNewHull = false;
tooCloseCharacter = null;
targetHulls.Clear();
hullWeights.Clear();
autonomousObjectiveRetryTimer = 10;
}
}
}
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
@@ -89,10 +88,6 @@ namespace Barotrauma
{
syncTimer -= deltaTime;
}
if (Objectives.None() && Targets.Any(t => !ignoreList.Contains(t)))
{
CreateObjectives();
}
}
// the timer is set between 1 and 10 seconds, depending on the priority modifier and a random +-25%
@@ -113,7 +108,7 @@ namespace Barotrauma
Priority = 0;
return Priority;
}
if (character.LockHands || character.Submarine == null || Targets.None())
if (character.LockHands || character.Submarine == null)
{
Priority = 0;
}
@@ -92,6 +92,7 @@ namespace Barotrauma
}
public Dictionary<AIObjective, CoroutineHandle> DelayedObjectives { get; private set; } = new Dictionary<AIObjective, CoroutineHandle>();
public bool FailedAutonomousObjectives { get; private set; }
private void ClearIgnored()
{
@@ -118,10 +119,11 @@ namespace Barotrauma
}
DelayedObjectives.Clear();
Objectives.Clear();
FailedAutonomousObjectives = false;
AddObjective(new AIObjectiveFindSafety(character, this));
AddObjective(new AIObjectiveIdle(character, this));
int objectiveCount = Objectives.Count;
foreach (var autonomousObjective in character.Info.Job.Prefab.AutonomousObjective)
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}'"); }
@@ -129,6 +131,7 @@ namespace Barotrauma
var order = new Order(orderPrefab, item ?? character.CurrentHull as Entity,
item?.Components.FirstOrDefault(ic => ic.GetType() == orderPrefab.ItemComponentType), orderGiver: character);
if (order == null) { continue; }
if (autonomousObjective.ignoreAtOutpost && Level.IsLoadedOutpost && character.TeamID != Character.TeamType.FriendlyNPC) { continue; }
var objective = CreateObjective(order, autonomousObjective.option, character, isAutonomous: true, autonomousObjective.priorityModifier);
if (objective != null && objective.CanBeCompleted)
{
@@ -220,7 +223,7 @@ namespace Barotrauma
if (objective.IsCompleted)
{
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Removing objective {objective.DebugTag}, because it is completed.", Color.LightGreen);
DebugConsole.NewMessage($"{character.Name}: Removing objective {objective.DebugTag}, because it is completed.", Color.LightBlue);
#endif
Objectives.Remove(objective);
}
@@ -230,6 +233,7 @@ namespace Barotrauma
DebugConsole.NewMessage($"{character.Name}: Removing objective {objective.DebugTag}, because it cannot be completed.", Color.Red);
#endif
Objectives.Remove(objective);
FailedAutonomousObjectives = true;
}
else
{
@@ -286,6 +290,7 @@ namespace Barotrauma
}
else
{
// This should be redundant, because all the objectives are reset when they are selected as active.
CurrentOrder.Reset();
}
}
@@ -8,8 +8,9 @@ namespace Barotrauma
{
class AIObjectiveOperateItem : AIObjective
{
public override string DebugTag => "operate item";
public override bool UnequipItems => true;
public override string DebugTag => $"operate item {component.Name}";
public override bool AllowAutomaticItemUnequipping => true;
public override bool AllowMultipleInstances => true;
private ItemComponent component, controller;
private Entity operateTarget;
@@ -22,6 +23,8 @@ namespace Barotrauma
public override bool CanBeCompleted => base.CanBeCompleted && (!useController || controller != null);
public override bool IsDuplicate<T>(T otherObjective) => base.IsDuplicate(otherObjective) && otherObjective is AIObjectiveOperateItem operateObjective && operateObjective.component == component;
public Entity OperateTarget => operateTarget;
public ItemComponent Component => component;
@@ -32,7 +35,7 @@ namespace Barotrauma
public override float GetPriority()
{
if (!IsAllowed)
if (!IsAllowed || character.LockHands)
{
Priority = 0;
return Priority;
@@ -43,7 +46,8 @@ namespace Barotrauma
}
else
{
if (objectiveManager.CurrentOrder == this)
bool isOrder = objectiveManager.CurrentOrder == this;
if (isOrder)
{
Priority = AIObjectiveManager.OrderPriority;
}
@@ -61,6 +65,16 @@ namespace Barotrauma
var reactor = component?.Item.GetComponent<Reactor>();
if (reactor != null)
{
if (!isOrder)
{
if (reactor.LastUserWasPlayer && character.TeamID != Character.TeamType.FriendlyNPC ||
HumanAIController.IsTrueForAnyCrewMember(c =>
c.ObjectiveManager.CurrentOrder is AIObjectiveOperateItem operateOrder && operateOrder.GetTarget() == target))
{
Priority = 0;
return Priority;
}
}
switch (Option)
{
case "shutdown":
@@ -73,7 +87,7 @@ namespace Barotrauma
case "powerup":
// Check that we don't already have another order that is targeting the same item.
// Without this the autonomous objective will tell the bot to turn the reactor on again.
if (objectiveManager.CurrentOrder is AIObjectiveOperateItem operateOrder && operateOrder != this && operateOrder.GetTarget() == target)
if (objectiveManager.CurrentOrder is AIObjectiveOperateItem operateOrder && operateOrder != this && operateOrder.GetTarget() == target && operateOrder.Option != Option)
{
Priority = 0;
return Priority;
@@ -82,7 +96,7 @@ namespace Barotrauma
}
}
if (targetItem.CurrentHull == null ||
targetItem.Submarine != character.Submarine && objectiveManager.CurrentOrder != this ||
targetItem.Submarine != character.Submarine && !isOrder ||
targetItem.CurrentHull.FireSources.Any() ||
HumanAIController.IsItemOperatedByAnother(target, out _) ||
Character.CharacterList.Any(c => c.CurrentHull == targetItem.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c)))
@@ -92,7 +106,12 @@ namespace Barotrauma
else
{
float value = CumulatedDevotion + (AIObjectiveManager.OrderPriority * PriorityModifier);
float max = objectiveManager.CurrentOrder == this ? MathHelper.Min(AIObjectiveManager.OrderPriority, 90) : AIObjectiveManager.RunPriority - 1;
float max = isOrder ? MathHelper.Min(AIObjectiveManager.OrderPriority, 90) : AIObjectiveManager.RunPriority - 1;
if (!isOrder && reactor != null && reactor.PowerOn && Option == "powerup")
{
// Decrease the priority when targeting a reactor that is already on.
value /= 2;
}
Priority = MathHelper.Clamp(value, 0, max);
}
}
@@ -142,6 +161,15 @@ namespace Barotrauma
// Don't abandon
return;
}
if (operateTarget != null)
{
if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.ObjectiveManager.GetActiveObjective() is AIObjectiveOperateItem operateObjective && operateObjective.operateTarget == operateTarget))
{
// Another crew member is already targeting this entity.
Abandon = true;
return;
}
}
if (target.CanBeSelected)
{
if (character.CanInteractWith(target.Item, out _, checkLinked: false))
@@ -227,5 +255,12 @@ namespace Barotrauma
}
protected override bool Check() => isDoneOperating && !IsLoop;
public override void Reset()
{
base.Reset();
goToObjective = null;
getItemObjective = null;
}
}
}
@@ -3,6 +3,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -10,7 +11,7 @@ namespace Barotrauma
{
public override string DebugTag => "pump water";
public override bool KeepDivingGearOn => true;
public override bool UnequipItems => true;
public override bool AllowAutomaticItemUnequipping => true;
private IEnumerable<Pump> pumpList;
@@ -35,8 +36,7 @@ namespace Barotrauma
if (pump.Item.CurrentHull.FireSources.Count > 0) { return false; }
if (character.Submarine != null)
{
if (pump.Item.Submarine.Info.Type != character.Submarine.Info.Type) { return false; }
if (!character.Submarine.IsEntityFoundOnThisSub(pump.Item, true)) { return false; }
if (!character.Submarine.IsConnectedTo(pump.Item.Submarine)) { return false; }
}
if (Character.CharacterList.Any(c => c.CurrentHull == pump.Item.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return false; }
if (IsReady(pump)) { return false; }
@@ -54,6 +54,7 @@ namespace Barotrauma
protected override float TargetEvaluation()
{
if (Targets.None()) { return 0; }
if (Option == "stoppumping")
{
return Targets.Max(t => MathHelper.Lerp(0, 100, Math.Abs(t.FlowPercentage / 100)));
@@ -9,7 +9,6 @@ namespace Barotrauma
class AIObjectiveRepairItem : AIObjective
{
public override string DebugTag => "repair item";
public override bool KeepDivingGearOn => true;
public Item Item { get; private set; }
@@ -18,9 +17,11 @@ namespace Barotrauma
private float previousCondition = -1;
private RepairTool repairTool;
private bool IsRepairing => character.SelectedConstruction == Item && Item.GetComponent<Repairable>()?.CurrentFixer == character;
private bool IsRepairing() => IsRepairing(character, Item);
private readonly bool isPriority;
public static bool IsRepairing(Character character, Item item) => character.SelectedConstruction == item && item.Repairables.Any(r => r.CurrentFixer == character);
public AIObjectiveRepairItem(Character character, Item item, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool isPriority = false)
: base(character, objectiveManager, priorityModifier)
{
@@ -49,12 +50,15 @@ namespace Barotrauma
float yDist = Math.Abs(character.WorldPosition.Y - Item.WorldPosition.Y);
yDist = yDist > 100 ? yDist * 5 : 0;
float dist = Math.Abs(character.WorldPosition.X - Item.WorldPosition.X) + yDist;
distanceFactor = MathHelper.Lerp(1, 0.25f, MathUtils.InverseLerp(0, 5000, dist));
distanceFactor = MathHelper.Lerp(1, 0.25f, MathUtils.InverseLerp(0, 4000, dist));
}
float severity = isPriority ? 1 : AIObjectiveRepairItems.GetTargetPriority(Item, character, requiredSuccessFactor: objectiveManager.CurrentOrder != this ? AIObjectiveRepairItems.RequiredSuccessFactor : 0);
float isSelected = IsRepairing ? 50 : 0;
float devotion = (CumulatedDevotion + isSelected) / 100;
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
float requiredSuccessFactor = objectiveManager.IsCurrentOrder<AIObjectiveRepairItems>() ? 0 : AIObjectiveRepairItems.RequiredSuccessFactor;
float severity = isPriority ? 1 : AIObjectiveRepairItems.GetTargetPriority(Item, character, requiredSuccessFactor) / 100;
bool isSelected = IsRepairing();
float selectedBonus = isSelected ? 100 - MaxDevotion : 0;
float devotion = (CumulatedDevotion + selectedBonus) / 100;
float reduction = isPriority ? 1 : isSelected ? 2 : 3;
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - reduction, 90);
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
}
return Priority;
@@ -63,7 +67,7 @@ namespace Barotrauma
protected override bool Check()
{
IsCompleted = Item.IsFullCondition;
if (IsCompleted && IsRepairing)
if (IsCompleted && IsRepairing())
{
character?.Speak(TextManager.GetWithVariable("DialogItemRepaired", "[itemname]", Item.Name, true), null, 0.0f, "itemrepaired", 10.0f);
}
@@ -95,7 +99,7 @@ namespace Barotrauma
}
if (repairTool != null)
{
var containedItems = repairTool.Item.ContainedItems;
var containedItems = repairTool.Item.OwnInventory?.Items;
if (containedItems == null)
{
#if DEBUG
@@ -118,13 +122,13 @@ namespace Barotrauma
foreach (RelatedItem requiredItem in repairTool.requiredItems[RelatedItem.RelationType.Contained])
{
item = requiredItem;
fuel = containedItems.FirstOrDefault(it => it.Condition > 0.0f && requiredItem.MatchesItem(it));
fuel = containedItems.FirstOrDefault(it => it != null && it.Condition > 0.0f && requiredItem.MatchesItem(it));
if (fuel != null) { break; }
}
if (fuel == null)
{
RemoveSubObjective(ref goToObjective);
TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, item.Identifiers, repairTool.Item.GetComponent<ItemContainer>(), objectiveManager),
TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, item.Identifiers, repairTool.Item.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == Character.TeamType.FriendlyNPC),
onCompleted: () => RemoveSubObjective(ref refuelObjective),
onAbandon: () => Abandon = true);
return;
@@ -142,7 +146,7 @@ namespace Barotrauma
if (repairable.CurrentFixer != null && repairable.CurrentFixer != character)
{
// Someone else is repairing the target. Abandon the objective if the other is better at this than us.
Abandon = repairable.DegreeOfSuccess(character) < repairable.DegreeOfSuccess(repairable.CurrentFixer);
Abandon = repairable.CurrentFixer.IsPlayer || repairable.DegreeOfSuccess(character) < repairable.DegreeOfSuccess(repairable.CurrentFixer);
}
if (!Abandon)
{
@@ -166,7 +170,7 @@ namespace Barotrauma
}
if (Abandon)
{
if (IsRepairing)
if (IsRepairing())
{
character.Speak(TextManager.GetWithVariable("DialogCannotRepair", "[itemname]", Item.Name, true), null, 0.0f, "cannotrepair", 10.0f);
}
@@ -201,7 +205,7 @@ namespace Barotrauma
onAbandon: () =>
{
Abandon = true;
if (IsRepairing)
if (IsRepairing())
{
character.Speak(TextManager.GetWithVariable("DialogCannotRepair", "[itemname]", Item.Name, true), null, 0.0f, "cannotrepair", 10.0f);
}
@@ -251,5 +255,14 @@ namespace Barotrauma
repairTool.Use(deltaTime, character);
}
}
public override void Reset()
{
base.Reset();
goToObjective = null;
refuelObjective = null;
previousCondition = -1;
repairTool = null;
}
}
}
@@ -90,18 +90,23 @@ namespace Barotrauma
protected override float TargetEvaluation()
{
if (character.SelectedConstruction != null && Targets.Any(t => character.SelectedConstruction == t && t.ConditionPercentage < 100))
var selectedItem = character.SelectedConstruction;
if (selectedItem != null && AIObjectiveRepairItem.IsRepairing(character, selectedItem) && selectedItem.ConditionPercentage < 100)
{
// Don't stop fixing until done
// Don't stop fixing until completely done
return 100;
}
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveRepairItems>() && !c.Character.IsIncapacitated, onlyBots: true);
int items = Targets.Count;
if (items == 0)
{
return 0;
}
bool anyFixers = otherFixers > 0;
float ratio = anyFixers ? items / (float)otherFixers : 1;
if (objectiveManager.CurrentOrder == this)
{
return Targets.Sum(t => 100 - t.ConditionPercentage) * ratio;
return Targets.Sum(t => 100 - t.ConditionPercentage);
}
else
{
@@ -151,8 +156,7 @@ namespace Barotrauma
if (item.Repairables.None()) { return false; }
if (character.Submarine != null)
{
if (item.Submarine.Info.Type != character.Submarine.Info.Type) { return false; }
if (!character.Submarine.IsEntityFoundOnThisSub(item, true)) { return false; }
if (!character.Submarine.IsConnectedTo(item.Submarine)) { return false; }
}
return true;
}
@@ -1,4 +1,5 @@
using Barotrauma.Items.Components;
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -21,10 +22,12 @@ namespace Barotrauma
private readonly Character targetCharacter;
private AIObjectiveGoTo goToObjective;
private AIObjectiveContainItem replaceOxygenObjective;
private AIObjectiveGetItem getItemObjective;
private float treatmentTimer;
private Hull safeHull;
private float findHullTimer;
private bool ignoreOxygen;
private readonly float findHullInterval = 1.0f;
public AIObjectiveRescue(Character character, Character targetCharacter, AIObjectiveManager objectiveManager, float priorityModifier = 1)
@@ -69,65 +72,130 @@ namespace Barotrauma
}
if (targetCharacter != character)
{
// Incapacitated target is not in a safe place -> Move to a safe place first
if (targetCharacter.IsIncapacitated && HumanAIController.GetHullSafety(targetCharacter.CurrentHull, targetCharacter) < HumanAIController.HULL_SAFETY_THRESHOLD)
if (targetCharacter.IsIncapacitated)
{
if (character.SelectedCharacter != targetCharacter)
// Check if the character needs more oxygen
if (!ignoreOxygen && character.SelectedCharacter == targetCharacter || character.CanInteractWith(targetCharacter))
{
if (targetCharacter.CurrentHull.DisplayName != null)
// Replace empty oxygen tank
// First remove empty tanks
if (HumanAIController.HasItem(targetCharacter, AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR, out IEnumerable<Item> suits, requireEquipped: true))
{
character.Speak(TextManager.GetWithVariables("DialogFoundUnconsciousTarget", new string[2] { "[targetname]", "[roomname]" },
new string[2] { targetCharacter.Name, targetCharacter.CurrentHull.DisplayName }, new bool[2] { false, true }),
null, 1.0f, "foundunconscioustarget" + targetCharacter.Name, 60.0f);
}
// Go to the target and select it
if (!character.CanInteractWith(targetCharacter))
{
RemoveSubObjective(ref goToObjective);
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager)
Item suit = suits.FirstOrDefault();
if (suit != null)
{
CloseEnough = CloseEnoughToTreat,
DialogueIdentifier = "dialogcannotreachpatient",
TargetName = targetCharacter.DisplayName
},
AIObjectiveFindDivingGear.DropEmptyTanks(character, suit, out _);
}
}
else if (HumanAIController.HasItem(targetCharacter, AIObjectiveFindDivingGear.LIGHT_DIVING_GEAR, out IEnumerable<Item> masks, requireEquipped: true))
{
Item mask = masks.FirstOrDefault();
if (mask != null)
{
AIObjectiveFindDivingGear.DropEmptyTanks(character, mask, out _);
}
}
bool ShouldRemoveDivingSuit() => targetCharacter.OxygenAvailable < CharacterHealth.InsufficientOxygenThreshold && targetCharacter.CurrentHull?.LethalPressure <= 0;
if (ShouldRemoveDivingSuit())
{
suits.ForEach(suit => suit.Drop(character));
}
else if (suits.Any() && suits.None(s => s.OwnInventory?.Items != null && s.OwnInventory.Items.Any(it => it != null && it.HasTag(AIObjectiveFindDivingGear.OXYGEN_SOURCE) && it.ConditionPercentage > 0)))
{
// The target has a suit equipped with an empty oxygen tank.
// Can't remove the suit, because the target needs it.
// If we happen to have an extra oxygen tank in the inventory, let's swap it.
Item spareOxygenTank = FindOxygenTank(targetCharacter) ?? FindOxygenTank(character);
if (spareOxygenTank != null)
{
Item suit = suits.FirstOrDefault();
if (suit != null)
{
// Insert the new oxygen tank
TryAddSubObjective(ref replaceOxygenObjective, () => new AIObjectiveContainItem(character, spareOxygenTank, suit.GetComponent<ItemContainer>(), objectiveManager),
onCompleted: () => RemoveSubObjective(ref replaceOxygenObjective),
onAbandon: () =>
{
RemoveSubObjective(ref replaceOxygenObjective);
ignoreOxygen = true;
if (ShouldRemoveDivingSuit())
{
suits.ForEach(suit => suit.Drop(character));
}
});
return;
}
}
Item FindOxygenTank(Character c) =>
c.Inventory.FindItem(i =>
i.HasTag(AIObjectiveFindDivingGear.OXYGEN_SOURCE) &&
i.ConditionPercentage > 1 &&
i.FindParentInventory(inv => inv.Owner is Item otherItem && otherItem.HasTag("diving")) == null,
recursive: true);
}
}
if (HumanAIController.GetHullSafety(targetCharacter.CurrentHull, targetCharacter) < HumanAIController.HULL_SAFETY_THRESHOLD)
{
// Incapacitated target is not in a safe place -> Move to a safe place first
if (character.SelectedCharacter != targetCharacter)
{
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);
}
// Go to the target and select it
if (!character.CanInteractWith(targetCharacter))
{
RemoveSubObjective(ref replaceOxygenObjective);
RemoveSubObjective(ref goToObjective);
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager)
{
CloseEnough = CloseEnoughToTreat,
DialogueIdentifier = "dialogcannotreachpatient",
TargetName = targetCharacter.DisplayName
},
onCompleted: () => RemoveSubObjective(ref goToObjective),
onAbandon: () =>
{
RemoveSubObjective(ref goToObjective);
Abandon = true;
});
}
else
{
character.SelectCharacter(targetCharacter);
}
}
else
{
// Drag the character into safety
if (safeHull == null)
{
if (findHullTimer > 0)
{
findHullTimer -= deltaTime;
}
else
{
safeHull = objectiveManager.GetObjective<AIObjectiveFindSafety>().FindBestHull(HumanAIController.VisibleHulls);
findHullTimer = findHullInterval * Rand.Range(0.9f, 1.1f);
character.SelectCharacter(targetCharacter);
}
}
if (safeHull != null && character.CurrentHull != safeHull)
else
{
RemoveSubObjective(ref goToObjective);
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(safeHull, character, objectiveManager),
onCompleted: () => RemoveSubObjective(ref goToObjective),
onAbandon: () =>
// Drag the character into safety
if (safeHull == null)
{
if (findHullTimer > 0)
{
RemoveSubObjective(ref goToObjective);
safeHull = character.CurrentHull;
});
findHullTimer -= deltaTime;
}
else
{
safeHull = objectiveManager.GetObjective<AIObjectiveFindSafety>().FindBestHull(HumanAIController.VisibleHulls);
findHullTimer = findHullInterval * Rand.Range(0.9f, 1.1f);
}
}
if (safeHull != null && character.CurrentHull != safeHull)
{
RemoveSubObjective(ref replaceOxygenObjective);
RemoveSubObjective(ref goToObjective);
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(safeHull, character, objectiveManager),
onCompleted: () => RemoveSubObjective(ref goToObjective),
onAbandon: () =>
{
RemoveSubObjective(ref goToObjective);
safeHull = character.CurrentHull;
});
}
}
}
}
@@ -137,6 +205,7 @@ namespace Barotrauma
if (targetCharacter != character && !character.CanInteractWith(targetCharacter))
{
RemoveSubObjective(ref replaceOxygenObjective);
RemoveSubObjective(ref goToObjective);
// Go to the target and select it
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager)
@@ -325,7 +394,7 @@ namespace Barotrauma
Priority = 0;
return Priority;
}
if (targetCharacter == null || targetCharacter.CurrentHull == null || targetCharacter.Removed || targetCharacter.IsDead)
if (character.LockHands || targetCharacter == null || targetCharacter.CurrentHull == null || targetCharacter.Removed || targetCharacter.IsDead)
{
Priority = 0;
}
@@ -346,5 +415,15 @@ namespace Barotrauma
}
public static IEnumerable<Affliction> GetSortedAfflictions(Character character) => CharacterHealth.SortAfflictionsBySeverity(character.CharacterHealth.GetAllAfflictions());
public override void Reset()
{
base.Reset();
goToObjective = null;
getItemObjective = null;
replaceOxygenObjective = null;
safeHull = null;
ignoreOxygen = false;
}
}
}
@@ -1,4 +1,5 @@
using System;
using Barotrauma.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -34,6 +35,7 @@ namespace Barotrauma
protected override float TargetEvaluation()
{
if (Targets.None()) { return 100; }
if (objectiveManager.CurrentOrder != this)
{
if (!character.IsMedic && HumanAIController.IsTrueForAnyCrewMember(c => c != HumanAIController && c.Character.IsMedic && !c.Character.IsUnconscious))
@@ -72,7 +74,7 @@ namespace Barotrauma
public static bool IsValidTarget(Character target, Character character)
{
if (target == null || target.IsDead || target.Removed) { return false; }
if (target.TurnedHostileByEvent) { return false; }
if (target.IsInstigator) { return false; }
if (!HumanAIController.IsFriendly(character, target, onlySameTeam: true)) { return false; }
if (character.AIController is HumanAIController humanAI)
{
@@ -83,7 +85,7 @@ namespace Barotrauma
{
// Don't allow to treat others autonomously
return false;
}
}
// Ignore unsafe hulls, unless ordered
if (humanAI.UnsafeHulls.Contains(target.CurrentHull))
{
@@ -100,10 +102,11 @@ namespace Barotrauma
if (!character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, includingConnectedSubs: true)) { return false; }
if (target != character &&!target.IsPlayer && HumanAIController.IsActive(target) && target.AIController is HumanAIController targetAI)
{
// Ignore all concious targets that are currently fighting, fleeing or treating characters
// Ignore all concious targets that are currently fighting, fleeing, fixing, or treating characters
if (targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveCombat>() ||
targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveFindSafety>() ||
targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveRescue>())
targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveRescue>() ||
targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveFixLeak>())
{
return false;
}