(e0d673b0d) Test: Don't calculate the distance to the buttons when seeking path. Instead, check the distance when checking the doors. Allows the bots to access the Remora ballast through the door where the button is a bit farther from the door than usually.
This commit is contained in:
@@ -12,21 +12,6 @@ namespace Barotrauma
|
||||
|
||||
public abstract string DebugTag { get; }
|
||||
public virtual bool ForceRun => false;
|
||||
public virtual bool KeepDivingGearOn => false;
|
||||
|
||||
protected readonly List<AIObjective> subObjectives = new List<AIObjective>();
|
||||
public float Priority { get; set; }
|
||||
protected readonly Character character;
|
||||
protected string option;
|
||||
protected bool abandon;
|
||||
|
||||
public virtual bool CanBeCompleted => !abandon && subObjectives.All(so => so.CanBeCompleted);
|
||||
public IEnumerable<AIObjective> SubObjectives => subObjectives;
|
||||
public AIObjective CurrentSubObjective { get; private set; }
|
||||
|
||||
protected HumanAIController HumanAIController => character.AIController as HumanAIController;
|
||||
protected IndoorsSteeringManager PathSteering => HumanAIController.PathSteering;
|
||||
protected SteeringManager SteeringManager => HumanAIController.SteeringManager;
|
||||
|
||||
/// <summary>
|
||||
/// Run the main objective with all subobjectives concurrently?
|
||||
@@ -62,6 +47,7 @@ namespace Barotrauma
|
||||
|
||||
public AIObjective(Character character, AIObjectiveManager objectiveManager, float priorityModifier, string option = null)
|
||||
{
|
||||
this.objectiveManager = objectiveManager;
|
||||
this.character = character;
|
||||
Option = option ?? string.Empty;
|
||||
|
||||
@@ -94,18 +80,6 @@ namespace Barotrauma
|
||||
#endif
|
||||
subObjectives.Remove(subObjective);
|
||||
}
|
||||
else if (subObjective.ShouldInterruptSubObjective(subObjective))
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"Removing subobjective {subObjective.DebugTag} of {DebugTag}, because it is interrupted.");
|
||||
#endif
|
||||
subObjectives.Remove(subObjective);
|
||||
}
|
||||
}
|
||||
|
||||
if (!subObjectives.Contains(CurrentSubObjective))
|
||||
{
|
||||
CurrentSubObjective = null;
|
||||
}
|
||||
|
||||
foreach (AIObjective objective in subObjectives)
|
||||
@@ -132,29 +106,40 @@ namespace Barotrauma
|
||||
subObjectives.Add(objective);
|
||||
}
|
||||
|
||||
public void SortSubObjectives(AIObjectiveManager objectiveManager)
|
||||
public void SortSubObjectives()
|
||||
{
|
||||
if (subObjectives.None()) { return; }
|
||||
subObjectives.Sort((x, y) => y.GetPriority(objectiveManager).CompareTo(x.GetPriority(objectiveManager)));
|
||||
CurrentSubObjective = SubObjectives.First();
|
||||
CurrentSubObjective.SortSubObjectives(objectiveManager);
|
||||
subObjectives.Sort((x, y) => y.GetPriority().CompareTo(x.GetPriority()));
|
||||
if (ConcurrentObjectives)
|
||||
{
|
||||
subObjectives.ForEach(so => so.SortSubObjectives());
|
||||
}
|
||||
else
|
||||
{
|
||||
subObjectives.First().SortSubObjectives();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual float GetPriority(AIObjectiveManager objectiveManager) => Priority;
|
||||
public virtual float GetPriority() => Priority * PriorityModifier;
|
||||
|
||||
public virtual void Update(AIObjectiveManager objectiveManager, float deltaTime)
|
||||
public virtual void Update(float deltaTime)
|
||||
{
|
||||
var subObjective = objectiveManager.CurrentObjective?.CurrentSubObjective;
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
Priority = AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
else if (objectiveManager.CurrentObjective == this || subObjective == this)
|
||||
else if (objectiveManager.WaitTimer <= 0)
|
||||
{
|
||||
Priority += Devotion * deltaTime;
|
||||
if (objectiveManager.CurrentObjective != null)
|
||||
{
|
||||
if (objectiveManager.CurrentObjective == this || objectiveManager.CurrentObjective.subObjectives.Any(so => so == this))
|
||||
{
|
||||
Priority += Devotion * PriorityModifier * deltaTime;
|
||||
}
|
||||
}
|
||||
Priority = MathHelper.Clamp(Priority, 0, 100);
|
||||
subObjectives.ForEach(so => so.Update(deltaTime));
|
||||
}
|
||||
Priority = MathHelper.Clamp(Priority, 0, 100);
|
||||
subObjectives.ForEach(so => so.Update(objectiveManager, deltaTime));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -174,13 +159,54 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual bool ShouldInterruptSubObjective(AIObjective subObjective) => false;
|
||||
public virtual void OnSelected() { }
|
||||
/// <summary>
|
||||
/// Checks if the objective already is created and added in subobjectives. If not, creates it.
|
||||
/// Handles objectives that cannot be completed. If the objective has been removed form the subobjectives, a null value is assigned to the reference.
|
||||
/// Returns true if the objective was created.
|
||||
/// </summary>
|
||||
protected bool TryAddSubObjective<T>(ref T objective, Func<T> constructor, Action onAbandon = null) where T : AIObjective
|
||||
{
|
||||
if (objective != null)
|
||||
{
|
||||
// Sub objective already found, no need to do anything if it remains in the subobjectives
|
||||
// If the sub objective is removed -> it's either completed or impossible to complete.
|
||||
if (!subObjectives.Contains(objective))
|
||||
{
|
||||
if (!objective.CanBeCompleted)
|
||||
{
|
||||
abandon = true;
|
||||
onAbandon?.Invoke();
|
||||
}
|
||||
objective = null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
objective = constructor();
|
||||
if (!subObjectives.Contains(objective))
|
||||
{
|
||||
AddSubObjective(objective);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void OnSelected()
|
||||
{
|
||||
// Should we reset steering here?
|
||||
//if (!ConcurrentObjectives)
|
||||
//{
|
||||
// SteeringManager.Reset();
|
||||
//}
|
||||
}
|
||||
|
||||
public virtual void Reset() { }
|
||||
|
||||
protected abstract void Act(float deltaTime);
|
||||
|
||||
public abstract bool IsCompleted();
|
||||
|
||||
public abstract bool IsDuplicate(AIObjective otherObjective);
|
||||
}
|
||||
}
|
||||
|
||||
+49
-29
@@ -1,19 +1,19 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveChargeBatteries : AIObjectiveLoop<PowerContainer>
|
||||
{
|
||||
public override string DebugTag => "charge batteries";
|
||||
private readonly IEnumerable<PowerContainer> batteryList;
|
||||
private IEnumerable<PowerContainer> batteryList;
|
||||
|
||||
public AIObjectiveChargeBatteries(Character character, string option) : base(character, option)
|
||||
{
|
||||
batteryList = Item.ItemList.Select(i => i.GetComponent<PowerContainer>()).Where(b => b != null);
|
||||
}
|
||||
public AIObjectiveChargeBatteries(Character character, AIObjectiveManager objectiveManager, string option, float priorityModifier)
|
||||
: base(character, objectiveManager, priorityModifier, option) { }
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
@@ -22,37 +22,57 @@ namespace Barotrauma
|
||||
|
||||
protected override void FindTargets()
|
||||
{
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Prefab.Identifier != "battery" && !item.HasTag("battery")) { continue; }
|
||||
if (item.Submarine == null) { continue; }
|
||||
if (item.Submarine.TeamID != character.TeamID) { continue; }
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; }
|
||||
var battery = item.GetComponent<PowerContainer>();
|
||||
if (battery != null)
|
||||
{
|
||||
if (!ignoreList.Contains(battery))
|
||||
{
|
||||
if (!targets.Contains(battery))
|
||||
{
|
||||
targets.Add(battery);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targets.None())
|
||||
base.FindTargets();
|
||||
if (targets.None() && objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNoBatteries"), null, 4.0f, "nobatteries", 10.0f);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool Filter(PowerContainer battery)
|
||||
{
|
||||
if (battery == null) { return false; }
|
||||
var item = battery.Item;
|
||||
if (item.Submarine == null) { return false; }
|
||||
if (item.CurrentHull == null) { return false; }
|
||||
if (item.Submarine.TeamID != character.TeamID) { return false; }
|
||||
if (item.ConditionPercentage <= 0) { return false; }
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { return false; }
|
||||
if (Character.CharacterList.Any(c => c.CurrentHull == item.CurrentHull && !HumanAIController.IsFriendly(c))) { return false; }
|
||||
if (Option == "charge")
|
||||
{
|
||||
if (battery.RechargeRatio >= PowerContainer.aiRechargeTargetRatio - 0.01f) { return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
targets.Sort((x, y) => x.ChargePercentage.CompareTo(y.ChargePercentage));
|
||||
if (battery.RechargeRatio <= 0) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override float TargetEvaluation()
|
||||
{
|
||||
if (Option == "charge")
|
||||
{
|
||||
return targets.Max(t => MathHelper.Lerp(100, 0, Math.Abs(PowerContainer.aiRechargeTargetRatio - t.RechargeRatio)));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
return targets.Max(t => MathHelper.Lerp(0, 100, t.RechargeRatio));
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool Filter(PowerContainer battery) => true;
|
||||
protected override float Average(PowerContainer battery) => 100 - battery.ChargePercentage;
|
||||
protected override IEnumerable<PowerContainer> GetList() => batteryList;
|
||||
protected override AIObjective ObjectiveConstructor(PowerContainer battery) => new AIObjectiveOperateItem(battery, character, Option, false);
|
||||
protected override IEnumerable<PowerContainer> GetList()
|
||||
{
|
||||
if (batteryList == null)
|
||||
{
|
||||
batteryList = Item.ItemList.Select(i => i.GetComponent<PowerContainer>()).Where(b => b != null);
|
||||
}
|
||||
return batteryList;
|
||||
}
|
||||
|
||||
protected override AIObjective ObjectiveConstructor(PowerContainer battery)
|
||||
=> new AIObjectiveOperateItem(battery, character, objectiveManager, Option, false, priorityModifier: PriorityModifier) { IsLoop = false };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using FarseerPhysics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveCombat : AIObjective
|
||||
{
|
||||
public override string DebugTag => "combat";
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public bool useCoolDown = true;
|
||||
|
||||
const float CoolDown = 10.0f;
|
||||
|
||||
@@ -41,29 +43,39 @@ namespace Barotrauma
|
||||
return _weaponComponent;
|
||||
}
|
||||
}
|
||||
private AIObjectiveContainItem reloadWeaponObjective;
|
||||
private Hull retreatTarget;
|
||||
private AIObjectiveGoTo retreatObjective;
|
||||
private AIObjectiveFindSafety findSafety;
|
||||
|
||||
private readonly AIObjectiveFindSafety findSafety;
|
||||
private readonly HashSet<RangedWeapon> rangedWeapons = new HashSet<RangedWeapon>();
|
||||
private readonly HashSet<MeleeWeapon> meleeWeapons = new HashSet<MeleeWeapon>();
|
||||
private readonly HashSet<Item> adHocWeapons = new HashSet<Item>();
|
||||
|
||||
private AIObjectiveContainItem reloadWeaponObjective;
|
||||
private AIObjectiveGoTo retreatObjective;
|
||||
private AIObjectiveGoTo followTargetObjective;
|
||||
|
||||
private Hull retreatTarget;
|
||||
private float coolDownTimer;
|
||||
|
||||
public enum CombatMode
|
||||
{
|
||||
Defensive,
|
||||
Offensive, // Not implemented
|
||||
Offensive,
|
||||
Retreat
|
||||
}
|
||||
|
||||
public CombatMode Mode { get; private set; }
|
||||
|
||||
public AIObjectiveCombat(Character character, Character enemy, CombatMode mode) : base(character, "")
|
||||
public AIObjectiveCombat(Character character, Character enemy, CombatMode mode, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
Enemy = enemy;
|
||||
coolDownTimer = CoolDown;
|
||||
findSafety = HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>();
|
||||
findSafety.Priority = 0;
|
||||
findSafety.unreachable.Clear();
|
||||
findSafety = objectiveManager.GetObjective<AIObjectiveFindSafety>();
|
||||
if (findSafety != null)
|
||||
{
|
||||
findSafety.Priority = 0;
|
||||
findSafety.unreachable.Clear();
|
||||
}
|
||||
Mode = mode;
|
||||
if (Enemy == null)
|
||||
{
|
||||
@@ -73,12 +85,22 @@ namespace Barotrauma
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
coolDownTimer -= deltaTime;
|
||||
if (useCoolDown)
|
||||
{
|
||||
coolDownTimer -= deltaTime;
|
||||
}
|
||||
if (abandon) { return; }
|
||||
Arm(deltaTime);
|
||||
Move(deltaTime);
|
||||
}
|
||||
|
||||
private void Arm(float deltaTime)
|
||||
{
|
||||
switch (Mode)
|
||||
{
|
||||
case CombatMode.Offensive:
|
||||
case CombatMode.Defensive:
|
||||
if (Weapon != null && character.Inventory.Items.Contains(_weapon))
|
||||
if (Weapon != null && !character.Inventory.Items.Contains(_weapon) || _weaponComponent != null && !_weaponComponent.HasRequiredContainedItems(false))
|
||||
{
|
||||
Weapon = null;
|
||||
}
|
||||
@@ -90,40 +112,65 @@ namespace Barotrauma
|
||||
{
|
||||
Mode = CombatMode.Retreat;
|
||||
}
|
||||
else if (Equip(deltaTime))
|
||||
if (Equip())
|
||||
{
|
||||
if (Reload(deltaTime))
|
||||
{
|
||||
Attack(deltaTime);
|
||||
}
|
||||
}
|
||||
// When defensive, try to retreat to safety. TODO: in offsensive mode, engage the target
|
||||
Retreat(deltaTime);
|
||||
break;
|
||||
case CombatMode.Retreat:
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private void Move(float deltaTime)
|
||||
{
|
||||
switch (Mode)
|
||||
{
|
||||
case CombatMode.Offensive:
|
||||
Engage(deltaTime);
|
||||
break;
|
||||
case CombatMode.Defensive:
|
||||
case CombatMode.Retreat:
|
||||
Retreat(deltaTime);
|
||||
break;
|
||||
case CombatMode.Offensive:
|
||||
default:
|
||||
throw new System.NotImplementedException();
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private Item GetWeapon()
|
||||
{
|
||||
rangedWeapons.Clear();
|
||||
meleeWeapons.Clear();
|
||||
adHocWeapons.Clear();
|
||||
Item weapon = null;
|
||||
_weaponComponent = null;
|
||||
var weapon = character.Inventory.FindItemByTag("weapon");
|
||||
if (weapon == null)
|
||||
foreach (var item in character.Inventory.Items)
|
||||
{
|
||||
foreach (var item in character.Inventory.Items)
|
||||
if (item == null) { continue; }
|
||||
foreach (var component in item.Components)
|
||||
{
|
||||
if (item == null) { continue; }
|
||||
foreach (var component in item.Components)
|
||||
if (component is RangedWeapon rw)
|
||||
{
|
||||
if (component is MeleeWeapon || component is RangedWeapon)
|
||||
if (rw.HasRequiredContainedItems(false))
|
||||
{
|
||||
return item;
|
||||
rangedWeapons.Add(rw);
|
||||
}
|
||||
}
|
||||
else if (component is MeleeWeapon mw)
|
||||
{
|
||||
if (mw.HasRequiredContainedItems(false))
|
||||
{
|
||||
meleeWeapons.Add(mw);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var effects = component.statusEffectLists;
|
||||
if (effects != null)
|
||||
{
|
||||
@@ -133,7 +180,10 @@ namespace Barotrauma
|
||||
{
|
||||
if (statusEffect.Afflictions.Any())
|
||||
{
|
||||
return item;
|
||||
if (component.HasRequiredContainedItems(false))
|
||||
{
|
||||
adHocWeapons.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,6 +191,20 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
var rangedWeapon = rangedWeapons.OrderByDescending(w => w.CombatPriority).FirstOrDefault();
|
||||
var meleeWeapon = meleeWeapons.OrderByDescending(w => w.CombatPriority).FirstOrDefault();
|
||||
if (rangedWeapon != null)
|
||||
{
|
||||
weapon = rangedWeapon.Item;
|
||||
}
|
||||
else if (meleeWeapon != null)
|
||||
{
|
||||
weapon = meleeWeapon.Item;
|
||||
}
|
||||
if (weapon == null)
|
||||
{
|
||||
weapon = adHocWeapons.GetRandom(Rand.RandSync.Server);
|
||||
}
|
||||
return weapon;
|
||||
}
|
||||
|
||||
@@ -155,7 +219,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool Equip(float deltaTime)
|
||||
private bool Equip()
|
||||
{
|
||||
if (!character.SelectedItems.Contains(Weapon))
|
||||
{
|
||||
@@ -166,8 +230,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
//couldn't equip the item, escape
|
||||
//Abandon(deltaTime);
|
||||
Mode = CombatMode.Retreat;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -176,6 +239,7 @@ namespace Barotrauma
|
||||
|
||||
private void Retreat(float deltaTime)
|
||||
{
|
||||
followTargetObjective = null;
|
||||
if (retreatTarget == null || (retreatObjective != null && !retreatObjective.CanBeCompleted))
|
||||
{
|
||||
retreatTarget = findSafety.FindBestHull(new List<Hull>() { character.CurrentHull });
|
||||
@@ -184,12 +248,45 @@ namespace Barotrauma
|
||||
{
|
||||
if (retreatObjective == null || retreatObjective.Target != retreatTarget)
|
||||
{
|
||||
retreatObjective = new AIObjectiveGoTo(retreatTarget, character, false, true);
|
||||
retreatObjective = new AIObjectiveGoTo(retreatTarget, character, objectiveManager, false, true);
|
||||
}
|
||||
retreatObjective.TryComplete(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void Engage(float deltaTime)
|
||||
{
|
||||
retreatTarget = null;
|
||||
retreatObjective = null;
|
||||
if (followTargetObjective == null)
|
||||
{
|
||||
followTargetObjective = new AIObjectiveGoTo(Enemy, character, objectiveManager, repeat: true, getDivingGearIfNeeded: true)
|
||||
{
|
||||
AllowGoingOutside = true,
|
||||
IgnoreIfTargetDead = true,
|
||||
CheckVisibility = true
|
||||
};
|
||||
}
|
||||
if (WeaponComponent is RangedWeapon)
|
||||
{
|
||||
followTargetObjective.CloseEnough = 3;
|
||||
}
|
||||
else if (WeaponComponent is MeleeWeapon mw)
|
||||
{
|
||||
followTargetObjective.CloseEnough = ConvertUnits.ToSimUnits(mw.Range);
|
||||
}
|
||||
else if (WeaponComponent is RepairTool rt)
|
||||
{
|
||||
followTargetObjective.CloseEnough = ConvertUnits.ToSimUnits(rt.Range);
|
||||
}
|
||||
if (retreatTarget != null)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
Mode = CombatMode.Retreat;
|
||||
}
|
||||
followTargetObjective.TryComplete(deltaTime);
|
||||
}
|
||||
|
||||
private bool Reload(float deltaTime)
|
||||
{
|
||||
if (WeaponComponent != null && WeaponComponent.requiredItems.ContainsKey(RelatedItem.RelationType.Contained))
|
||||
@@ -202,7 +299,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (reloadWeaponObjective == null)
|
||||
{
|
||||
reloadWeaponObjective = new AIObjectiveContainItem(character, requiredItem.Identifiers, Weapon.GetComponent<ItemContainer>());
|
||||
reloadWeaponObjective = new AIObjectiveContainItem(character, requiredItem.Identifiers, Weapon.GetComponent<ItemContainer>(), objectiveManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -215,6 +312,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (!reloadWeaponObjective.CanBeCompleted)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
Mode = CombatMode.Retreat;
|
||||
}
|
||||
else
|
||||
@@ -229,14 +327,31 @@ namespace Barotrauma
|
||||
private IEnumerable<FarseerPhysics.Dynamics.Body> myBodies;
|
||||
private void Attack(float deltaTime)
|
||||
{
|
||||
float squaredDistance = Vector2.DistanceSquared(character.Position, Enemy.Position);
|
||||
character.CursorPosition = Enemy.Position;
|
||||
float engageDistance = 500;
|
||||
if (squaredDistance > engageDistance * engageDistance) { return; }
|
||||
bool canSeeTarget = character.CanSeeCharacter(Enemy);
|
||||
if (!canSeeTarget && character.CurrentHull != Enemy.CurrentHull) { return; }
|
||||
if (Weapon.RequireAimToUse)
|
||||
{
|
||||
character.SetInput(InputType.Aim, false, true);
|
||||
bool isOperatingButtons = false;
|
||||
if (SteeringManager == PathSteering)
|
||||
{
|
||||
var door = PathSteering.CurrentPath?.CurrentNode?.ConnectedDoor;
|
||||
if (door != null && !door.IsOpen)
|
||||
{
|
||||
isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents<Controller>(true).Any();
|
||||
}
|
||||
}
|
||||
if (!isOperatingButtons && character.SelectedConstruction == null)
|
||||
{
|
||||
character.SetInput(InputType.Aim, false, true);
|
||||
}
|
||||
}
|
||||
if (WeaponComponent is MeleeWeapon meleeWeapon)
|
||||
{
|
||||
if (Vector2.DistanceSquared(character.Position, Enemy.Position) <= meleeWeapon.Range * meleeWeapon.Range)
|
||||
if (squaredDistance <= meleeWeapon.Range * meleeWeapon.Range)
|
||||
{
|
||||
character.SetInput(InputType.Shoot, false, true);
|
||||
Weapon.Use(deltaTime, character);
|
||||
@@ -246,7 +361,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (WeaponComponent is RepairTool repairTool)
|
||||
{
|
||||
if (Vector2.DistanceSquared(character.Position, Enemy.Position) > repairTool.Range * repairTool.Range) { return; }
|
||||
if (squaredDistance > repairTool.Range * repairTool.Range) { return; }
|
||||
}
|
||||
if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - character.Position) < MathHelper.PiOver4)
|
||||
{
|
||||
@@ -254,7 +369,8 @@ namespace Barotrauma
|
||||
{
|
||||
myBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody);
|
||||
}
|
||||
var pickedBody = Submarine.PickBody(character.SimPosition, Enemy.SimPosition, myBodies);
|
||||
var collisionCategories = Physics.CollisionCharacter | Physics.CollisionWall;
|
||||
var pickedBody = Submarine.PickBody(character.SimPosition, Enemy.SimPosition, myBodies, collisionCategories);
|
||||
if (pickedBody != null)
|
||||
{
|
||||
Character target = null;
|
||||
@@ -276,17 +392,15 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void Abandon(float deltaTime)
|
||||
{
|
||||
abandon = true;
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
|
||||
public override bool IsCompleted()
|
||||
{
|
||||
bool completed = (Enemy != null && (Enemy.Removed || Enemy.IsDead)) || coolDownTimer <= 0;
|
||||
bool completed = (Enemy != null && (Enemy.Removed || Enemy.IsDead)) || (useCoolDown && coolDownTimer <= 0);
|
||||
if (completed)
|
||||
{
|
||||
if (objectiveManager.CurrentOrder == this && Enemy != null && Enemy.IsDead)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogTargetDown"), null, 3.0f, "targetdown", 30.0f);
|
||||
}
|
||||
if (Weapon != null)
|
||||
{
|
||||
Unequip();
|
||||
@@ -295,8 +409,12 @@ namespace Barotrauma
|
||||
return completed;
|
||||
}
|
||||
|
||||
public override bool CanBeCompleted => !abandon && (reloadWeaponObjective == null || reloadWeaponObjective.CanBeCompleted) && (retreatObjective == null || retreatObjective.CanBeCompleted);
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager) => (Enemy != null && (Enemy.Removed || Enemy.IsDead)) ? 0 : 100;
|
||||
public override bool CanBeCompleted => !abandon &&
|
||||
(reloadWeaponObjective == null || reloadWeaponObjective.CanBeCompleted) &&
|
||||
(retreatObjective == null || retreatObjective.CanBeCompleted) &&
|
||||
(followTargetObjective == null || followTargetObjective.CanBeCompleted);
|
||||
|
||||
public override float GetPriority() => (Enemy != null && (Enemy.Removed || Enemy.IsDead)) ? 0 : Math.Min(100 * PriorityModifier, 100);
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
|
||||
+24
-43
@@ -9,15 +9,9 @@ namespace Barotrauma
|
||||
{
|
||||
public override string DebugTag => "contain item";
|
||||
|
||||
public int MinContainedAmount = 1;
|
||||
|
||||
//can either be a tag or an identifier
|
||||
private string[] itemIdentifiers;
|
||||
|
||||
private ItemContainer container;
|
||||
|
||||
public Func<Item, float> GetItemPriority;
|
||||
|
||||
public int MinContainedAmount = 1;
|
||||
public string[] ignoredContainerIdentifiers;
|
||||
|
||||
//can either be a tag or an identifier
|
||||
@@ -27,14 +21,12 @@ namespace Barotrauma
|
||||
private bool isCompleted;
|
||||
private AIObjectiveGetItem getItemObjective;
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
|
||||
public AIObjectiveContainItem(Character character, string itemIdentifier, ItemContainer container)
|
||||
: this(character, new string[] { itemIdentifier }, container)
|
||||
{
|
||||
}
|
||||
|
||||
public AIObjectiveContainItem(Character character, string[] itemIdentifiers, ItemContainer container)
|
||||
: base (character, "")
|
||||
public AIObjectiveContainItem(Character character, string itemIdentifier, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: this(character, new string[] { itemIdentifier }, container, objectiveManager, priorityModifier) { }
|
||||
|
||||
public AIObjectiveContainItem(Character character, string[] itemIdentifiers, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base (character, objectiveManager, priorityModifier)
|
||||
{
|
||||
this.itemIdentifiers = itemIdentifiers;
|
||||
for (int i = 0; i < itemIdentifiers.Length; i++)
|
||||
@@ -51,22 +43,10 @@ namespace Barotrauma
|
||||
int containedItemCount = 0;
|
||||
foreach (Item item in container.Inventory.Items)
|
||||
{
|
||||
if (item != null && itemIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id))) containedItemCount++;
|
||||
}
|
||||
|
||||
return containedItemCount >= MinContainedAmount;
|
||||
}
|
||||
|
||||
public override bool CanBeCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
if (goToObjective != null)
|
||||
if (item != null && itemIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id)))
|
||||
{
|
||||
containedItemCount++;
|
||||
}
|
||||
|
||||
return getItemObjective == null || getItemObjective.CanBeCompleted;
|
||||
}
|
||||
return containedItemCount >= MinContainedAmount;
|
||||
}
|
||||
@@ -88,17 +68,16 @@ namespace Barotrauma
|
||||
foreach (string identifier in itemIdentifiers)
|
||||
{
|
||||
itemToContain = character.Inventory.FindItemByIdentifier(identifier) ?? character.Inventory.FindItemByTag(identifier);
|
||||
if (itemToContain != null && itemToContain.Condition > 0.0f) break;
|
||||
}
|
||||
|
||||
if (itemToContain != null && itemToContain.Condition > 0.0f) { break; }
|
||||
}
|
||||
if (itemToContain == null)
|
||||
{
|
||||
getItemObjective = new AIObjectiveGetItem(character, itemIdentifiers)
|
||||
{
|
||||
GetItemPriority = GetItemPriority,
|
||||
ignoredContainerIdentifiers = ignoredContainerIdentifiers
|
||||
};
|
||||
AddSubObjective(getItemObjective);
|
||||
TryAddSubObjective(ref getItemObjective, () =>
|
||||
new AIObjectiveGetItem(character, itemIdentifiers, objectiveManager)
|
||||
{
|
||||
GetItemPriority = GetItemPriority,
|
||||
ignoredContainerIdentifiers = ignoredContainerIdentifiers
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (container.Item.ParentInventory == character.Inventory)
|
||||
@@ -115,7 +94,8 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (container.Item.CurrentHull != character.CurrentHull || (Vector2.Distance(character.Position, container.Item.Position) > container.Item.InteractDistance && !container.Item.IsInsideTrigger(character.WorldPosition)))
|
||||
if (container.Item.CurrentHull != character.CurrentHull ||
|
||||
(Vector2.DistanceSquared(character.Position, container.Item.Position) > Math.Pow(container.Item.InteractDistance, 2) && !container.Item.IsInsideTrigger(character.WorldPosition)))
|
||||
{
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(container.Item, character, objectiveManager));
|
||||
return;
|
||||
@@ -127,14 +107,15 @@ namespace Barotrauma
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
AIObjectiveContainItem objective = otherObjective as AIObjectiveContainItem;
|
||||
if (objective == null) return false;
|
||||
if (objective.container != container) return false;
|
||||
if (objective.itemIdentifiers.Length != itemIdentifiers.Length) return false;
|
||||
|
||||
if (!(otherObjective is AIObjectiveContainItem objective)) { return false; }
|
||||
if (objective.container != container) { return false; }
|
||||
if (objective.itemIdentifiers.Length != itemIdentifiers.Length) { return false; }
|
||||
for (int i = 0; i < itemIdentifiers.Length; i++)
|
||||
{
|
||||
if (objective.itemIdentifiers[i] != itemIdentifiers[i]) return false;
|
||||
if (objective.itemIdentifiers[i] != itemIdentifiers[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
+22
-54
@@ -8,78 +8,53 @@ namespace Barotrauma
|
||||
{
|
||||
public override string DebugTag => "decontain item";
|
||||
|
||||
//can either be a tag or an identifier
|
||||
private string[] itemIdentifiers;
|
||||
|
||||
private ItemContainer container;
|
||||
|
||||
private bool isCompleted;
|
||||
|
||||
public Func<Item, float> GetItemPriority;
|
||||
|
||||
private AIObjectiveGetItem getItemObjective;
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private Item targetItem;
|
||||
//can either be a tag or an identifier
|
||||
private readonly string[] itemIdentifiers;
|
||||
private readonly ItemContainer container;
|
||||
private readonly Item targetItem;
|
||||
|
||||
public AIObjectiveDecontainItem(Character character, Item targetItem, ItemContainer container)
|
||||
: base(character, "")
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private bool isCompleted;
|
||||
|
||||
public AIObjectiveDecontainItem(Character character, Item targetItem, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
this.targetItem = targetItem;
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
|
||||
public AIObjectiveDecontainItem(Character character, string itemIdentifier, ItemContainer container)
|
||||
: this(character, new string[] { itemIdentifier }, container)
|
||||
{
|
||||
}
|
||||
public AIObjectiveDecontainItem(Character character, string itemIdentifier, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: this(character, new string[] { itemIdentifier }, container, objectiveManager, priorityModifier) { }
|
||||
|
||||
public AIObjectiveDecontainItem(Character character, string[] itemIdentifiers, ItemContainer container)
|
||||
: base(character, "")
|
||||
public AIObjectiveDecontainItem(Character character, string[] itemIdentifiers, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
this.itemIdentifiers = itemIdentifiers;
|
||||
for (int i = 0; i < itemIdentifiers.Length; i++)
|
||||
{
|
||||
itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant();
|
||||
}
|
||||
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
public override bool IsCompleted()
|
||||
{
|
||||
return isCompleted;
|
||||
}
|
||||
public override bool IsCompleted() => isCompleted;
|
||||
|
||||
public override bool CanBeCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
if (goToObjective != null)
|
||||
{
|
||||
return goToObjective.CanBeCompleted;
|
||||
}
|
||||
|
||||
return getItemObjective == null || getItemObjective.CanBeCompleted;
|
||||
}
|
||||
}
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
public override float GetPriority()
|
||||
{
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (isCompleted) return;
|
||||
|
||||
if (isCompleted) { return; }
|
||||
Item itemToDecontain = null;
|
||||
|
||||
//get the item that should be de-contained
|
||||
if (targetItem == null)
|
||||
{
|
||||
@@ -88,7 +63,7 @@ namespace Barotrauma
|
||||
foreach (string identifier in itemIdentifiers)
|
||||
{
|
||||
itemToDecontain = container.Inventory.FindItemByIdentifier(identifier) ?? container.Inventory.FindItemByTag(identifier);
|
||||
if (itemToDecontain != null) break;
|
||||
if (itemToDecontain != null) { break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,38 +71,32 @@ namespace Barotrauma
|
||||
{
|
||||
itemToDecontain = targetItem;
|
||||
}
|
||||
|
||||
if (itemToDecontain == null || itemToDecontain.Container != container.Item) // Item not found or already de-contained, consider complete
|
||||
{
|
||||
isCompleted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (itemToDecontain.OwnInventory != character.Inventory && itemToDecontain.ParentInventory != character.Inventory)
|
||||
{
|
||||
if (Vector2.Distance(character.Position, container.Item.Position) > container.Item.InteractDistance
|
||||
&& !container.Item.IsInsideTrigger(character.WorldPosition))
|
||||
if (Vector2.DistanceSquared(character.Position, container.Item.Position) > MathUtils.Pow(container.Item.InteractDistance, 2) && !container.Item.IsInsideTrigger(character.WorldPosition))
|
||||
{
|
||||
goToObjective = new AIObjectiveGoTo(container.Item, character);
|
||||
AddSubObjective(goToObjective);
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(container.Item, character, objectiveManager));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
itemToDecontain.Drop(character);
|
||||
isCompleted = true;
|
||||
}
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
AIObjectiveDecontainItem decontainItem = otherObjective as AIObjectiveDecontainItem;
|
||||
if (decontainItem == null) return false;
|
||||
if (!(otherObjective is AIObjectiveDecontainItem decontainItem)) { return false; }
|
||||
if (decontainItem.itemIdentifiers != null && itemIdentifiers != null)
|
||||
{
|
||||
if (decontainItem.itemIdentifiers.Length != itemIdentifiers.Length) return false;
|
||||
if (decontainItem.itemIdentifiers.Length != itemIdentifiers.Length) { return false; }
|
||||
for (int i = 0; i < decontainItem.itemIdentifiers.Length; i++)
|
||||
{
|
||||
if (decontainItem.itemIdentifiers[i] != itemIdentifiers[i]) return false;
|
||||
if (decontainItem.itemIdentifiers[i] != itemIdentifiers[i]) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -135,7 +104,6 @@ namespace Barotrauma
|
||||
{
|
||||
return decontainItem.targetItem == targetItem;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+77
-84
@@ -3,6 +3,7 @@ using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -10,125 +11,117 @@ namespace Barotrauma
|
||||
{
|
||||
public override string DebugTag => "extinguish fire";
|
||||
public override bool ForceRun => true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool ConcurrentObjectives => true;
|
||||
|
||||
private Hull targetHull;
|
||||
private readonly Hull targetHull;
|
||||
|
||||
private AIObjectiveGetItem getExtinguisherObjective;
|
||||
|
||||
private AIObjectiveGoTo gotoObjective;
|
||||
|
||||
private float useExtinquisherTimer;
|
||||
|
||||
public AIObjectiveExtinguishFire(Character character, Hull targetHull) : base(character, "")
|
||||
public AIObjectiveExtinguishFire(Character character, Hull targetHull, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
this.targetHull = targetHull;
|
||||
}
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
public override float GetPriority()
|
||||
{
|
||||
if (gotoObjective != null && !gotoObjective.CanBeCompleted) { return 0; }
|
||||
if (Character.CharacterList.Any(c => c.CurrentHull == targetHull && !HumanAIController.IsFriendly(c))) { return 0; }
|
||||
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
|
||||
float dist = Math.Abs(character.WorldPosition.X - targetHull.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - targetHull.WorldPosition.Y) * 2.0f;
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 10000, dist));
|
||||
float severityFactor = MathHelper.Lerp(0, 1, MathHelper.Clamp(targetHull.FireSources.Sum(fs => fs.Size.X) / targetHull.Size.X, 0, 1));
|
||||
return MathHelper.Clamp(Priority * severityFactor * distanceFactor, 0, 100);
|
||||
float severity = AIObjectiveExtinguishFires.GetFireSeverity(targetHull);
|
||||
float severityFactor = MathHelper.Lerp(0, 1, severity / 100);
|
||||
float devotion = Math.Max(Priority, 10) / 100;
|
||||
return MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + severityFactor * distanceFactor, 0, 1));
|
||||
}
|
||||
|
||||
public override bool IsCompleted()
|
||||
{
|
||||
return targetHull.FireSources.Count == 0;
|
||||
}
|
||||
public override bool IsCompleted() => targetHull.FireSources.None();
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
var otherExtinguishFire = otherObjective as AIObjectiveExtinguishFire;
|
||||
return otherExtinguishFire != null && otherExtinguishFire.targetHull == targetHull;
|
||||
}
|
||||
|
||||
public override bool CanBeCompleted
|
||||
{
|
||||
get { return getExtinguisherObjective == null || getExtinguisherObjective.IsCompleted() || getExtinguisherObjective.CanBeCompleted; }
|
||||
}
|
||||
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveExtinguishFire otherExtinguishFire && otherExtinguishFire.targetHull == targetHull;
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
var extinguisherItem = character.Inventory.FindItemByIdentifier("extinguisher") ?? character.Inventory.FindItemByTag("extinguisher");
|
||||
if (extinguisherItem == null || extinguisherItem.Condition <= 0.0f || !character.HasEquippedItem(extinguisherItem))
|
||||
{
|
||||
if (getExtinguisherObjective == null)
|
||||
TryAddSubObjective(ref getExtinguisherObjective, () =>
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogFindExtinguisher"), null, 2.0f, "findextinguisher", 30.0f);
|
||||
getExtinguisherObjective = new AIObjectiveGetItem(character, "extinguisher", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
getExtinguisherObjective.TryComplete(deltaTime);
|
||||
}
|
||||
|
||||
return;
|
||||
return new AIObjectiveGetItem(character, "extinguisher", objectiveManager, equip: true);
|
||||
});
|
||||
}
|
||||
|
||||
var extinguisher = extinguisherItem.GetComponent<RepairTool>();
|
||||
if (extinguisher == null)
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("AIObjectiveExtinguishFire failed - the item \"" + extinguisherItem + "\" has no RepairTool component but is tagged as an extinguisher");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (FireSource fs in targetHull.FireSources)
|
||||
{
|
||||
bool inRange = fs.IsInDamageRange(character, MathHelper.Clamp(fs.DamageRange * 1.5f, extinguisher.Range * 0.5f, extinguisher.Range));
|
||||
bool move = !inRange;
|
||||
if (inRange || useExtinquisherTimer > 0.0f)
|
||||
var extinguisher = extinguisherItem.GetComponent<RepairTool>();
|
||||
if (extinguisher == null)
|
||||
{
|
||||
useExtinquisherTimer += deltaTime;
|
||||
if (useExtinquisherTimer > 2.0f)
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("AIObjectiveExtinguishFire failed - the item \"" + extinguisherItem + "\" has no RepairTool component but is tagged as an extinguisher");
|
||||
#endif
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
foreach (FireSource fs in targetHull.FireSources)
|
||||
{
|
||||
bool inRange = fs.IsInDamageRange(character, MathHelper.Clamp(fs.DamageRange * 1.5f, extinguisher.Range * 0.5f, extinguisher.Range));
|
||||
bool move = !inRange;
|
||||
if (inRange || useExtinquisherTimer > 0.0f)
|
||||
{
|
||||
useExtinquisherTimer = 0.0f;
|
||||
}
|
||||
character.AIController.SteeringManager.Reset();
|
||||
character.CursorPosition = fs.Position;
|
||||
if (extinguisher.Item.RequireAimToUse)
|
||||
{
|
||||
character.SetInput(InputType.Aim, false, true);
|
||||
}
|
||||
Limb sightLimb = null;
|
||||
if (character.Inventory.IsInLimbSlot(extinguisherItem, InvSlotType.RightHand))
|
||||
{
|
||||
sightLimb = character.AnimController.GetLimb(LimbType.RightHand);
|
||||
}
|
||||
else if (character.Inventory.IsInLimbSlot(extinguisherItem, InvSlotType.LeftHand))
|
||||
{
|
||||
sightLimb = character.AnimController.GetLimb(LimbType.LeftHand);
|
||||
}
|
||||
if (!character.CanSeeTarget(fs, sightLimb))
|
||||
{
|
||||
move = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
move = false;
|
||||
extinguisher.Use(deltaTime, character);
|
||||
if (!targetHull.FireSources.Contains(fs))
|
||||
useExtinquisherTimer += deltaTime;
|
||||
if (useExtinquisherTimer > 2.0f)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogPutOutFire").Replace("[roomname]", targetHull.Name), null, 0, "putoutfire", 10.0f);
|
||||
useExtinquisherTimer = 0.0f;
|
||||
}
|
||||
character.AIController.SteeringManager.Reset();
|
||||
character.CursorPosition = fs.Position;
|
||||
if (extinguisher.Item.RequireAimToUse)
|
||||
{
|
||||
bool isOperatingButtons = false;
|
||||
if (SteeringManager == PathSteering)
|
||||
{
|
||||
var door = PathSteering.CurrentPath?.CurrentNode?.ConnectedDoor;
|
||||
if (door != null && !door.IsOpen)
|
||||
{
|
||||
isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents<Controller>(true).Any();
|
||||
}
|
||||
}
|
||||
if (!isOperatingButtons)
|
||||
{
|
||||
character.SetInput(InputType.Aim, false, true);
|
||||
}
|
||||
}
|
||||
Limb sightLimb = null;
|
||||
if (character.Inventory.IsInLimbSlot(extinguisherItem, InvSlotType.RightHand))
|
||||
{
|
||||
sightLimb = character.AnimController.GetLimb(LimbType.RightHand);
|
||||
}
|
||||
else if (character.Inventory.IsInLimbSlot(extinguisherItem, InvSlotType.LeftHand))
|
||||
{
|
||||
sightLimb = character.AnimController.GetLimb(LimbType.LeftHand);
|
||||
}
|
||||
if (!character.CanSeeTarget(fs, sightLimb))
|
||||
{
|
||||
move = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
move = false;
|
||||
extinguisher.Use(deltaTime, character);
|
||||
if (!targetHull.FireSources.Contains(fs))
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogPutOutFire").Replace("[roomname]", targetHull.Name), null, 0, "putoutfire", 10.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (move)
|
||||
{
|
||||
//go to the first firesource
|
||||
if (gotoObjective == null || !gotoObjective.CanBeCompleted || gotoObjective.IsCompleted())
|
||||
if (move)
|
||||
{
|
||||
gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(fs.Position), character);
|
||||
}
|
||||
else
|
||||
{
|
||||
gotoObjective.TryComplete(deltaTime);
|
||||
//go to the first firesource
|
||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(fs.Position), character, objectiveManager));
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+22
-40
@@ -1,62 +1,44 @@
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveExtinguishFires : AIObjective
|
||||
class AIObjectiveExtinguishFires : AIObjectiveLoop<Hull>
|
||||
{
|
||||
public override string DebugTag => "extinguish fires";
|
||||
public override bool ForceRun => true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
|
||||
private Dictionary<Hull, AIObjectiveExtinguishFire> extinguishObjectives = new Dictionary<Hull, AIObjectiveExtinguishFire>();
|
||||
public AIObjectiveExtinguishFires(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
|
||||
|
||||
public AIObjectiveExtinguishFires(Character character) : base(character, "") { }
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
protected override void FindTargets()
|
||||
{
|
||||
if (character.Submarine == null) { return 0; }
|
||||
int fireCount = character.Submarine.GetHulls(true).Sum(h => h.FireSources.Count);
|
||||
if (objectiveManager.CurrentOrder == this && fireCount > 0)
|
||||
base.FindTargets();
|
||||
if (targets.None() && objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
character.Speak(TextManager.Get("DialogNoFire"), null, 3.0f, "nofire", 30.0f);
|
||||
}
|
||||
|
||||
return MathHelper.Clamp(fireCount * 20, 0, 100);
|
||||
}
|
||||
|
||||
public override bool IsCompleted() => false;
|
||||
public override bool CanBeCompleted => true;
|
||||
protected override bool Filter(Hull hull) => IsValidTarget(hull, character);
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
return otherObjective is AIObjectiveExtinguishFires;
|
||||
}
|
||||
protected override float TargetEvaluation() => objectiveManager.CurrentObjective == this ? 100 : targets.Sum(t => GetFireSeverity(t));
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
public static float GetFireSeverity(Hull hull) => hull.FireSources.Sum(fs => fs.Size.X);
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveExtinguishFires;
|
||||
protected override IEnumerable<Hull> GetList() => Hull.hullList;
|
||||
|
||||
protected override AIObjective ObjectiveConstructor(Hull target) => new AIObjectiveExtinguishFire(character, target, objectiveManager, PriorityModifier);
|
||||
|
||||
public static bool IsValidTarget(Hull hull, Character character)
|
||||
{
|
||||
SyncRemovedObjectives(extinguishObjectives, Hull.hullList);
|
||||
if (character.Submarine == null) { return; }
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
if (hull.FireSources.None()) { continue; }
|
||||
if (hull.Submarine == null) { continue; }
|
||||
if (hull.Submarine.TeamID != character.TeamID) { continue; }
|
||||
// If the character is inside, only take connected hulls into account.
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(hull, true)) { continue; }
|
||||
if (!extinguishObjectives.TryGetValue(hull, out AIObjectiveExtinguishFire objective))
|
||||
{
|
||||
objective = new AIObjectiveExtinguishFire(character, hull);
|
||||
extinguishObjectives.Add(hull, objective);
|
||||
AddSubObjective(objective);
|
||||
}
|
||||
}
|
||||
if (extinguishObjectives.None())
|
||||
{
|
||||
character?.Speak(TextManager.Get("DialogNoFire"), null, 3.0f, "nofire", 30.0f);
|
||||
}
|
||||
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 && !character.Submarine.IsEntityFoundOnThisSub(hull, true)) { return false; }
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+28
-27
@@ -1,6 +1,7 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -9,8 +10,10 @@ namespace Barotrauma
|
||||
public override string DebugTag => "find diving gear";
|
||||
public override bool ForceRun => true;
|
||||
|
||||
private AIObjective subObjective;
|
||||
private string gearTag;
|
||||
private readonly string gearTag;
|
||||
|
||||
private AIObjectiveGetItem getDivingGear;
|
||||
private AIObjectiveContainItem getOxygen;
|
||||
|
||||
public override bool IsCompleted()
|
||||
{
|
||||
@@ -21,15 +24,16 @@ namespace Barotrauma
|
||||
{
|
||||
var containedItems = character.Inventory.Items[i].ContainedItems;
|
||||
if (containedItems == null) { continue; }
|
||||
|
||||
var oxygenTank = containedItems.FirstOrDefault(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f);
|
||||
if (oxygenTank != null) { return true; }
|
||||
return containedItems.Any(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public AIObjectiveFindDivingGear(Character character, bool needDivingSuit) : base(character, "")
|
||||
public override float GetPriority() => MathHelper.Clamp(100 - character.OxygenAvailable, 0, 100);
|
||||
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFindDivingGear;
|
||||
|
||||
public AIObjectiveFindDivingGear(Character character, bool needDivingSuit, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
gearTag = needDivingSuit ? "divingsuit" : "diving";
|
||||
}
|
||||
@@ -42,16 +46,23 @@ namespace Barotrauma
|
||||
TryAddSubObjective(ref getDivingGear, () =>
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogGetDivingGear"), null, 0.0f, "getdivinggear", 30.0f);
|
||||
subObjective = new AIObjectiveGetItem(character, gearTag, true);
|
||||
}
|
||||
return new AIObjectiveGetItem(character, gearTag, objectiveManager, equip: true);
|
||||
});
|
||||
}
|
||||
if (getDivingGear != null) { return; }
|
||||
var containedItems = item.ContainedItems;
|
||||
if (containedItems == null)
|
||||
{
|
||||
var containedItems = item.ContainedItems;
|
||||
if (containedItems == null) { return; }
|
||||
//check if there's an oxygen tank in the mask/suit
|
||||
if (containedItems == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("AIObjectiveFindDivingGear failed - the item \"" + item + "\" has no proper inventory");
|
||||
#endif
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
// Drop empty tanks
|
||||
foreach (Item containedItem in containedItems)
|
||||
{
|
||||
if (containedItem == null) { continue; }
|
||||
@@ -59,26 +70,16 @@ namespace Barotrauma
|
||||
{
|
||||
containedItem.Drop(character);
|
||||
}
|
||||
else if (containedItem.Prefab.Identifier == "oxygentank" || containedItem.HasTag("oxygensource"))
|
||||
{
|
||||
//we've got an oxygen source inside the mask/suit, all good
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!(subObjective is AIObjectiveContainItem) || subObjective.IsCompleted())
|
||||
}
|
||||
if (containedItems.None(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f))
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f);
|
||||
subObjective = new AIObjectiveContainItem(character, new string[] { "oxygentank", "oxygensource" }, item.GetComponent<ItemContainer>());
|
||||
TryAddSubObjective(ref getOxygen, () =>
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f);
|
||||
return new AIObjectiveContainItem(character, new string[] { "oxygentank", "oxygensource" }, item.GetComponent<ItemContainer>(), objectiveManager);
|
||||
});
|
||||
}
|
||||
}
|
||||
if (subObjective != null)
|
||||
{
|
||||
subObjective.TryComplete(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanBeCompleted => subObjective == null || subObjective.CanBeCompleted;
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager) => MathHelper.Clamp(100 - character.OxygenAvailable, 0, 100);
|
||||
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFindDivingGear;
|
||||
}
|
||||
}
|
||||
|
||||
+77
-116
@@ -10,7 +10,6 @@ namespace Barotrauma
|
||||
{
|
||||
public override string DebugTag => "find safety";
|
||||
public override bool ForceRun => true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
|
||||
// TODO: expose?
|
||||
const float priorityIncrease = 25;
|
||||
@@ -18,7 +17,7 @@ namespace Barotrauma
|
||||
const float SearchHullInterval = 3.0f;
|
||||
const float clearUnreachableInterval = 30;
|
||||
|
||||
public readonly List<Hull> unreachable = new List<Hull>();
|
||||
public readonly HashSet<Hull> unreachable = new HashSet<Hull>();
|
||||
|
||||
private float currenthullSafety;
|
||||
private float unreachableClearTimer;
|
||||
@@ -27,49 +26,15 @@ namespace Barotrauma
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private AIObjectiveFindDivingGear divingGearObjective;
|
||||
|
||||
public AIObjectiveFindSafety(Character character) : base(character, "") { }
|
||||
public AIObjectiveFindSafety(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
|
||||
|
||||
public override bool IsCompleted() => false;
|
||||
public override bool CanBeCompleted => true;
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFindSafety;
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
var currentHull = character.AnimController.CurrentHull;
|
||||
if (HumanAIController.NeedsDivingGear(currentHull) && divingGearObjective == null)
|
||||
{
|
||||
bool needsDivingSuit = currentHull == null || currentHull.WaterPercentage > 90;
|
||||
bool hasEquipment = needsDivingSuit ? HumanAIController.HasDivingSuit(character) : HumanAIController.HasDivingGear(character);
|
||||
if (!hasEquipment)
|
||||
{
|
||||
divingGearObjective = new AIObjectiveFindDivingGear(character, needsDivingSuit);
|
||||
}
|
||||
}
|
||||
if (divingGearObjective != null)
|
||||
{
|
||||
divingGearObjective.TryComplete(deltaTime);
|
||||
if (divingGearObjective.IsCompleted())
|
||||
{
|
||||
divingGearObjective = null;
|
||||
Priority = 0;
|
||||
}
|
||||
else if (divingGearObjective.CanBeCompleted)
|
||||
{
|
||||
// If diving gear objective is active and can be completed, wait for it to complete.
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
divingGearObjective = null;
|
||||
// Reduce the timer so that we get a safe hull target faster.
|
||||
searchHullTimer = Math.Min(1, searchHullTimer);
|
||||
}
|
||||
}
|
||||
|
||||
if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
searchHullTimer = Math.Min(1, searchHullTimer);
|
||||
}
|
||||
|
||||
if (unreachableClearTimer > 0)
|
||||
{
|
||||
unreachableClearTimer -= deltaTime;
|
||||
@@ -79,56 +44,86 @@ namespace Barotrauma
|
||||
unreachableClearTimer = clearUnreachableInterval;
|
||||
unreachable.Clear();
|
||||
}
|
||||
|
||||
if (searchHullTimer > 0.0f)
|
||||
{
|
||||
unreachableClearTimer = clearUnreachableInterval;
|
||||
unreachable.Clear();
|
||||
}
|
||||
if (character.CurrentHull == null)
|
||||
{
|
||||
var bestHull = FindBestHull();
|
||||
if (bestHull != null && bestHull != currentHull)
|
||||
{
|
||||
if (goToObjective != null)
|
||||
{
|
||||
if (goToObjective.Target != bestHull)
|
||||
{
|
||||
// If we need diving gear, we should already have it, if possible.
|
||||
goToObjective = new AIObjectiveGoTo(bestHull, character, getDivingGearIfNeeded: false)
|
||||
{
|
||||
AllowGoingOutside = HumanAIController.HasDivingSuit(character)
|
||||
};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
goToObjective = new AIObjectiveGoTo(bestHull, character, getDivingGearIfNeeded: false)
|
||||
{
|
||||
AllowGoingOutside = HumanAIController.HasDivingSuit(character)
|
||||
};
|
||||
}
|
||||
}
|
||||
searchHullTimer = SearchHullInterval;
|
||||
currenthullSafety = 0;
|
||||
Priority = 100;
|
||||
return;
|
||||
}
|
||||
if (character.OxygenAvailable < CharacterHealth.LowOxygenThreshold) { Priority = 100; }
|
||||
currenthullSafety = HumanAIController.GetHullSafety(character.CurrentHull);
|
||||
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
goToObjective.TryComplete(deltaTime);
|
||||
if (!goToObjective.CanBeCompleted)
|
||||
{
|
||||
if (!unreachable.Contains(goToObjective.Target))
|
||||
{
|
||||
unreachable.Add(goToObjective.Target as Hull);
|
||||
}
|
||||
goToObjective = null;
|
||||
HumanAIController.ObjectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
|
||||
//SteeringManager.SteeringWander();
|
||||
}
|
||||
Priority -= priorityDecrease * deltaTime;
|
||||
}
|
||||
else if (currentHull != null)
|
||||
else
|
||||
{
|
||||
float dangerFactor = (100 - currenthullSafety) / 100;
|
||||
Priority += dangerFactor * priorityIncrease * deltaTime;
|
||||
}
|
||||
Priority = MathHelper.Clamp(Priority, 0, 100);
|
||||
if (divingGearObjective != null && !divingGearObjective.IsCompleted() && divingGearObjective.CanBeCompleted)
|
||||
{
|
||||
Priority = Math.Max(Priority, Math.Min(AIObjectiveManager.OrderPriority + 20, 100));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
var currentHull = character.AnimController.CurrentHull;
|
||||
bool needsDivingGear = HumanAIController.NeedsDivingGear(currentHull);
|
||||
bool needsDivingSuit = needsDivingGear && (currentHull == null || currentHull.WaterPercentage > 90);
|
||||
bool needsEquipment = false;
|
||||
if (needsDivingSuit)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingSuit(character);
|
||||
}
|
||||
else if (needsDivingGear)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingMask(character);
|
||||
}
|
||||
if (needsEquipment)
|
||||
{
|
||||
TryAddSubObjective(ref divingGearObjective,
|
||||
() => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager),
|
||||
onAbandon: () => searchHullTimer = Math.Min(1, searchHullTimer));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (divingGearObjective != null && divingGearObjective.IsCompleted())
|
||||
{
|
||||
// Reset the devotion.
|
||||
Priority = 0;
|
||||
divingGearObjective = null;
|
||||
}
|
||||
if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
searchHullTimer = Math.Min(1, searchHullTimer);
|
||||
}
|
||||
if (searchHullTimer > 0.0f)
|
||||
{
|
||||
searchHullTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
searchHullTimer = SearchHullInterval;
|
||||
var bestHull = FindBestHull();
|
||||
if (bestHull != null && bestHull != currentHull)
|
||||
{
|
||||
if (goToObjective?.Target != bestHull)
|
||||
{
|
||||
goToObjective = null;
|
||||
}
|
||||
TryAddSubObjective(ref goToObjective, () =>
|
||||
new AIObjectiveGoTo(bestHull, character, objectiveManager, getDivingGearIfNeeded: false)
|
||||
{
|
||||
// If we need diving gear, we should already have it, if possible.
|
||||
AllowGoingOutside = HumanAIController.HasDivingSuit(character)
|
||||
}, onAbandon: () => unreachable.Add(goToObjective.Target as Hull));
|
||||
}
|
||||
}
|
||||
if (goToObjective != null) { return; }
|
||||
if (currentHull == null) { return; }
|
||||
//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;
|
||||
@@ -136,17 +131,15 @@ namespace Barotrauma
|
||||
{
|
||||
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);
|
||||
escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
//don't run from friendly NPCs
|
||||
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
|
||||
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
|
||||
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
|
||||
|
||||
if (enemy.CurrentHull == currentHull && !enemy.IsDead && !enemy.IsUnconscious &&
|
||||
if (enemy.CurrentHull == currentHull && !enemy.IsDead && !enemy.IsUnconscious &&
|
||||
(enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID))
|
||||
{
|
||||
Vector2 dir = character.Position - enemy.Position;
|
||||
@@ -154,7 +147,6 @@ namespace Barotrauma
|
||||
escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
if (escapeVel != Vector2.Zero)
|
||||
{
|
||||
//only move if we haven't reached the edge of the room
|
||||
@@ -251,36 +243,5 @@ namespace Barotrauma
|
||||
}
|
||||
return bestHull;
|
||||
}
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
return (otherObjective is AIObjectiveFindSafety);
|
||||
}
|
||||
|
||||
public override void Update(AIObjectiveManager objectiveManager, float deltaTime)
|
||||
{
|
||||
if (character.CurrentHull == null)
|
||||
{
|
||||
currenthullSafety = 0;
|
||||
Priority = 5;
|
||||
return;
|
||||
}
|
||||
if (character.OxygenAvailable < CharacterHealth.LowOxygenThreshold) { Priority = 100; }
|
||||
currenthullSafety = HumanAIController.GetHullSafety(character.CurrentHull);
|
||||
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
Priority -= priorityDecrease * deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
float dangerFactor = (100 - currenthullSafety) / 100;
|
||||
Priority += dangerFactor * priorityIncrease * deltaTime;
|
||||
}
|
||||
Priority = MathHelper.Clamp(Priority, 0, 100);
|
||||
if (divingGearObjective != null && !divingGearObjective.IsCompleted() && divingGearObjective.CanBeCompleted)
|
||||
{
|
||||
Priority = Math.Max(Priority, AIObjectiveManager.OrderPriority + 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,28 +3,24 @@ using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveFixLeak : AIObjective
|
||||
{
|
||||
public override string DebugTag => "fix leak";
|
||||
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool ForceRun => true;
|
||||
|
||||
private readonly Gap leak;
|
||||
public Gap Leak { get; private set; }
|
||||
|
||||
private AIObjectiveFindDivingGear findDivingGear;
|
||||
private AIObjectiveGetItem getWeldingTool;
|
||||
private AIObjectiveContainItem refuelObjective;
|
||||
private AIObjectiveGoTo gotoObjective;
|
||||
private AIObjectiveOperateItem operateObjective;
|
||||
|
||||
public Gap Leak
|
||||
{
|
||||
get { return leak; }
|
||||
}
|
||||
|
||||
public AIObjectiveFixLeak(Gap leak, Character character) : base (character, "")
|
||||
public AIObjectiveFixLeak(Gap leak, Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base (character, objectiveManager, priorityModifier)
|
||||
{
|
||||
Leak = leak;
|
||||
}
|
||||
@@ -34,17 +30,16 @@ namespace Barotrauma
|
||||
return Leak.Open <= 0.0f || Leak.Removed;
|
||||
}
|
||||
|
||||
public override bool CanBeCompleted => !abandon && base.CanBeCompleted;
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
public override float GetPriority()
|
||||
{
|
||||
if (leak.Open == 0.0f) { return 0.0f; }
|
||||
|
||||
float leakSize = (leak.IsHorizontal ? leak.Rect.Height : leak.Rect.Width) * Math.Max(leak.Open, 0.1f);
|
||||
|
||||
float dist = Vector2.DistanceSquared(character.SimPosition, leak.SimPosition);
|
||||
dist = Math.Max(dist / 100.0f, 1.0f);
|
||||
return Math.Min(leakSize / dist, 40.0f);
|
||||
if (Leak.Open == 0.0f) { return 0.0f; }
|
||||
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
|
||||
float dist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y) * 2.0f;
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.25f, MathUtils.InverseLerp(0, 10000, dist));
|
||||
float severity = AIObjectiveFixLeaks.GetLeakSeverity(Leak);
|
||||
float max = Math.Min((AIObjectiveManager.OrderPriority - 1), 90);
|
||||
float devotion = Math.Min(Priority, 10) / 100;
|
||||
return MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + severity * distanceFactor * PriorityModifier, 0, 1));
|
||||
}
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
@@ -55,59 +50,44 @@ namespace Barotrauma
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (!leak.IsRoomToRoom)
|
||||
if (!Leak.IsRoomToRoom)
|
||||
{
|
||||
if (findDivingGear == null)
|
||||
if (!HumanAIController.HasDivingSuit(character))
|
||||
{
|
||||
findDivingGear = new AIObjectiveFindDivingGear(character, true);
|
||||
AddSubObjective(findDivingGear);
|
||||
}
|
||||
else if (!findDivingGear.CanBeCompleted)
|
||||
{
|
||||
abandon = true;
|
||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, true, objectiveManager));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var weldingTool = character.Inventory.FindItemByTag("weldingtool");
|
||||
|
||||
if (weldingTool == null)
|
||||
{
|
||||
AddSubObjective(new AIObjectiveGetItem(character, "weldingtool", true));
|
||||
TryAddSubObjective(ref getWeldingTool, () => new AIObjectiveGetItem(character, "weldingtool", objectiveManager, true));
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
var containedItems = weldingTool.ContainedItems;
|
||||
if (containedItems == null) return;
|
||||
|
||||
var fuelTank = containedItems.FirstOrDefault(i => i.HasTag("weldingfueltank") && i.Condition > 0.0f);
|
||||
if (fuelTank == null)
|
||||
if (containedItems == null)
|
||||
{
|
||||
AddSubObjective(new AIObjectiveContainItem(character, "weldingfueltank", weldingTool.GetComponent<ItemContainer>()));
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no proper inventory");
|
||||
#endif
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
||||
if (repairTool == null) { return; }
|
||||
|
||||
Vector2 gapDiff = leak.WorldPosition - character.WorldPosition;
|
||||
|
||||
// TODO: use the collider size/reach?
|
||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
||||
{
|
||||
HumanAIController.AnimController.Crouching = true;
|
||||
}
|
||||
|
||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
||||
bool canReach = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
||||
if (canReach)
|
||||
{
|
||||
Limb sightLimb = null;
|
||||
if (character.Inventory.IsInLimbSlot(repairTool.Item, InvSlotType.RightHand))
|
||||
// Drop empty tanks
|
||||
foreach (Item containedItem in containedItems)
|
||||
{
|
||||
sightLimb = character.AnimController.GetLimb(LimbType.RightHand);
|
||||
if (containedItem == null) { continue; }
|
||||
if (containedItem.Condition <= 0.0f)
|
||||
{
|
||||
containedItem.Drop(character);
|
||||
}
|
||||
}
|
||||
if (containedItems.None(i => i.HasTag("weldingfueltank") && i.Condition > 0.0f))
|
||||
{
|
||||
TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, "weldingfueltank", weldingTool.GetComponent<ItemContainer>(), objectiveManager));
|
||||
return;
|
||||
}
|
||||
else if (character.Inventory.IsInLimbSlot(repairTool.Item, InvSlotType.LeftHand))
|
||||
{
|
||||
@@ -115,45 +95,31 @@ namespace Barotrauma
|
||||
}
|
||||
canReach = character.CanSeeTarget(leak, sightLimb);
|
||||
}
|
||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
||||
if (repairTool == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
||||
#endif
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
||||
// TODO: use the collider size/reach?
|
||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
||||
{
|
||||
HumanAIController.AnimController.Crouching = true;
|
||||
}
|
||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
||||
if (canOperate)
|
||||
{
|
||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (gotoObjective != null)
|
||||
{
|
||||
// Check if the objective is already removed -> completed/impossible
|
||||
if (!subObjectives.Contains(gotoObjective))
|
||||
{
|
||||
if (!gotoObjective.CanBeCompleted)
|
||||
{
|
||||
abandon = true;
|
||||
}
|
||||
gotoObjective = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character)
|
||||
{
|
||||
CloseEnough = reach
|
||||
};
|
||||
if (!subObjectives.Contains(gotoObjective))
|
||||
{
|
||||
AddSubObjective(gotoObjective);
|
||||
}
|
||||
}
|
||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
||||
}
|
||||
if (gotoObjective == null || gotoObjective.IsCompleted())
|
||||
{
|
||||
if (operateObjective == null)
|
||||
{
|
||||
operateObjective = new AIObjectiveOperateItem(repairTool, character, "", true, leak);
|
||||
AddSubObjective(operateObjective);
|
||||
}
|
||||
else if (!subObjectives.Contains(operateObjective))
|
||||
{
|
||||
operateObjective = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 GetStandPosition()
|
||||
|
||||
+22
-27
@@ -9,38 +9,28 @@ namespace Barotrauma
|
||||
class AIObjectiveFixLeaks : AIObjectiveLoop<Gap>
|
||||
{
|
||||
public override string DebugTag => "fix leaks";
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool ForceRun => true;
|
||||
|
||||
public AIObjectiveFixLeaks(Character character) : base (character, "") { }
|
||||
|
||||
protected override void FindTargets()
|
||||
{
|
||||
if (character.Submarine == null) { return 0; }
|
||||
if (targets.None()) { return 0; }
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
return MathHelper.Lerp(0, AIObjectiveManager.OrderPriority, targets.Average(t => Average(t)));
|
||||
}
|
||||
public AIObjectiveFixLeaks(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
|
||||
|
||||
protected override void FindTargets()
|
||||
{
|
||||
base.FindTargets();
|
||||
targets.Sort((x, y) => GetGapFixPriority(y).CompareTo(GetGapFixPriority(x)));
|
||||
if (targets.None() && objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNoLeaks"), null, 3.0f, "noleaks", 30.0f);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool Filter(Gap gap)
|
||||
protected override bool Filter(Gap gap) => IsValidTarget(gap, character);
|
||||
|
||||
public static float GetLeakSeverity(Gap leak)
|
||||
{
|
||||
bool ignore = ignoreList.Contains(gap) || gap.ConnectedWall == null || gap.ConnectedDoor != null || gap.Open <= 0 || gap.linkedTo.All(l => l == null);
|
||||
if (!ignore)
|
||||
{
|
||||
if (gap.Submarine == null) { ignore = true; }
|
||||
else if (gap.Submarine.TeamID != character.TeamID) { ignore = true; }
|
||||
else if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(gap, true)) { ignore = true; }
|
||||
}
|
||||
return ignore;
|
||||
if (leak == null) { return 0; }
|
||||
float sizeFactor = MathHelper.Lerp(1, 10, MathUtils.InverseLerp(0, 200, (leak.IsHorizontal ? leak.Rect.Width : leak.Rect.Height)));
|
||||
float severity = sizeFactor * leak.Open;
|
||||
if (!leak.IsRoomToRoom) { severity *= 50; }
|
||||
return MathHelper.Min(severity, 100);
|
||||
}
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFixLeaks;
|
||||
@@ -48,9 +38,14 @@ namespace Barotrauma
|
||||
protected override IEnumerable<Gap> GetList() => Gap.GapList;
|
||||
protected override AIObjective ObjectiveConstructor(Gap gap) => new AIObjectiveFixLeak(gap, character, objectiveManager, PriorityModifier);
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFixLeaks;
|
||||
protected override float Average(Gap gap) => gap.Open;
|
||||
protected override IEnumerable<Gap> GetList() => Gap.GapList;
|
||||
protected override AIObjective ObjectiveConstructor(Gap gap) => new AIObjectiveFixLeak(gap, character);
|
||||
public static bool IsValidTarget(Gap gap, Character character)
|
||||
{
|
||||
if (gap == null) { return false; }
|
||||
if (gap.ConnectedWall == null || gap.ConnectedDoor != null || gap.Open <= 0 || gap.linkedTo.All(l => l == null)) { return false; }
|
||||
if (gap.Submarine == null) { return false; }
|
||||
if (gap.Submarine.TeamID != character.TeamID) { return false; }
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(gap, true)) { return false; }
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ namespace Barotrauma
|
||||
{
|
||||
public override string DebugTag => "get item";
|
||||
|
||||
private readonly bool equip;
|
||||
private readonly HashSet<Item> ignoredItems = new HashSet<Item>();
|
||||
|
||||
public Func<Item, float> GetItemPriority;
|
||||
|
||||
//can be either tags or identifiers
|
||||
@@ -20,14 +23,8 @@ namespace Barotrauma
|
||||
public string[] ignoredContainerIdentifiers;
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private float currItemPriority;
|
||||
private bool equip;
|
||||
|
||||
private HashSet<Item> ignoredItems = new HashSet<Item>();
|
||||
|
||||
private bool canBeCompleted = true;
|
||||
public override bool CanBeCompleted => canBeCompleted;
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
public override float GetPriority()
|
||||
{
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
@@ -36,16 +33,19 @@ namespace Barotrauma
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
public AIObjectiveGetItem(Character character, Item targetItem, bool equip = false) : base(character, "")
|
||||
public AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip = false, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
currSearchIndex = -1;
|
||||
this.equip = equip;
|
||||
this.targetItem = targetItem;
|
||||
}
|
||||
|
||||
public AIObjectiveGetItem(Character character, string itemIdentifier, bool equip = false) : this(character, new string[] { itemIdentifier }, equip) { }
|
||||
public AIObjectiveGetItem(Character character, string itemIdentifier, AIObjectiveManager objectiveManager, bool equip = false, float priorityModifier = 1)
|
||||
: this(character, new string[] { itemIdentifier }, objectiveManager, equip, priorityModifier) { }
|
||||
|
||||
public AIObjectiveGetItem(Character character, string[] itemIdentifiers, bool equip = false) : base(character, "")
|
||||
public AIObjectiveGetItem(Character character, string[] itemIdentifiers, AIObjectiveManager objectiveManager, bool equip = false, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
currSearchIndex = -1;
|
||||
this.equip = equip;
|
||||
@@ -54,20 +54,15 @@ namespace Barotrauma
|
||||
{
|
||||
itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant();
|
||||
}
|
||||
|
||||
CheckInventory();
|
||||
}
|
||||
|
||||
private void CheckInventory()
|
||||
{
|
||||
if (itemIdentifiers == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (itemIdentifiers == null) { return; }
|
||||
for (int i = 0; i < character.Inventory.Items.Length; i++)
|
||||
{
|
||||
if (character.Inventory.Items[i] == null || character.Inventory.Items[i].Condition <= 0.0f) continue;
|
||||
if (character.Inventory.Items[i] == null || character.Inventory.Items[i].Condition <= 0.0f) { continue; }
|
||||
if (itemIdentifiers.Any(id => character.Inventory.Items[i].Prefab.Identifier == id || character.Inventory.Items[i].HasTag(id)))
|
||||
{
|
||||
targetItem = character.Inventory.Items[i];
|
||||
@@ -81,7 +76,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Item containedItem in containedItems)
|
||||
{
|
||||
if (containedItem == null || containedItem.Condition <= 0.0f) continue;
|
||||
if (containedItem == null || containedItem.Condition <= 0.0f) { continue; }
|
||||
if (itemIdentifiers.Any(id => containedItem.Prefab.Identifier == id || containedItem.HasTag(id)))
|
||||
{
|
||||
targetItem = containedItem;
|
||||
@@ -99,13 +94,11 @@ namespace Barotrauma
|
||||
FindTargetItem();
|
||||
if (targetItem == null || moveToTarget == null)
|
||||
{
|
||||
HumanAIController.ObjectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
|
||||
//SteeringManager.SteeringWander();
|
||||
objectiveManager.GetObjective<AIObjectiveIdle>()?.Wander(deltaTime);
|
||||
return;
|
||||
}
|
||||
|
||||
if (moveToTarget.CurrentHull == character.CurrentHull &&
|
||||
Vector2.DistanceSquared(character.Position, moveToTarget.Position) < MathUtils.Pow(targetItem.InteractDistance * 2, 2))
|
||||
Vector2.DistanceSquared(character.Position, moveToTarget.Position) < MathUtils.Pow(targetItem.InteractDistance, 2))
|
||||
{
|
||||
int targetSlot = -1;
|
||||
if (equip)
|
||||
@@ -123,8 +116,7 @@ namespace Barotrauma
|
||||
for (int i = 0; i < character.Inventory.Items.Length; i++)
|
||||
{
|
||||
//slot not needed by the item, continue
|
||||
if (!slots.HasFlag(character.Inventory.SlotTypes[i])) continue;
|
||||
|
||||
if (!slots.HasFlag(character.Inventory.SlotTypes[i])) { continue; }
|
||||
targetSlot = i;
|
||||
//slot free, continue
|
||||
if (character.Inventory.Items[i] == null) { continue; }
|
||||
@@ -136,7 +128,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
targetItem.TryInteract(character, false, true);
|
||||
|
||||
if (targetSlot > -1 && !character.HasEquippedItem(targetItem))
|
||||
{
|
||||
character.Inventory.TryPutItem(targetItem, targetSlot, false, false, character);
|
||||
@@ -144,23 +135,20 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (goToObjective == null || moveToTarget != goToObjective.Target)
|
||||
{
|
||||
//check if we're already looking for a diving gear
|
||||
bool gettingDivingGear = (targetItem != null && targetItem.Prefab.Identifier == "divingsuit" || targetItem.HasTag("diving")) ||
|
||||
(itemIdentifiers != null && (itemIdentifiers.Contains("diving") || itemIdentifiers.Contains("divingsuit")));
|
||||
|
||||
//don't attempt to get diving gear to reach the destination if the item we're trying to get is diving gear
|
||||
goToObjective = new AIObjectiveGoTo(moveToTarget, character, false, !gettingDivingGear);
|
||||
}
|
||||
|
||||
goToObjective.TryComplete(deltaTime);
|
||||
if (!goToObjective.CanBeCompleted)
|
||||
{
|
||||
targetItem = null;
|
||||
moveToTarget = null;
|
||||
ignoredItems.Add(targetItem);
|
||||
}
|
||||
TryAddSubObjective(ref goToObjective,
|
||||
constructor: () =>
|
||||
{
|
||||
//check if we're already looking for a diving gear
|
||||
bool gettingDivingGear = (targetItem != null && targetItem.Prefab.Identifier == "divingsuit" || targetItem.HasTag("diving")) ||
|
||||
(itemIdentifiers != null && (itemIdentifiers.Contains("diving") || itemIdentifiers.Contains("divingsuit")));
|
||||
return new AIObjectiveGoTo(moveToTarget, character, objectiveManager, repeat: false, getDivingGearIfNeeded: !gettingDivingGear);
|
||||
},
|
||||
onAbandon: () =>
|
||||
{
|
||||
targetItem = null;
|
||||
moveToTarget = null;
|
||||
ignoredItems.Add(targetItem);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,11 +164,10 @@ namespace Barotrauma
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot find the item, because neither identifiers nor item is was defined.");
|
||||
#endif
|
||||
canBeCompleted = false;
|
||||
abandon = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10 && currSearchIndex < Item.ItemList.Count - 1; i++)
|
||||
{
|
||||
currSearchIndex++;
|
||||
@@ -189,15 +176,18 @@ namespace Barotrauma
|
||||
if (item.Submarine == null) { continue; }
|
||||
else if (item.Submarine.TeamID != character.TeamID) { continue; }
|
||||
else if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; }
|
||||
|
||||
if (item.CurrentHull == null || item.Condition <= 0.0f) { continue; }
|
||||
if (itemIdentifiers.None(id => item.Prefab.Identifier == id || item.HasTag(id))) { continue; }
|
||||
|
||||
if (ignoredContainerIdentifiers != null && item.Container != null)
|
||||
{
|
||||
if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; }
|
||||
}
|
||||
|
||||
// TODO: need to exclude items that already are in the inventory?
|
||||
//// Don't allow to get items in rooms that have a fire (except fire extinguishers) or an enemy inside (except weapons)
|
||||
//if (!itemIdentifiers.Contains("extinguisher") && item.CurrentHull.FireSources.Count > 0 ||
|
||||
// item.GetComponent<MeleeWeapon>() == null && item.GetComponent<RangedWeapon>() == null && Character.CharacterList.Any(c => c.CurrentHull == item.CurrentHull && !HumanAIController.IsFriendly(c))) { continue; }
|
||||
|
||||
//if the item is inside a character's inventory, don't steal it unless the character is dead
|
||||
if (item.ParentInventory is CharacterInventory)
|
||||
{
|
||||
@@ -219,11 +209,9 @@ namespace Barotrauma
|
||||
itemPriority -= Vector2.Distance((rootContainer ?? item).Position, character.Position) * 0.01f;
|
||||
//ignore if the item has a lower priority than the currently selected one
|
||||
if (moveToTarget != null && itemPriority < currItemPriority) { continue; }
|
||||
|
||||
currItemPriority = itemPriority;
|
||||
targetItem = item;
|
||||
moveToTarget = rootContainer ?? item;
|
||||
|
||||
}
|
||||
//if searched through all the items and a target wasn't found, can't be completed
|
||||
if (currSearchIndex >= Item.ItemList.Count - 1 && targetItem == null)
|
||||
@@ -231,21 +219,21 @@ namespace Barotrauma
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s): {string.Join(", ", itemIdentifiers)}");
|
||||
#endif
|
||||
canBeCompleted = false;
|
||||
abandon = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
AIObjectiveGetItem getItem = otherObjective as AIObjectiveGetItem;
|
||||
if (getItem == null) return false;
|
||||
if (getItem.equip != equip) return false;
|
||||
if (getItem == null) { return false; }
|
||||
if (getItem.equip != equip) { return false; }
|
||||
if (getItem.itemIdentifiers != null && itemIdentifiers != null)
|
||||
{
|
||||
if (getItem.itemIdentifiers.Length != itemIdentifiers.Length) return false;
|
||||
if (getItem.itemIdentifiers.Length != itemIdentifiers.Length) { return false; }
|
||||
for (int i = 0; i < getItem.itemIdentifiers.Length; i++)
|
||||
{
|
||||
if (getItem.itemIdentifiers[i] != itemIdentifiers[i]) return false;
|
||||
if (getItem.itemIdentifiers[i] != itemIdentifiers[i]) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -263,7 +251,10 @@ namespace Barotrauma
|
||||
foreach (string itemName in itemIdentifiers)
|
||||
{
|
||||
var matchingItem = character.Inventory.FindItemByTag(itemName) ?? character.Inventory.FindItemByIdentifier(itemName);
|
||||
if (matchingItem != null && (!equip || character.HasEquippedItem(matchingItem))) return true;
|
||||
if (matchingItem != null && (!equip || character.HasEquippedItem(matchingItem)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -9,29 +9,23 @@ namespace Barotrauma
|
||||
{
|
||||
public override string DebugTag => "go to";
|
||||
|
||||
private AIObjectiveFindDivingGear findDivingGear;
|
||||
|
||||
private AIObjectiveFindDivingGear findDivingGear;
|
||||
private Vector2 targetPos;
|
||||
private bool repeat;
|
||||
private bool cannotReach;
|
||||
|
||||
//how long until the path to the target is declared unreachable
|
||||
private float waitUntilPathUnreachable;
|
||||
private bool getDivingGearIfNeeded;
|
||||
|
||||
public float CloseEnough = 0.5f;
|
||||
public float CloseEnough { get; set; } = 0.5f;
|
||||
public bool IgnoreIfTargetDead { get; set; }
|
||||
public bool AllowGoingOutside { get; set; }
|
||||
public bool CheckVisibility { get; set; }
|
||||
|
||||
public bool IgnoreIfTargetDead;
|
||||
|
||||
public bool AllowGoingOutside = false;
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
public override float GetPriority()
|
||||
{
|
||||
if (FollowControlledCharacter && Character.Controlled == null) { return 0.0f; }
|
||||
if (Target != null && Target.Removed) { return 0.0f; }
|
||||
if (IgnoreIfTargetDead && Target is Character character && character.IsDead) { return 0.0f; }
|
||||
|
||||
if (IgnoreIfTargetDead && Target is Character character && character.IsDead) { return 0.0f; }
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
@@ -39,44 +33,6 @@ namespace Barotrauma
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
public override bool CanBeCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
bool canComplete = !cannotReach && !abandon;
|
||||
if (canComplete)
|
||||
{
|
||||
if (FollowControlledCharacter && Character.Controlled == null)
|
||||
{
|
||||
canComplete = false;
|
||||
}
|
||||
else if (Target != null && Target.Removed)
|
||||
{
|
||||
canComplete = false;
|
||||
}
|
||||
else if (!repeat && waitUntilPathUnreachable < 0)
|
||||
{
|
||||
if (SteeringManager == PathSteering && PathSteering.CurrentPath != null)
|
||||
{
|
||||
canComplete = !PathSteering.CurrentPath.Unreachable;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!canComplete)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {(Target != null ? Target.ToString() : TargetPos.ToString())}", Color.Yellow);
|
||||
#endif
|
||||
if (HumanAIController.ObjectiveManager.CurrentOrder != null)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogCannotReach"), identifier: "cannotreach", minDurationBetweenSimilar: 10.0f);
|
||||
}
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
return canComplete;
|
||||
}
|
||||
}
|
||||
|
||||
public Entity Target { get; private set; }
|
||||
|
||||
public Vector2 TargetPos => Target != null ? Target.SimPosition : targetPos;
|
||||
@@ -110,27 +66,36 @@ namespace Barotrauma
|
||||
{
|
||||
if (FollowControlledCharacter)
|
||||
{
|
||||
if (Character.Controlled == null) { return; }
|
||||
if (Character.Controlled == null)
|
||||
{
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
Target = Character.Controlled;
|
||||
}
|
||||
|
||||
if (Target == character)
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
waitUntilPathUnreachable -= deltaTime;
|
||||
|
||||
if (!character.IsClimbing)
|
||||
{
|
||||
character.SelectedConstruction = null;
|
||||
}
|
||||
|
||||
if (Target != null) { character.AIController.SelectTarget(Target.AiTarget); }
|
||||
|
||||
if (Target != null)
|
||||
{
|
||||
if (Target.Removed)
|
||||
{
|
||||
abandon = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AIController.SelectTarget(Target.AiTarget);
|
||||
}
|
||||
}
|
||||
Vector2 currTargetPos = Vector2.Zero;
|
||||
|
||||
if (Target == null)
|
||||
{
|
||||
currTargetPos = targetPos;
|
||||
@@ -145,8 +110,12 @@ namespace Barotrauma
|
||||
currTargetPos -= character.Submarine.SimPosition;
|
||||
}
|
||||
}
|
||||
|
||||
if (Vector2.DistanceSquared(currTargetPos, character.SimPosition) < CloseEnough * CloseEnough)
|
||||
bool sightCheck = true;
|
||||
if (CheckVisibility && Target != null)
|
||||
{
|
||||
sightCheck = Target is Character ch ? character.CanSeeCharacter(ch) : character.CanSeeTarget(Target);
|
||||
}
|
||||
if (sightCheck && Vector2.DistanceSquared(currTargetPos, character.SimPosition) < CloseEnough * CloseEnough)
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
character.AnimController.TargetDir = currTargetPos.X > character.SimPosition.X ? Direction.Right : Direction.Left;
|
||||
@@ -154,31 +123,50 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
bool isInside = character.CurrentHull != null;
|
||||
bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null;
|
||||
bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty;
|
||||
bool targetIsOutside = (Target != null && Target.Submarine == null) || (insideSteering && PathSteering.CurrentPath.HasOutdoorsNodes);
|
||||
if (isInside && targetIsOutside && !AllowGoingOutside)
|
||||
{
|
||||
cannotReach = true;
|
||||
abandon = true;
|
||||
}
|
||||
else if (!repeat && waitUntilPathUnreachable < 0)
|
||||
{
|
||||
if (SteeringManager == PathSteering && PathSteering.CurrentPath != null)
|
||||
{
|
||||
abandon = PathSteering.CurrentPath.Unreachable;
|
||||
}
|
||||
}
|
||||
if (abandon)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {(Target != null ? Target.ToString() : TargetPos.ToString())}", Color.Yellow);
|
||||
#endif
|
||||
if (objectiveManager.CurrentOrder != null)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogCannotReach"), identifier: "cannotreach", minDurationBetweenSimilar: 10.0f);
|
||||
}
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AIController.SteeringManager.SteeringSeek(currTargetPos);
|
||||
if (getDivingGearIfNeeded)
|
||||
{
|
||||
if (targetIsOutside ||
|
||||
Target is Hull h && HumanAIController.NeedsDivingGear(h) ||
|
||||
Target is Item i && HumanAIController.NeedsDivingGear(i.CurrentHull) ||
|
||||
Target is Character c && HumanAIController.NeedsDivingGear(c.CurrentHull))
|
||||
var targetHull = Target is Hull h ? h : Target is Item i ? i.CurrentHull : Target is Character c ? c.CurrentHull : character.CurrentHull;
|
||||
bool needsDivingGear = HumanAIController.NeedsDivingGear(targetHull);
|
||||
bool needsDivingSuit = needsDivingGear && (targetIsOutside || targetHull.WaterPercentage > 90);
|
||||
bool needsEquipment = false;
|
||||
if (needsDivingSuit)
|
||||
{
|
||||
if (findDivingGear == null)
|
||||
{
|
||||
findDivingGear = new AIObjectiveFindDivingGear(character, true);
|
||||
AddSubObjective(findDivingGear);
|
||||
}
|
||||
else if (!findDivingGear.CanBeCompleted)
|
||||
{
|
||||
abandon = true;
|
||||
}
|
||||
needsEquipment = !HumanAIController.HasDivingSuit(character);
|
||||
}
|
||||
else if (needsDivingGear)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingMask(character);
|
||||
}
|
||||
if (needsEquipment)
|
||||
{
|
||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,40 +177,30 @@ namespace Barotrauma
|
||||
{
|
||||
if (repeat) { return false; }
|
||||
bool completed = false;
|
||||
|
||||
if (Target is Item item)
|
||||
{
|
||||
if (item.IsInsideTrigger(character.WorldPosition)) completed = true;
|
||||
if (item.IsInsideTrigger(character.WorldPosition)) { completed = true; }
|
||||
}
|
||||
else if (Target is Character targetCharacter)
|
||||
{
|
||||
if (character.CanInteractWith(targetCharacter)) completed = true;
|
||||
if (character.CanInteractWith(targetCharacter)) { completed = true; }
|
||||
}
|
||||
|
||||
completed = completed || Vector2.DistanceSquared(Target != null ? Target.SimPosition : targetPos, character.SimPosition) < CloseEnough * CloseEnough;
|
||||
|
||||
if (completed) character.AIController.SteeringManager.Reset();
|
||||
|
||||
if (completed) { character.AIController.SteeringManager.Reset(); }
|
||||
return completed;
|
||||
}
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
AIObjectiveGoTo objective = otherObjective as AIObjectiveGoTo;
|
||||
if (objective == null) return false;
|
||||
|
||||
if (objective.Target == Target) return true;
|
||||
if (!(otherObjective is AIObjectiveGoTo objective)) { return false; }
|
||||
if (objective.Target == Target) { return true; }
|
||||
return objective.targetPos == targetPos;
|
||||
}
|
||||
|
||||
private void CalculateCloseEnough()
|
||||
{
|
||||
float interactionDistance = Target is Item i ? ConvertUnits.ToSimUnits(i.InteractDistance * 0.9f) : 0;
|
||||
CloseEnough = Math.Max(interactionDistance, CloseEnough);
|
||||
}
|
||||
|
||||
private void CalculateCloseEnough()
|
||||
{
|
||||
float interactionDistance = Target is Item i ? ConvertUnits.ToSimUnits(i.InteractDistance) : 0;
|
||||
CloseEnough = Math.Max(interactionDistance, CloseEnough);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Barotrauma
|
||||
private readonly List<Hull> targetHulls = new List<Hull>(20);
|
||||
private readonly List<float> hullWeights = new List<float>(20);
|
||||
|
||||
public void SetRandom()
|
||||
public AIObjectiveIdle(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
standStillTimer = Rand.Range(-10.0f, 10.0f);
|
||||
walkDuration = Rand.Range(0.0f, 10.0f);
|
||||
@@ -38,19 +38,25 @@ namespace Barotrauma
|
||||
public override bool IsCompleted() => false;
|
||||
public override bool CanBeCompleted => true;
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
public override bool IsLoop { get => true; set => throw new System.Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace); }
|
||||
|
||||
private float randomTimer;
|
||||
private float randomUpdateInterval = 5;
|
||||
public float Random { get; private set; }
|
||||
|
||||
public void SetRandom()
|
||||
{
|
||||
if (objectiveManager.CurrentObjective == this)
|
||||
{
|
||||
if (randomTimer > 0)
|
||||
{
|
||||
randomTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
SetRandom();
|
||||
}
|
||||
}
|
||||
Random = Rand.Range(0.5f, 1.5f);
|
||||
randomTimer = randomUpdateInterval;
|
||||
}
|
||||
|
||||
public override float GetPriority()
|
||||
{
|
||||
return 1;
|
||||
float max = Math.Min(Math.Min(AIObjectiveManager.RunPriority, AIObjectiveManager.OrderPriority) - 1, 100);
|
||||
float initiative = character.GetSkillLevel("initiative");
|
||||
Priority = MathHelper.Lerp(1, max, MathUtils.InverseLerp(100, 0, initiative * Random));
|
||||
return Priority;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
@@ -78,7 +84,7 @@ namespace Barotrauma
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (PathSteering == null) return;
|
||||
if (PathSteering == null) { return; }
|
||||
|
||||
//don't keep dragging others when idling
|
||||
if (character.SelectedCharacter != null)
|
||||
@@ -90,17 +96,10 @@ namespace Barotrauma
|
||||
character.SelectedConstruction = null;
|
||||
}
|
||||
|
||||
bool currentHullForbidden = IsForbidden(character.CurrentHull);
|
||||
if (!currentHullForbidden && !character.AnimController.InWater && !character.IsClimbing && HumanAIController.ObjectiveManager.WaitTimer > 0)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) ||
|
||||
(PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)));
|
||||
|
||||
if (currentTargetIsInvalid || (currentTarget == null && currentHullForbidden))
|
||||
if (currentTargetIsInvalid || currentTarget == null && IsForbidden(character.CurrentHull))
|
||||
{
|
||||
newTargetTimer = 0;
|
||||
standStillTimer = 0;
|
||||
@@ -137,7 +136,6 @@ namespace Barotrauma
|
||||
{
|
||||
//choose a random available hull
|
||||
var randomHull = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced);
|
||||
|
||||
bool isCurrentHullOK = !HumanAIController.UnsafeHulls.Contains(character.CurrentHull) && !IsForbidden(character.CurrentHull);
|
||||
if (isCurrentHullOK)
|
||||
{
|
||||
@@ -145,7 +143,8 @@ namespace Barotrauma
|
||||
// Only do this when the current hull is ok, because otherwise would block all paths from the current hull to the target hull.
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, randomHull.SimPosition);
|
||||
if (path.Unreachable ||
|
||||
path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) || IsForbidden(n.CurrentHull)))
|
||||
path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) ||
|
||||
IsForbidden(n.CurrentHull)))
|
||||
{
|
||||
//can't go to this room, remove it from the list and try another room next frame
|
||||
int index = targetHulls.IndexOf(randomHull);
|
||||
@@ -268,7 +267,6 @@ namespace Barotrauma
|
||||
|
||||
private void FindTargetHulls()
|
||||
{
|
||||
bool isCurrentHullOK = !HumanAIController.UnsafeHulls.Contains(character.CurrentHull) && !IsForbidden(character.CurrentHull);
|
||||
targetHulls.Clear();
|
||||
hullWeights.Clear();
|
||||
foreach (var hull in Hull.hullList)
|
||||
@@ -300,7 +298,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsForbidden(Hull hull)
|
||||
public static bool IsForbidden(Hull hull)
|
||||
{
|
||||
if (hull == null) { return true; }
|
||||
string hullName = hull.RoomName?.ToLowerInvariant();
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Barotrauma
|
||||
{
|
||||
abstract class AIObjectiveLoop<T> : AIObjective
|
||||
{
|
||||
protected List<T> targets = new List<T>();
|
||||
protected HashSet<T> targets = new HashSet<T>();
|
||||
protected Dictionary<T, AIObjective> objectives = new Dictionary<T, AIObjective>();
|
||||
protected HashSet<T> ignoreList = new HashSet<T>();
|
||||
private float ignoreListTimer;
|
||||
@@ -17,7 +17,24 @@ namespace Barotrauma
|
||||
protected virtual float IgnoreListClearInterval => 0;
|
||||
protected virtual float TargetUpdateInterval => 2;
|
||||
|
||||
public AIObjectiveLoop(Character character, string option) : base(character, option)
|
||||
public HashSet<T> ReportedTargets { get; private set; } = new HashSet<T>();
|
||||
|
||||
public bool AddTarget(T target)
|
||||
{
|
||||
if (ReportedTargets.Contains(target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (Filter(target))
|
||||
{
|
||||
ReportedTargets.Add(target);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public AIObjectiveLoop(Character character, AIObjectiveManager objectiveManager, float priorityModifier, string option = null)
|
||||
: base(character, objectiveManager, priorityModifier, option)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
@@ -26,9 +43,11 @@ namespace Barotrauma
|
||||
public override bool IsCompleted() => false;
|
||||
public override bool CanBeCompleted => true;
|
||||
|
||||
public override void Update(AIObjectiveManager objectiveManager, float deltaTime)
|
||||
public override bool IsLoop { get => true; set => throw new System.Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace); }
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(objectiveManager, deltaTime);
|
||||
base.Update(deltaTime);
|
||||
if (IgnoreListClearInterval > 0)
|
||||
{
|
||||
if (ignoreListTimer > IgnoreListClearInterval)
|
||||
@@ -79,15 +98,29 @@ namespace Barotrauma
|
||||
|
||||
public override void OnSelected()
|
||||
{
|
||||
Reset();
|
||||
base.OnSelected();
|
||||
if (HumanAIController.ObjectiveManager.CurrentOrder == this)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
public override float GetPriority()
|
||||
{
|
||||
if (character.Submarine == null) { return 0; }
|
||||
if (targets.None()) { return 0; }
|
||||
float avg = targets.Average(t => Average(t));
|
||||
return MathHelper.Lerp(0, AIObjectiveManager.OrderPriority + 20, avg / 100);
|
||||
// Allow the target value to be more than 100.
|
||||
float targetValue = TargetEvaluation();
|
||||
// If the target value is less than 1% of the max value, let's just treat it as zero.
|
||||
if (targetValue < 1) { return 0; }
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
|
||||
float devotion = MathHelper.Min(10, Priority);
|
||||
float value = MathHelper.Clamp((devotion + targetValue * PriorityModifier) / 100, 0, 1);
|
||||
return MathHelper.Lerp(0, max, value);
|
||||
}
|
||||
|
||||
protected void UpdateTargets()
|
||||
@@ -99,12 +132,19 @@ namespace Barotrauma
|
||||
|
||||
protected virtual void FindTargets()
|
||||
{
|
||||
foreach (T item in GetList())
|
||||
foreach (T target in GetList())
|
||||
{
|
||||
if (Filter(item)) { continue; }
|
||||
if (!targets.Contains(item))
|
||||
// The bots always find targets when the objective is an order.
|
||||
if (objectiveManager.CurrentOrder != this)
|
||||
{
|
||||
targets.Add(item);
|
||||
// Battery or pump states cannot currently be reported (not implemented) and therefore we must ignore them -> the bots always know if they require attention.
|
||||
bool ignore = this is AIObjectiveChargeBatteries || this is AIObjectivePumpWater;
|
||||
if (!ignore && !ReportedTargets.Contains(target)) { continue; }
|
||||
}
|
||||
if (!Filter(target)) { continue; }
|
||||
if (!ignoreList.Contains(target))
|
||||
{
|
||||
targets.Add(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,6 +156,7 @@ namespace Barotrauma
|
||||
if (!objectives.TryGetValue(target, out AIObjective objective))
|
||||
{
|
||||
objective = ObjectiveConstructor(target);
|
||||
objective.Completed += () => ReportedTargets.Remove(target);
|
||||
objectives.Add(target, objective);
|
||||
AddSubObjective(objective);
|
||||
}
|
||||
@@ -126,7 +167,9 @@ namespace Barotrauma
|
||||
/// List of all possible items of the specified type. Used for filtering the removed objectives.
|
||||
/// </summary>
|
||||
protected abstract IEnumerable<T> GetList();
|
||||
protected abstract float Average(T target);
|
||||
|
||||
protected abstract float TargetEvaluation();
|
||||
|
||||
protected abstract AIObjective ObjectiveConstructor(T target);
|
||||
protected abstract bool Filter(T target);
|
||||
}
|
||||
|
||||
@@ -9,27 +9,29 @@ namespace Barotrauma
|
||||
class AIObjectiveManager
|
||||
{
|
||||
// TODO: expose
|
||||
public const float OrderPriority = 50.0f;
|
||||
public const float OrderPriority = 70;
|
||||
public const float RunPriority = 50;
|
||||
// Constantly increases the priority of the selected objective, unless overridden
|
||||
public const float baseDevotion = 2;
|
||||
|
||||
public List<AIObjective> Objectives { get; private set; }
|
||||
public List<AIObjective> Objectives { get; private set; } = new List<AIObjective>();
|
||||
|
||||
private Character character;
|
||||
|
||||
/// <summary>
|
||||
/// When set above zero, the character will stand still doing nothing until the timer runs out. Only affects idling.
|
||||
/// When set above zero, the character will stand still doing nothing until the timer runs out. Does not affect orders.
|
||||
/// </summary>
|
||||
public float WaitTimer;
|
||||
|
||||
public AIObjective CurrentOrder { get; private set; }
|
||||
public AIObjective CurrentObjective { get; private set; }
|
||||
|
||||
public bool IsCurrentObjective<T>() where T : AIObjective => CurrentObjective is T;
|
||||
|
||||
public AIObjectiveManager(Character character)
|
||||
{
|
||||
this.character = character;
|
||||
|
||||
Objectives = new List<AIObjective>();
|
||||
CreateAutonomousObjectives();
|
||||
}
|
||||
|
||||
public void AddObjective(AIObjective objective)
|
||||
@@ -46,6 +48,30 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public Dictionary<AIObjective, CoroutineHandle> DelayedObjectives { get; private set; } = new Dictionary<AIObjective, CoroutineHandle>();
|
||||
|
||||
public void CreateAutonomousObjectives()
|
||||
{
|
||||
Objectives.Clear();
|
||||
AddObjective(new AIObjectiveFindSafety(character, this), delay: Rand.Value() / 2);
|
||||
AddObjective(new AIObjectiveIdle(character, this), delay: Rand.Value() / 2);
|
||||
int objectiveCount = Objectives.Count;
|
||||
foreach (var automaticOrder in character.Info.Job.Prefab.AutomaticOrders)
|
||||
{
|
||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == automaticOrder.aiTag);
|
||||
if (orderPrefab == null) { throw new Exception("Could not find a matching prefab by ai tag: " + automaticOrder.aiTag); }
|
||||
// TODO: Similar code is used in CrewManager:815-> DRY
|
||||
var matchingItems = orderPrefab.ItemIdentifiers.Any() ?
|
||||
Item.ItemList.FindAll(it => orderPrefab.ItemIdentifiers.Contains(it.Prefab.Identifier) || it.HasTag(orderPrefab.ItemIdentifiers)) :
|
||||
Item.ItemList.FindAll(it => it.Components.Any(ic => ic.GetType() == orderPrefab.ItemComponentType));
|
||||
matchingItems.RemoveAll(it => it.Submarine != character.Submarine);
|
||||
var item = matchingItems.GetRandom();
|
||||
var order = new Order(orderPrefab, item ?? character.CurrentHull as Entity, item?.Components.FirstOrDefault(ic => ic.GetType() == orderPrefab.ItemComponentType));
|
||||
AddObjective(CreateObjective(order, automaticOrder.option, character, automaticOrder.priorityModifier), delay: Rand.Value() / 2);
|
||||
objectiveCount++;
|
||||
}
|
||||
WaitTimer = Math.Max(WaitTimer, Rand.Range(0.5f, 1f) * objectiveCount);
|
||||
}
|
||||
|
||||
public void AddObjective(AIObjective objective, float delay, Action callback = null)
|
||||
{
|
||||
if (DelayedObjectives.TryGetValue(objective, out CoroutineHandle coroutine))
|
||||
@@ -75,7 +101,7 @@ namespace Barotrauma
|
||||
{
|
||||
var previousObjective = CurrentObjective;
|
||||
var firstObjective = Objectives.FirstOrDefault();
|
||||
if (CurrentOrder != null && firstObjective != null && CurrentOrder.GetPriority(this) > firstObjective.GetPriority(this))
|
||||
if (CurrentOrder != null && firstObjective != null && CurrentOrder.GetPriority() > firstObjective.GetPriority())
|
||||
{
|
||||
CurrentObjective = CurrentOrder;
|
||||
}
|
||||
@@ -86,18 +112,24 @@ namespace Barotrauma
|
||||
if (previousObjective != CurrentObjective)
|
||||
{
|
||||
CurrentObjective?.OnSelected();
|
||||
GetObjective<AIObjectiveIdle>()?.SetRandom();
|
||||
}
|
||||
return CurrentObjective;
|
||||
}
|
||||
|
||||
public float GetCurrentPriority()
|
||||
{
|
||||
return CurrentObjective == null ? 0.0f : CurrentObjective.GetPriority(this);
|
||||
return CurrentObjective == null ? 0.0f : CurrentObjective.GetPriority();
|
||||
}
|
||||
|
||||
public void UpdateObjectives(float deltaTime)
|
||||
{
|
||||
CurrentOrder?.Update(this, deltaTime);
|
||||
CurrentOrder?.Update(deltaTime);
|
||||
if (WaitTimer > 0)
|
||||
{
|
||||
WaitTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < Objectives.Count; i++)
|
||||
{
|
||||
var objective = Objectives[i];
|
||||
@@ -117,7 +149,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
objective.Update(this, deltaTime);
|
||||
objective.Update(deltaTime);
|
||||
}
|
||||
}
|
||||
GetCurrentObjective();
|
||||
@@ -127,15 +159,41 @@ namespace Barotrauma
|
||||
{
|
||||
if (Objectives.Any())
|
||||
{
|
||||
Objectives.Sort((x, y) => y.GetPriority(this).CompareTo(x.GetPriority(this)));
|
||||
Objectives.Sort((x, y) => y.GetPriority().CompareTo(x.GetPriority()));
|
||||
}
|
||||
CurrentObjective?.SortSubObjectives(this);
|
||||
CurrentObjective?.SortSubObjectives();
|
||||
}
|
||||
|
||||
public void DoCurrentObjective(float deltaTime)
|
||||
{
|
||||
if (WaitTimer > 0.0f) { WaitTimer -= deltaTime; }
|
||||
CurrentObjective?.TryComplete(deltaTime);
|
||||
if (WaitTimer <= 0)
|
||||
{
|
||||
CurrentObjective?.TryComplete(deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (CurrentOrder != null)
|
||||
{
|
||||
CurrentOrder.TryComplete(deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (character.AIController is HumanAIController humanAI && humanAI.SteeringManager != null)
|
||||
{
|
||||
if (!character.AnimController.InWater &&
|
||||
!character.IsClimbing &&
|
||||
!humanAI.UnsafeHulls.Contains(character.CurrentHull) &&
|
||||
!AIObjectiveIdle.IsForbidden(character.CurrentHull))
|
||||
{
|
||||
humanAI.SteeringManager.Reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentObjective?.TryComplete(deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetOrder(AIObjective objective)
|
||||
@@ -145,13 +203,22 @@ namespace Barotrauma
|
||||
|
||||
public void SetOrder(Order order, string option, Character orderGiver)
|
||||
{
|
||||
CurrentOrder = null;
|
||||
if (order == null) return;
|
||||
CurrentOrder = CreateObjective(order, option, orderGiver);
|
||||
if (CurrentOrder == null)
|
||||
{
|
||||
// Recreate objectives, because some of them may be removed, if impossible to complete (e.g. due to path finding)
|
||||
CreateAutonomousObjectives();
|
||||
}
|
||||
}
|
||||
|
||||
public AIObjective CreateObjective(Order order, string option, Character orderGiver, float priorityModifier = 1)
|
||||
{
|
||||
if (order == null) { return null; }
|
||||
AIObjective newObjective;
|
||||
switch (order.AITag.ToLowerInvariant())
|
||||
{
|
||||
case "follow":
|
||||
CurrentOrder = new AIObjectiveGoTo(orderGiver, character, true)
|
||||
newObjective = new AIObjectiveGoTo(orderGiver, character, this, repeat: true, priorityModifier: priorityModifier)
|
||||
{
|
||||
CloseEnough = 1.5f,
|
||||
AllowGoingOutside = true,
|
||||
@@ -160,38 +227,41 @@ namespace Barotrauma
|
||||
};
|
||||
break;
|
||||
case "wait":
|
||||
CurrentOrder = new AIObjectiveGoTo(character, character, true)
|
||||
newObjective = new AIObjectiveGoTo(character, character, this, repeat: true, priorityModifier: priorityModifier)
|
||||
{
|
||||
AllowGoingOutside = true
|
||||
};
|
||||
break;
|
||||
case "fixleaks":
|
||||
CurrentOrder = new AIObjectiveFixLeaks(character);
|
||||
newObjective = new AIObjectiveFixLeaks(character, this, priorityModifier);
|
||||
break;
|
||||
case "chargebatteries":
|
||||
CurrentOrder = new AIObjectiveChargeBatteries(character, option);
|
||||
newObjective = new AIObjectiveChargeBatteries(character, this, option, priorityModifier);
|
||||
break;
|
||||
case "rescue":
|
||||
CurrentOrder = new AIObjectiveRescueAll(character);
|
||||
newObjective = new AIObjectiveRescueAll(character, this, priorityModifier);
|
||||
break;
|
||||
case "repairsystems":
|
||||
CurrentOrder = new AIObjectiveRepairItems(character) { RequireAdequateSkills = option != "all" };
|
||||
newObjective = new AIObjectiveRepairItems(character, this, priorityModifier) { RequireAdequateSkills = option != "all" };
|
||||
break;
|
||||
case "pumpwater":
|
||||
CurrentOrder = new AIObjectivePumpWater(character, option);
|
||||
newObjective = new AIObjectivePumpWater(character, this, option, priorityModifier: priorityModifier);
|
||||
break;
|
||||
case "extinguishfires":
|
||||
CurrentOrder = new AIObjectiveExtinguishFires(character);
|
||||
newObjective = new AIObjectiveExtinguishFires(character, this, priorityModifier);
|
||||
break;
|
||||
case "fightintruders":
|
||||
newObjective = new AIObjectiveFightIntruders(character, this, priorityModifier);
|
||||
break;
|
||||
case "steer":
|
||||
var steering = (order?.TargetEntity as Item)?.GetComponent<Steering>();
|
||||
if (steering != null) steering.PosToMaintain = steering.Item.Submarine?.WorldPosition;
|
||||
if (order.TargetItemComponent == null) return;
|
||||
CurrentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController);
|
||||
if (order.TargetItemComponent == null) { return null; }
|
||||
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, requireEquip: false, useController: order.UseController, priorityModifier: priorityModifier) { IsLoop = true };
|
||||
break;
|
||||
default:
|
||||
if (order.TargetItemComponent == null) return;
|
||||
CurrentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController);
|
||||
if (order.TargetItemComponent == null) { return null; }
|
||||
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, requireEquip: false, useController: order.UseController, priorityModifier: priorityModifier) { IsLoop = true };
|
||||
break;
|
||||
}
|
||||
return newObjective;
|
||||
|
||||
+28
-41
@@ -10,8 +10,6 @@ namespace Barotrauma
|
||||
{
|
||||
public override string DebugTag => "operate item";
|
||||
|
||||
private ItemComponent component, controller;
|
||||
|
||||
private ItemComponent component, controller;
|
||||
private Entity operateTarget;
|
||||
private bool isCompleted;
|
||||
@@ -20,35 +18,25 @@ namespace Barotrauma
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private AIObjectiveGetItem getItemObjective;
|
||||
|
||||
private bool useController;
|
||||
|
||||
private AIObjectiveGoTo gotoObjective;
|
||||
|
||||
public override bool CanBeCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
if (gotoObjective != null && !gotoObjective.CanBeCompleted) return false;
|
||||
|
||||
if (useController && controller == null) return false;
|
||||
|
||||
return canBeCompleted;
|
||||
}
|
||||
}
|
||||
public override bool CanBeCompleted => base.CanBeCompleted && (!useController || controller != null);
|
||||
|
||||
public Entity OperateTarget => operateTarget;
|
||||
public ItemComponent Component => component;
|
||||
|
||||
public ItemComponent Component => component;
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
public override float GetPriority()
|
||||
{
|
||||
if (gotoObjective != null && !gotoObjective.CanBeCompleted) { return 0; }
|
||||
if (component.Item.ConditionPercentage <= 0) { return 0; }
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
return base.GetPriority(objectiveManager);
|
||||
if (component.Item.CurrentHull == null) { return 0; }
|
||||
if (component.Item.CurrentHull.FireSources.Count > 0) { return 0; }
|
||||
if (Character.CharacterList.Any(c => c.CurrentHull == component.Item.CurrentHull && !HumanAIController.IsFriendly(c))) { return 0; }
|
||||
float devotion = MathHelper.Min(10, Priority);
|
||||
float value = devotion + AIObjectiveManager.OrderPriority * PriorityModifier;
|
||||
float max = MathHelper.Min((AIObjectiveManager.OrderPriority - 1), 90);
|
||||
return MathHelper.Clamp(value, 0, max);
|
||||
}
|
||||
|
||||
public AIObjectiveOperateItem(ItemComponent item, Character character, AIObjectiveManager objectiveManager, string option, bool requireEquip, Entity operateTarget = null, bool useController = false, float priorityModifier = 1)
|
||||
@@ -58,34 +46,31 @@ namespace Barotrauma
|
||||
this.requireEquip = requireEquip;
|
||||
this.operateTarget = operateTarget;
|
||||
this.useController = useController;
|
||||
|
||||
if (useController)
|
||||
{
|
||||
var controllers = component.Item.GetConnectedComponents<Controller>();
|
||||
if (controllers.Any()) controller = controllers[0];
|
||||
//try finding the controller with the simpler non-recursive method first
|
||||
controller =
|
||||
component.Item.GetConnectedComponents<Controller>().FirstOrDefault() ??
|
||||
component.Item.GetConnectedComponents<Controller>(recursive: true).FirstOrDefault();
|
||||
}
|
||||
|
||||
canBeCompleted = true;
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
ItemComponent target = useController ? controller : component;
|
||||
|
||||
if (useController && controller == null)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogCantFindController").Replace("[item]", component.Item.Name), null, 2.0f, "cantfindcontroller", 30.0f);
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (target.CanBeSelected)
|
||||
{
|
||||
bool inSameRoom = character.CurrentHull == target.Item.CurrentHull;
|
||||
bool withinReach = target.Item.IsInsideTrigger(character.WorldPosition) || Vector2.DistanceSquared(character.Position, target.Item.Position) < MathUtils.Pow(target.Item.InteractDistance, 2);
|
||||
if (inSameRoom && withinReach)
|
||||
{
|
||||
if (character.CurrentHull == target.Item.CurrentHull)
|
||||
if (character.SelectedConstruction != target.Item)
|
||||
{
|
||||
if (character.SelectedConstruction != target.Item && target.CanBeSelected)
|
||||
{
|
||||
@@ -97,16 +82,22 @@ namespace Barotrauma
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
isCompleted = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(target.Item, character, objectiveManager));
|
||||
}
|
||||
|
||||
AddSubObjective(gotoObjective = new AIObjectiveGoTo(target.Item, character));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (component.Item.GetComponent<Pickable>() == null)
|
||||
{
|
||||
//controller/target can't be selected and the item cannot be picked -> objective can't be completed
|
||||
canBeCompleted = false;
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
else if (!character.Inventory.Items.Contains(component.Item))
|
||||
@@ -126,11 +117,9 @@ namespace Barotrauma
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < character.Inventory.Capacity; i++)
|
||||
{
|
||||
if (character.Inventory.SlotTypes[i] == InvSlotType.Any ||
|
||||
!holdable.AllowedSlots.Any(s => s.HasFlag(character.Inventory.SlotTypes[i])))
|
||||
if (character.Inventory.SlotTypes[i] == InvSlotType.Any || !holdable.AllowedSlots.Any(s => s.HasFlag(character.Inventory.SlotTypes[i])))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -164,10 +153,8 @@ namespace Barotrauma
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
AIObjectiveOperateItem operateItem = otherObjective as AIObjectiveOperateItem;
|
||||
if (operateItem == null) return false;
|
||||
|
||||
return (operateItem.component == component ||otherObjective.Option == Option);
|
||||
if (!(otherObjective is AIObjectiveOperateItem operateItem)) { return false; }
|
||||
return (operateItem.component == component || otherObjective.Option == Option);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+53
-47
@@ -1,68 +1,74 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectivePumpWater : AIObjectiveLoop<Pump>
|
||||
{
|
||||
public override string DebugTag => "pump water";
|
||||
public override bool KeepDivingGearOn => true;
|
||||
private readonly IEnumerable<Pump> pumpList;
|
||||
private IEnumerable<Pump> pumpList;
|
||||
|
||||
public AIObjectivePumpWater(Character character, string option) : base(character, option)
|
||||
{
|
||||
pumpList = character.Submarine.GetItems(true).Select(i => i.GetComponent<Pump>()).Where(p => p != null);
|
||||
}
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
{
|
||||
if (character.Submarine == null) { return 0; }
|
||||
if (objectiveManager.CurrentOrder == this && targets.Count > 0)
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
public AIObjectivePumpWater(Character character, AIObjectiveManager objectiveManager, string option, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier, option) { }
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectivePumpWater && otherObjective.Option == Option;
|
||||
|
||||
//availablePumps = allPumps.Where(p => !p.Item.HasTag("ballast") && p.Item.Connections.None(c => c.IsPower && p.Item.GetConnectedComponentsRecursive<Steering>(c).None())).ToList();
|
||||
protected override void FindTargets()
|
||||
{
|
||||
if (option == null) { return; }
|
||||
foreach (Item item in Item.ItemList)
|
||||
if (Option == null) { return; }
|
||||
base.FindTargets();
|
||||
if (targets.None() && objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
if (item.HasTag("ballast")) { continue; }
|
||||
if (item.Submarine == null) { continue; }
|
||||
if (item.Submarine.TeamID != character.TeamID) { continue; }
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; }
|
||||
var pump = item.GetComponent<Pump>();
|
||||
if (pump != null)
|
||||
{
|
||||
if (!ignoreList.Contains(pump))
|
||||
{
|
||||
if (option == "stoppumping")
|
||||
{
|
||||
if (!pump.IsActive || pump.FlowPercentage == 0.0f) { continue; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!pump.Item.InWater) { continue; }
|
||||
if (pump.IsActive && pump.FlowPercentage <= -90.0f) { continue; }
|
||||
}
|
||||
if (!targets.Contains(pump))
|
||||
{
|
||||
targets.Add(pump);
|
||||
}
|
||||
}
|
||||
}
|
||||
character.Speak(TextManager.Get("DialogNoPumps"), null, 3.0f, "nopumps", 30.0f);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool Filter(Pump pump) => true;
|
||||
protected override IEnumerable<Pump> GetList() => pumpList;
|
||||
protected override AIObjective ObjectiveConstructor(Pump pump) => new AIObjectiveOperateItem(pump, character, Option, false);
|
||||
protected override float Average(Pump target) => 0;
|
||||
protected override bool Filter(Pump pump)
|
||||
{
|
||||
if (pump == null) { return false; }
|
||||
if (pump.Item.HasTag("ballast")) { return false; }
|
||||
if (pump.Item.Submarine == null) { return false; }
|
||||
if (pump.Item.CurrentHull == null) { return false; }
|
||||
if (pump.Item.Submarine.TeamID != character.TeamID) { return false; }
|
||||
if (pump.Item.ConditionPercentage <= 0) { return false; }
|
||||
if (pump.Item.CurrentHull.FireSources.Count > 0) { return false; }
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(pump.Item, true)) { return false; }
|
||||
if (Character.CharacterList.Any(c => c.CurrentHull == pump.Item.CurrentHull && !HumanAIController.IsFriendly(c))) { return false; }
|
||||
if (Option == "stoppumping")
|
||||
{
|
||||
if (!pump.IsActive || MathUtils.NearlyEqual(pump.FlowPercentage, 0)) { return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!pump.Item.InWater) { return false; }
|
||||
if (pump.IsActive && pump.FlowPercentage <= -99.9f) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
protected override IEnumerable<Pump> GetList()
|
||||
{
|
||||
if (pumpList == null)
|
||||
{
|
||||
pumpList = character.Submarine.GetItems(true).Select(i => i.GetComponent<Pump>()).Where(p => p != null);
|
||||
}
|
||||
return pumpList;
|
||||
}
|
||||
|
||||
protected override AIObjective ObjectiveConstructor(Pump pump) => new AIObjectiveOperateItem(pump, character, objectiveManager, Option, false) { IsLoop = false };
|
||||
protected override float TargetEvaluation()
|
||||
{
|
||||
if (Option == "stoppumping")
|
||||
{
|
||||
return targets.Max(t => MathHelper.Lerp(0, 100, Math.Abs(t.FlowPercentage / 100)));
|
||||
}
|
||||
else
|
||||
{
|
||||
return targets.Max(t => MathHelper.Lerp(100, 0, Math.Abs(-t.FlowPercentage / 100)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+25
-36
@@ -11,37 +11,33 @@ namespace Barotrauma
|
||||
{
|
||||
public override string DebugTag => "repair item";
|
||||
|
||||
public override bool KeepDivingGearOn => true;
|
||||
|
||||
public Item Item { get; private set; }
|
||||
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
|
||||
private float previousCondition = -1;
|
||||
private RepairTool repairTool;
|
||||
|
||||
public AIObjectiveRepairItem(Character character, Item item) : base(character, "")
|
||||
public AIObjectiveRepairItem(Character character, Item item, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
Item = item;
|
||||
}
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
public override float GetPriority()
|
||||
{
|
||||
// TODO: priority list?
|
||||
if (Item.Repairables.None()) { return 0; }
|
||||
// Ignore items that are being repaired by someone else.
|
||||
if (Item.Repairables.Any(r => r.CurrentFixer != null && r.CurrentFixer != character)) { return 0; }
|
||||
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
|
||||
float dist = Math.Abs(character.WorldPosition.X - Item.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - Item.WorldPosition.Y) * 2.0f;
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.5f, MathUtils.InverseLerp(0, 10000, dist));
|
||||
float damagePriority = MathHelper.Lerp(1, 0, (Item.Condition + 10) / Item.MaxCondition);
|
||||
float damagePriority = MathHelper.Lerp(1, 0, Item.Condition / Item.MaxCondition);
|
||||
float successFactor = MathHelper.Lerp(0, 1, Item.Repairables.Average(r => r.DegreeOfSuccess(character)));
|
||||
float isSelected = character.SelectedConstruction == Item ? 50 : 0;
|
||||
float baseLevel = Math.Max(Priority + isSelected, 1);
|
||||
return MathHelper.Clamp(baseLevel * damagePriority * distanceFactor * successFactor, 0, 100);
|
||||
float devotion = (Math.Min(Priority, 10) + isSelected) / 100;
|
||||
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
|
||||
return MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + damagePriority * distanceFactor * successFactor * PriorityModifier, 0, 1));
|
||||
}
|
||||
|
||||
public override bool CanBeCompleted => !abandon;
|
||||
|
||||
public override bool IsCompleted()
|
||||
{
|
||||
bool isCompleted = Item.IsFullCondition;
|
||||
@@ -59,15 +55,6 @@ namespace Barotrauma
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (goToObjective != null && !subObjectives.Contains(goToObjective))
|
||||
{
|
||||
if (!goToObjective.IsCompleted() && !goToObjective.CanBeCompleted)
|
||||
{
|
||||
abandon = true;
|
||||
character?.Speak(TextManager.Get("DialogCannotRepair").Replace("[itemname]", Item.Name), null, 0.0f, "cannotrepair", 10.0f);
|
||||
}
|
||||
goToObjective = null;
|
||||
}
|
||||
foreach (Repairable repairable in Item.Repairables)
|
||||
{
|
||||
if (!repairable.HasRequiredItems(character, false))
|
||||
@@ -77,12 +64,14 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (RelatedItem requiredItem in kvp.Value)
|
||||
{
|
||||
AddSubObjective(new AIObjectiveGetItem(character, requiredItem.Identifiers, true));
|
||||
AddSubObjective(new AIObjectiveGetItem(character, requiredItem.Identifiers, objectiveManager, true));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Only continue when the get item sub objectives have been completed.
|
||||
if (subObjectives.Any(so => so is AIObjectiveGetItem)) { return; }
|
||||
if (repairTool == null)
|
||||
{
|
||||
FindRepairTool();
|
||||
@@ -114,31 +103,31 @@ namespace Barotrauma
|
||||
{
|
||||
// If the current condition is less than the previous condition, we can't complete the task, so let's abandon it. The item is probably deteriorating at a greater speed than we can repair it.
|
||||
abandon = true;
|
||||
character?.Speak(TextManager.Get("DialogRepairFailed").Replace("[itemname]", Item.Name), null, 0.0f, "repairfailed", 10.0f);
|
||||
character?.Speak(TextManager.Get("DialogCannotRepair").Replace("[itemname]", Item.Name), null, 0.0f, "cannotrepair", 10.0f);
|
||||
}
|
||||
}
|
||||
repairable.CurrentFixer = abandon && repairable.CurrentFixer == character ? null : character;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (goToObjective == null || goToObjective.Target != Item)
|
||||
else
|
||||
{
|
||||
previousCondition = -1;
|
||||
if (goToObjective != null)
|
||||
{
|
||||
subObjectives.Remove(goToObjective);
|
||||
}
|
||||
goToObjective = new AIObjectiveGoTo(Item, character);
|
||||
if (repairTool != null)
|
||||
{
|
||||
//goToObjective.CloseEnough = (HumanAIController.AnimController.ArmLength + ConvertUnits.ToSimUnits(repairTool.Range)) * 0.75f;
|
||||
goToObjective.CloseEnough = ConvertUnits.ToSimUnits(repairTool.Range);
|
||||
}
|
||||
AddSubObjective(goToObjective);
|
||||
// If cannot reach the item, approach it.
|
||||
TryAddSubObjective(ref goToObjective,
|
||||
constructor: () =>
|
||||
{
|
||||
previousCondition = -1;
|
||||
var objective = new AIObjectiveGoTo(Item, character, objectiveManager);
|
||||
if (repairTool != null)
|
||||
{
|
||||
objective.CloseEnough = ConvertUnits.ToSimUnits(repairTool.Range) * 0.75f;
|
||||
}
|
||||
return objective;
|
||||
},
|
||||
onAbandon: () => character.Speak(TextManager.Get("DialogCannotRepair").Replace("[itemname]", Item.Name), null, 0.0f, "cannotrepair", 10.0f));
|
||||
}
|
||||
}
|
||||
|
||||
private RepairTool repairTool;
|
||||
private void FindRepairTool()
|
||||
{
|
||||
foreach (Repairable repairable in Item.Repairables)
|
||||
|
||||
+36
-30
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
@@ -7,18 +8,25 @@ namespace Barotrauma
|
||||
class AIObjectiveRepairItems : AIObjectiveLoop<Item>
|
||||
{
|
||||
public override string DebugTag => "repair items";
|
||||
public override bool KeepDivingGearOn => true;
|
||||
|
||||
/// <summary>
|
||||
/// Should the character only attempt to fix items they have the skills to fix, or any damaged item
|
||||
/// </summary>
|
||||
public bool RequireAdequateSkills;
|
||||
|
||||
public AIObjectiveRepairItems(Character character) : base(character, "") { }
|
||||
public AIObjectiveRepairItems(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
|
||||
|
||||
// TODO: This can allow two active repair items objectives, if RequireAdequateSkills is not at the same value. We don't want that.
|
||||
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveRepairItems repairItems && repairItems.RequireAdequateSkills == RequireAdequateSkills;
|
||||
|
||||
protected override void FindTargets()
|
||||
{
|
||||
base.FindTargets();
|
||||
if (targets.None() && objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNoRepairTargets"), null, 3.0f, "norepairtargets", 30.0f);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void CreateObjectives()
|
||||
{
|
||||
foreach (var item in targets)
|
||||
@@ -38,37 +46,35 @@ namespace Barotrauma
|
||||
|
||||
protected override bool Filter(Item item)
|
||||
{
|
||||
bool ignore = ignoreList.Contains(item) || item.IsFullCondition;
|
||||
if (!ignore)
|
||||
if (!IsValidTarget(item, character)) { return false; }
|
||||
if (item.CurrentHull.FireSources.Count > 0) { return false; }
|
||||
// Don't repair items in rooms that have enemies inside.
|
||||
if (Character.CharacterList.Any(c => c.CurrentHull == item.CurrentHull && !HumanAIController.IsFriendly(c))) { return false; }
|
||||
if (!objectives.ContainsKey(item))
|
||||
{
|
||||
if (item.Submarine == null) { ignore = true; }
|
||||
else if (item.Submarine.TeamID != character.TeamID) { ignore = true; }
|
||||
else if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { ignore = true; }
|
||||
else
|
||||
{
|
||||
if (item.Repairables.None()) { ignore = true; }
|
||||
else
|
||||
{
|
||||
foreach (Repairable repairable in item.Repairables)
|
||||
{
|
||||
if (!objectives.ContainsKey(item) && item.Condition > repairable.ShowRepairUIThreshold)
|
||||
{
|
||||
ignore = true;
|
||||
}
|
||||
else if (RequireAdequateSkills && !repairable.HasRequiredSkills(character))
|
||||
{
|
||||
ignore = true;
|
||||
}
|
||||
if (ignore) { break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (item.Repairables.All(r => item.Condition > r.ShowRepairUIThreshold)) { return false; }
|
||||
}
|
||||
return ignore;
|
||||
if (RequireAdequateSkills)
|
||||
{
|
||||
if (item.Repairables.Any(r => !r.HasRequiredSkills(character))) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override float Average(Item item) => 100 - item.ConditionPercentage;
|
||||
protected override float TargetEvaluation() => targets.Max(t => 100 - t.ConditionPercentage);
|
||||
protected override IEnumerable<Item> GetList() => Item.ItemList;
|
||||
protected override AIObjective ObjectiveConstructor(Item item) => new AIObjectiveRepairItem(character, item);
|
||||
protected override AIObjective ObjectiveConstructor(Item item) => new AIObjectiveRepairItem(character, item, objectiveManager, PriorityModifier);
|
||||
|
||||
public static bool IsValidTarget(Item item, Character character)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
if (item.IsFullCondition) { return false; }
|
||||
if (item.CurrentHull == null) { return false; }
|
||||
if (item.Submarine == null) { return false; }
|
||||
if (item.Submarine.TeamID != character.TeamID) { return false; }
|
||||
if (item.Repairables.None()) { return false; }
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { return false; }
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ namespace Barotrauma
|
||||
{
|
||||
public override string DebugTag => "rescue";
|
||||
public override bool ForceRun => true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
|
||||
const float TreatmentDelay = 0.5f;
|
||||
|
||||
@@ -21,26 +20,11 @@ namespace Barotrauma
|
||||
|
||||
private float treatmentTimer;
|
||||
|
||||
public override bool CanBeCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
if (targetCharacter.Removed ||
|
||||
targetCharacter.IsDead ||
|
||||
targetCharacter.Vitality / targetCharacter.MaxVitality > AIObjectiveRescueAll.VitalityThreshold)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (goToObjective != null && !goToObjective.CanBeCompleted) { return false; }
|
||||
public override bool CanBeCompleted => base.CanBeCompleted && AIObjectiveRescueAll.IsValidTarget(targetCharacter, character);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public AIObjectiveRescue(Character character, Character targetCharacter)
|
||||
: base(character, "")
|
||||
public AIObjectiveRescue(Character character, Character targetCharacter, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
Debug.Assert(character != targetCharacter);
|
||||
this.targetCharacter = targetCharacter;
|
||||
}
|
||||
|
||||
@@ -52,14 +36,19 @@ namespace Barotrauma
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
//target in water -> move to a dry place first
|
||||
if (targetCharacter.AnimController.InWater)
|
||||
// Target is not in a safe place -> Move to a safe place first
|
||||
if (HumanAIController.GetHullSafety(targetCharacter.CurrentHull, targetCharacter) < HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
if (character.SelectedCharacter != targetCharacter)
|
||||
{
|
||||
// Go to the target and select it
|
||||
if (!character.CanInteractWith(targetCharacter))
|
||||
{
|
||||
AddSubObjective(goToObjective = new AIObjectiveGoTo(targetCharacter, character));
|
||||
if (goToObjective != null && goToObjective.Target != targetCharacter)
|
||||
{
|
||||
goToObjective = null;
|
||||
}
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -68,15 +57,28 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
AddSubObjective(new AIObjectiveFindSafety(character));
|
||||
// Drag the character into safety
|
||||
if (goToObjective != null && goToObjective.Target == targetCharacter)
|
||||
{
|
||||
goToObjective = null;
|
||||
}
|
||||
var findSafety = objectiveManager.GetObjective<AIObjectiveFindSafety>();
|
||||
if (findSafety == null)
|
||||
{
|
||||
// Ensure that we have the find safety objective (should always be the case)
|
||||
objectiveManager.AddObjective(new AIObjectiveFindSafety(character, objectiveManager));
|
||||
}
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(findSafety.FindBestHull(), character, objectiveManager));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//target not in water -> we can start applying treatment
|
||||
if (subObjectives.Any()) { return; }
|
||||
|
||||
// We can start applying treatment
|
||||
if (!character.CanInteractWith(targetCharacter))
|
||||
{
|
||||
AddSubObjective(goToObjective = new AIObjectiveGoTo(targetCharacter, character));
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -87,31 +89,12 @@ namespace Barotrauma
|
||||
null, 1.0f,
|
||||
"foundunconscioustarget" + targetCharacter.Name, 60.0f);
|
||||
}
|
||||
|
||||
character.SelectCharacter(targetCharacter);
|
||||
GiveTreatment(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool ShouldInterruptSubObjective(AIObjective subObjective)
|
||||
{
|
||||
if (subObjective is AIObjectiveFindSafety)
|
||||
{
|
||||
if (character.SelectedCharacter != targetCharacter) return true;
|
||||
if (character.AnimController.InWater || targetCharacter.AnimController.InWater) return false;
|
||||
|
||||
foreach (Limb limb in targetCharacter.AnimController.Limbs)
|
||||
{
|
||||
if (!Submarine.RectContains(targetCharacter.CurrentHull.WorldRect, limb.WorldPosition)) return false;
|
||||
}
|
||||
|
||||
return !character.AnimController.InWater && !targetCharacter.AnimController.InWater &&
|
||||
HumanAIController.GetHullSafety(character.CurrentHull, character) > HumanAIController.HULL_SAFETY_THRESHOLD;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: consider optimizing a bit
|
||||
private void GiveTreatment(float deltaTime)
|
||||
{
|
||||
if (treatmentTimer > 0.0f)
|
||||
@@ -128,7 +111,6 @@ namespace Barotrauma
|
||||
{
|
||||
return Math.Sign(a2.GetVitalityDecrease(targetCharacter.CharacterHealth) - a1.GetVitalityDecrease(targetCharacter.CharacterHealth));
|
||||
});
|
||||
|
||||
//check if we already have a suitable treatment for any of the afflictions
|
||||
foreach (Affliction affliction in allAfflictions)
|
||||
{
|
||||
@@ -143,7 +125,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//didn't have any suitable treatments available, try to find some medical items
|
||||
HashSet<string> suitableItemIdentifiers = new HashSet<string>();
|
||||
foreach (Affliction affliction in allAfflictions)
|
||||
@@ -166,9 +147,8 @@ namespace Barotrauma
|
||||
itemNameList.Add(itemPrefab.Name);
|
||||
}
|
||||
//only list the first 4 items
|
||||
if (itemNameList.Count >= 4) break;
|
||||
if (itemNameList.Count >= 4) { break; }
|
||||
}
|
||||
|
||||
if (itemNameList.Count > 0)
|
||||
{
|
||||
string itemListStr = "";
|
||||
@@ -185,30 +165,29 @@ namespace Barotrauma
|
||||
.Replace("[treatmentlist]", itemListStr),
|
||||
null, 2.0f, "listrequiredtreatments" + targetCharacter.Name, 60.0f);
|
||||
}
|
||||
|
||||
character.DeselectCharacter();
|
||||
AddSubObjective(new AIObjectiveGetItem(character, suitableItemIdentifiers.ToArray(), true));
|
||||
AddSubObjective(new AIObjectiveGetItem(character, suitableItemIdentifiers.ToArray(), objectiveManager, equip: true));
|
||||
}
|
||||
|
||||
character.AnimController.Anim = AnimController.Animation.CPR;
|
||||
}
|
||||
|
||||
private void ApplyTreatment(Affliction affliction, Item item)
|
||||
{
|
||||
var targetLimb = targetCharacter.CharacterHealth.GetAfflictionLimb(affliction);
|
||||
|
||||
bool remove = false;
|
||||
foreach (ItemComponent ic in item.Components)
|
||||
{
|
||||
if (!ic.HasRequiredContainedItems(addMessage: false)) continue;
|
||||
if (!ic.HasRequiredContainedItems(addMessage: false)) { continue; }
|
||||
#if CLIENT
|
||||
ic.PlaySound(ActionType.OnUse, character.WorldPosition, character);
|
||||
#endif
|
||||
ic.WasUsed = true;
|
||||
ic.ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb);
|
||||
if (ic.DeleteOnUse) remove = true;
|
||||
if (ic.DeleteOnUse)
|
||||
{
|
||||
remove = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (remove)
|
||||
{
|
||||
Entity.Spawner?.AddToRemoveQueue(item);
|
||||
@@ -217,28 +196,26 @@ namespace Barotrauma
|
||||
|
||||
public override bool IsCompleted()
|
||||
{
|
||||
bool isCompleted =
|
||||
targetCharacter.Vitality / targetCharacter.MaxVitality > AIObjectiveRescueAll.VitalityThreshold;
|
||||
|
||||
bool isCompleted = targetCharacter.Vitality / targetCharacter.MaxVitality > AIObjectiveRescueAll.VitalityThreshold;
|
||||
if (isCompleted)
|
||||
{
|
||||
character?.Speak(TextManager.Get("DialogTargetHealed").Replace("[targetname]", targetCharacter.Name),
|
||||
null, 1.0f, "targethealed" + targetCharacter.Name, 60.0f);
|
||||
}
|
||||
|
||||
return isCompleted || targetCharacter.IsDead;
|
||||
return isCompleted || targetCharacter.Removed || targetCharacter.IsDead;
|
||||
}
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
public override float GetPriority()
|
||||
{
|
||||
if (targetCharacter.AnimController.CurrentHull == null || targetCharacter.IsDead) { return 0.0f; }
|
||||
|
||||
Vector2 diff = targetCharacter.WorldPosition - character.WorldPosition;
|
||||
float distance = Math.Abs(diff.X) + Math.Abs(diff.Y);
|
||||
|
||||
float vitalityFactor = (targetCharacter.MaxVitality - targetCharacter.Vitality) / targetCharacter.MaxVitality;
|
||||
|
||||
return 1000.0f * vitalityFactor / distance;
|
||||
if (targetCharacter.CurrentHull == null || targetCharacter.IsDead) { return 0; ; }
|
||||
// Don't go into rooms that have enemies
|
||||
if (Character.CharacterList.Any(c => c.CurrentHull == targetCharacter.CurrentHull && !HumanAIController.IsFriendly(c))) { return 0; }
|
||||
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
|
||||
float dist = Math.Abs(character.WorldPosition.X - targetCharacter.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - targetCharacter.WorldPosition.Y) * 2.0f;
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 10000, dist));
|
||||
float vitalityFactor = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter);
|
||||
float devotion = Math.Max(Priority, 10) / 100;
|
||||
return MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + vitalityFactor * distanceFactor, 0, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-35
@@ -4,21 +4,17 @@ using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveRescueAll : AIObjective
|
||||
class AIObjectiveRescueAll : AIObjectiveLoop<Character>
|
||||
{
|
||||
public override string DebugTag => "rescue all";
|
||||
|
||||
public override bool KeepDivingGearOn => true;
|
||||
|
||||
//only treat characters whose vitality is below this (0.8 = 80% of max vitality)
|
||||
public const float VitalityThreshold = 0.8f;
|
||||
|
||||
private List<Character> rescueTargets;
|
||||
|
||||
public AIObjectiveRescueAll(Character character) : base (character, "")
|
||||
{
|
||||
rescueTargets = new List<Character>();
|
||||
}
|
||||
public AIObjectiveRescueAll(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier) { }
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveRescueAll;
|
||||
|
||||
protected override void FindTargets()
|
||||
{
|
||||
@@ -29,31 +25,13 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
{
|
||||
if (character.Submarine == null) { return 0; }
|
||||
GetRescueTargets();
|
||||
if (!rescueTargets.Any()) { return 0.0f; }
|
||||
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
protected override bool Filter(Character target) => IsValidTarget(target, character);
|
||||
|
||||
//if there are targets to rescue, the priority is slightly less
|
||||
//than the priority of explicit orders given to the character
|
||||
return AIObjectiveManager.OrderPriority - 5.0f;
|
||||
}
|
||||
protected override IEnumerable<Character> GetList() => Character.CharacterList;
|
||||
|
||||
private void GetRescueTargets()
|
||||
{
|
||||
rescueTargets = Character.CharacterList.FindAll(c =>
|
||||
c.AIController is HumanAIController &&
|
||||
c.TeamID == character.TeamID &&
|
||||
c != character &&
|
||||
!c.IsDead &&
|
||||
c.Vitality / c.MaxVitality < VitalityThreshold);
|
||||
}
|
||||
protected override AIObjective ObjectiveConstructor(Character target) => new AIObjectiveRescue(character, target, objectiveManager, PriorityModifier);
|
||||
|
||||
protected override float TargetEvaluation() => targets.Max(t => GetVitalityFactor(t)) * 100;
|
||||
|
||||
public static float GetVitalityFactor(Character character) => (character.MaxVitality - character.Vitality) / character.MaxVitality;
|
||||
|
||||
@@ -69,8 +47,5 @@ namespace Barotrauma
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, true)) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool IsCompleted() => false;
|
||||
public override bool CanBeCompleted => true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user