Merge remote-tracking branch 'upstream/master' into develop
This commit is contained in:
@@ -15,7 +15,10 @@ namespace Barotrauma
|
||||
public virtual string DebugTag => Identifier.Value;
|
||||
public virtual bool ForceRun => false;
|
||||
public virtual bool IgnoreUnsafeHulls => false;
|
||||
public virtual bool AbandonWhenCannotCompleteSubjectives => true;
|
||||
public virtual bool AbandonWhenCannotCompleteSubObjectives => true;
|
||||
/// <summary>
|
||||
/// Should subobjectives be sorted according to their priority?
|
||||
/// </summary>
|
||||
public virtual bool AllowSubObjectiveSorting => false;
|
||||
public virtual bool PrioritizeIfSubObjectivesActive => false;
|
||||
|
||||
@@ -28,8 +31,7 @@ namespace Barotrauma
|
||||
/// Run the main objective with all subobjectives concurrently?
|
||||
/// If false, the main objective will continue only when all the subobjectives have been removed (done).
|
||||
/// </summary>
|
||||
public virtual bool ConcurrentObjectives => false;
|
||||
|
||||
protected virtual bool ConcurrentObjectives => false;
|
||||
public virtual bool KeepDivingGearOn => false;
|
||||
public virtual bool KeepDivingGearOnAlsoWhenInactive => false;
|
||||
|
||||
@@ -37,10 +39,36 @@ namespace Barotrauma
|
||||
/// There's a separate property for diving suit and mask: KeepDivingGearOn.
|
||||
/// </summary>
|
||||
public virtual bool AllowAutomaticItemUnequipping => false;
|
||||
public virtual bool AllowOutsideSubmarine => false;
|
||||
public virtual bool AllowInFriendlySubs => false;
|
||||
public virtual bool AllowInAnySub => false;
|
||||
public virtual bool AllowWhileHandcuffed => true;
|
||||
|
||||
// These booleans are used for defining whether the objective is allowed in different contexts. E.g. AllowOutsideSubmarine needs to be true or the objective cannot be active when the bot is swimming outside.
|
||||
protected virtual bool AllowOutsideSubmarine => false;
|
||||
/// <summary>
|
||||
/// When true, the objective is allowed in the player subs (when in the same team) and on friendly outposts (regardless of the alignment).
|
||||
/// Note: ignored when <see cref="AllowInAnySub"/> is true.
|
||||
/// </summary>
|
||||
protected virtual bool AllowInFriendlySubs => false;
|
||||
protected virtual bool AllowInAnySub => false;
|
||||
protected virtual bool AllowWhileHandcuffed => true;
|
||||
|
||||
/// <summary>
|
||||
/// Should the objective abandon when it's not allowed in the current context or should it just stay inactive with 0 priority?
|
||||
/// Abandoned automatic objectives are removed and recreated automatically (when new orders are assigned or after a cooldown period).
|
||||
/// Abandoned orders are removed, but the most recent order can be reissued by clicking the small order icon with the arrow in the crew manager panel.
|
||||
/// </summary>
|
||||
protected virtual bool AbandonIfDisallowed => true;
|
||||
|
||||
public virtual bool CanBeCompleted => !Abandon;
|
||||
|
||||
protected virtual float MaxDevotion => 10;
|
||||
|
||||
/// <summary>
|
||||
/// Which event action (if any) created this objective
|
||||
/// </summary>
|
||||
public EventAction SourceEventAction;
|
||||
/// <summary>
|
||||
/// Which objective (if any) created this objective. When this is a subobjective, the parent objective is used by default.
|
||||
/// </summary>
|
||||
public AIObjective SourceObjective;
|
||||
|
||||
protected readonly List<AIObjective> subObjectives = new List<AIObjective>();
|
||||
private float _cumulatedDevotion;
|
||||
@@ -50,8 +78,6 @@ namespace Barotrauma
|
||||
set { _cumulatedDevotion = MathHelper.Clamp(value, 0, MaxDevotion); }
|
||||
}
|
||||
|
||||
protected virtual float MaxDevotion => 10;
|
||||
|
||||
/// <summary>
|
||||
/// Final priority value after all calculations.
|
||||
/// </summary>
|
||||
@@ -100,17 +126,13 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool CanBeCompleted => !Abandon;
|
||||
|
||||
/// <summary>
|
||||
/// When true, the objective is never completed, unless CanBeCompleted returns false.
|
||||
/// </summary>
|
||||
public virtual bool IsLoop { get; set; }
|
||||
|
||||
public IEnumerable<AIObjective> SubObjectives => subObjectives;
|
||||
|
||||
public AIObjective CurrentSubObjective => subObjectives.FirstOrDefault();
|
||||
|
||||
private readonly List<AIObjective> all = new List<AIObjective>();
|
||||
|
||||
public IEnumerable<AIObjective> GetSubObjectivesRecursive(bool includingSelf = false)
|
||||
{
|
||||
all.Clear();
|
||||
@@ -124,13 +146,11 @@ namespace Barotrauma
|
||||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
#pragma warning disable CS0649
|
||||
|
||||
/// <summary>
|
||||
/// Aborts the objective when this condition is true.
|
||||
/// </summary>
|
||||
public Func<AIObjective, bool> AbortCondition;
|
||||
#pragma warning restore CS0649
|
||||
|
||||
/// <summary>
|
||||
/// A single shot event. Automatically cleared after launching. Use OnCompleted method for implementing (internal) persistent behavior.
|
||||
@@ -186,6 +206,7 @@ namespace Barotrauma
|
||||
public void AddSubObjective(AIObjective objective, bool addFirst = false)
|
||||
{
|
||||
var type = objective.GetType();
|
||||
objective.SourceObjective = this;
|
||||
subObjectives.RemoveAll(o => o.GetType() == type);
|
||||
if (addFirst)
|
||||
{
|
||||
@@ -259,17 +280,21 @@ namespace Barotrauma
|
||||
return character.Submarine.Info.IsOutpost && character.Submarine.TeamID == CharacterTeamType.FriendlyNPC;
|
||||
}
|
||||
|
||||
protected void HandleNonAllowed()
|
||||
protected void HandleDisallowed()
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = !IsIgnoredAtOutpost();
|
||||
if (AbandonIfDisallowed && !IsIgnoredAtOutpost())
|
||||
{
|
||||
// Never abandon objectives inside a friendly outpost, because otherwise we'd have to reassign most orders every round.
|
||||
Abandon = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual float GetPriority()
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
HandleNonAllowed();
|
||||
HandleDisallowed();
|
||||
return Priority;
|
||||
}
|
||||
if (objectiveManager.IsOrder(this))
|
||||
@@ -360,7 +385,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Checks if the subobjectives in the given collection are removed from the subobjectives. And if so, removes it also from the dictionary.
|
||||
/// </summary>
|
||||
protected void SyncRemovedObjectives<T1, T2>(Dictionary<T1, T2> dictionary, IEnumerable<T1> collection) where T2 : AIObjective
|
||||
protected virtual void SyncRemovedObjectives<T1, T2>(Dictionary<T1, T2> dictionary, IEnumerable<T1> collection) where T2 : AIObjective
|
||||
{
|
||||
foreach (T1 key in collection)
|
||||
{
|
||||
@@ -398,6 +423,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (objective.AllowMultipleInstances)
|
||||
{
|
||||
objective.SourceObjective = this;
|
||||
subObjectives.Add(objective);
|
||||
}
|
||||
else
|
||||
@@ -482,6 +508,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check whether the objective should be aborted (and abandon if it should), and return whether the objective is completed or not.
|
||||
/// </summary>
|
||||
private bool Check()
|
||||
{
|
||||
if (AbortCondition != null && AbortCondition(this))
|
||||
@@ -492,6 +521,9 @@ namespace Barotrauma
|
||||
return CheckObjectiveSpecific();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Should return whether the objective is completed or not.
|
||||
/// </summary>
|
||||
protected abstract bool CheckObjectiveSpecific();
|
||||
|
||||
private bool CheckState()
|
||||
@@ -527,7 +559,7 @@ namespace Barotrauma
|
||||
DebugConsole.NewMessage($"{character.Name}: Removing SUBobjective {subObjective.DebugTag} of {DebugTag}, because it cannot be completed.", Color.Red);
|
||||
#endif
|
||||
subObjectives.Remove(subObjective);
|
||||
if (AbandonWhenCannotCompleteSubjectives)
|
||||
if (AbandonWhenCannotCompleteSubObjectives)
|
||||
{
|
||||
if (objectiveManager.IsOrder(this))
|
||||
{
|
||||
|
||||
+2
-3
@@ -16,7 +16,7 @@ namespace Barotrauma
|
||||
public AIObjectiveChargeBatteries(Character character, AIObjectiveManager objectiveManager, Identifier option, float priorityModifier)
|
||||
: base(character, objectiveManager, priorityModifier, option) { }
|
||||
|
||||
protected override bool Filter(PowerContainer battery)
|
||||
protected override bool IsValidTarget(PowerContainer battery)
|
||||
{
|
||||
if (battery == null) { return false; }
|
||||
if (battery.OutputDisabled) { return false; }
|
||||
@@ -37,7 +37,7 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override float TargetEvaluation()
|
||||
protected override float GetTargetPriority()
|
||||
{
|
||||
if (Targets.None()) { return 0; }
|
||||
if (Option == "charge")
|
||||
@@ -80,7 +80,6 @@ namespace Barotrauma
|
||||
protected override AIObjective ObjectiveConstructor(PowerContainer battery) =>
|
||||
new AIObjectiveOperateItem(battery, character, objectiveManager, Option, false, priorityModifier: PriorityModifier)
|
||||
{
|
||||
IsLoop = false,
|
||||
Override = !character.IsDismissed,
|
||||
completionCondition = () => IsReady(battery)
|
||||
};
|
||||
|
||||
+105
-34
@@ -8,8 +8,8 @@ namespace Barotrauma
|
||||
class AIObjectiveCheckStolenItems : AIObjective
|
||||
{
|
||||
public override Identifier Identifier { get; set; } = "check stolen items".ToIdentifier();
|
||||
public override bool AllowOutsideSubmarine => false;
|
||||
public override bool AllowInAnySub => false;
|
||||
protected override bool AllowOutsideSubmarine => false;
|
||||
protected override bool AllowInAnySub => false;
|
||||
|
||||
public float FindStolenItemsProbability = 1.0f;
|
||||
|
||||
@@ -21,36 +21,38 @@ namespace Barotrauma
|
||||
Done
|
||||
}
|
||||
|
||||
private float inspectDelay;
|
||||
private float warnDelay;
|
||||
private const float InspectTime = 5.0f;
|
||||
private const float NormalWarnDelay = 5.0f;
|
||||
private const float CriminalWarnDelay = 3.0f;
|
||||
private float inspectTimer;
|
||||
private float warnTimer;
|
||||
private float currentWarnDelay;
|
||||
|
||||
private State currentState;
|
||||
|
||||
public readonly Character TargetCharacter;
|
||||
public readonly Character Target;
|
||||
|
||||
private AIObjectiveGoTo? goToObjective;
|
||||
|
||||
private readonly List<Item> stolenItems = new List<Item>();
|
||||
|
||||
public AIObjectiveCheckStolenItems(Character character, Character targetCharacter, AIObjectiveManager objectiveManager, float priorityModifier = 1) :
|
||||
public AIObjectiveCheckStolenItems(Character character, Character target, AIObjectiveManager objectiveManager, float priorityModifier = 1) :
|
||||
base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
TargetCharacter = targetCharacter;
|
||||
inspectDelay = 5.0f;
|
||||
warnDelay = 5.0f;
|
||||
}
|
||||
|
||||
public override bool IsLoop
|
||||
{
|
||||
get => false;
|
||||
set => throw new Exception("Trying to set the value for IsLoop from: " + Environment.StackTrace.CleanupStackTrace());
|
||||
Target = target;
|
||||
InitTimers();
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific() => false;
|
||||
|
||||
protected override float GetPriority()
|
||||
{
|
||||
if (!Abandon && !IsCompleted && objectiveManager.IsOrder(this))
|
||||
if (character.IsClimbing)
|
||||
{
|
||||
// Target is climbing -> stop following the objective (soft abandon, without ignoring the target).
|
||||
Priority = 0;
|
||||
}
|
||||
else if (!Abandon && !IsCompleted && objectiveManager.IsOrder(this))
|
||||
{
|
||||
Priority = objectiveManager.GetOrderPriority(this);
|
||||
}
|
||||
@@ -70,22 +72,32 @@ namespace Barotrauma
|
||||
{
|
||||
switch (currentState)
|
||||
{
|
||||
case State.Done:
|
||||
IsCompleted = true;
|
||||
break;
|
||||
case State.GotoTarget:
|
||||
TryAddSubObjective(ref goToObjective,
|
||||
constructor: () =>
|
||||
constructor: () => new AIObjectiveGoTo(Target, character, objectiveManager, repeat: false)
|
||||
{
|
||||
return new AIObjectiveGoTo(TargetCharacter, character, objectiveManager, repeat: false)
|
||||
{
|
||||
SpeakIfFails = false
|
||||
};
|
||||
SpeakIfFails = false
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
RemoveSubObjective(ref goToObjective);
|
||||
currentState = State.Inspect;
|
||||
stolenItems.Clear();
|
||||
TargetCharacter.Inventory.FindAllItems(it => it.SpawnedInCurrentOutpost && !it.AllowStealing, recursive: true, stolenItems);
|
||||
character.Speak(TextManager.Get("dialogcheckstolenitems").Value);
|
||||
if (character.IsClimbing)
|
||||
{
|
||||
// Shouldn't start inspecting characters when they climb, nor get here, because the priority should be 0,
|
||||
// but if this still happens, we'll have to abandon the objective
|
||||
// because it's not currently possible to hold to characters and ladders at the same time.
|
||||
Abandon = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentState = State.Inspect;
|
||||
stolenItems.Clear();
|
||||
Target.Inventory.FindAllItems(it => it.Illegitimate, recursive: true, stolenItems);
|
||||
character.Speak(TextManager.Get(Target.IsCriminal ? "dialogcheckstolenitems.criminal" : "dialogcheckstolenitems").Value);
|
||||
}
|
||||
},
|
||||
onAbandon: () =>
|
||||
{
|
||||
@@ -103,10 +115,23 @@ namespace Barotrauma
|
||||
|
||||
private void Inspect(float deltaTime)
|
||||
{
|
||||
if (inspectDelay > 0.0f)
|
||||
if (inspectTimer > 0.0f)
|
||||
{
|
||||
character.SelectCharacter(TargetCharacter);
|
||||
inspectDelay -= deltaTime;
|
||||
character.SelectCharacter(Target);
|
||||
inspectTimer -= deltaTime;
|
||||
if (inspectTimer < InspectTime - 1)
|
||||
{
|
||||
if (Target.AnimController.IsMovingFast)
|
||||
{
|
||||
ArrestFleeing();
|
||||
}
|
||||
else if (Math.Abs(Target.AnimController.TargetMovement.X) > 1.0f)
|
||||
{
|
||||
// If the target moves, reset the inspect timer and tell to hold still
|
||||
character.Speak(TextManager.Get("dialogcheckstolenitems.holdstill").Value, identifier: "holdstill".ToIdentifier(), minDurationBetweenSimilar: 3f);
|
||||
inspectTimer = InspectTime;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -118,7 +143,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
character.Speak(TextManager.Get("dialogcheckstolenitems.nostolenitems").Value);
|
||||
character.Speak(TextManager.Get(Target.IsCriminal ? "dialogcheckstolenitems.nostolenitems.criminal" : "dialogcheckstolenitems.nostolenitems").Value);
|
||||
currentState = State.Done;
|
||||
IsCompleted = true;
|
||||
}
|
||||
@@ -127,16 +152,23 @@ namespace Barotrauma
|
||||
|
||||
private void Warn(float deltaTime)
|
||||
{
|
||||
if (warnDelay > 0.0f)
|
||||
if (warnTimer > 0.0f)
|
||||
{
|
||||
warnDelay -= deltaTime;
|
||||
warnTimer -= deltaTime;
|
||||
if (warnTimer < currentWarnDelay - 1)
|
||||
{
|
||||
if (Target.AnimController.IsMovingFast)
|
||||
{
|
||||
ArrestFleeing();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
var stolenItemsOnCharacter = stolenItems.Where(it => it.GetRootInventoryOwner() == TargetCharacter);
|
||||
var stolenItemsOnCharacter = stolenItems.Where(it => it.GetRootInventoryOwner() == Target);
|
||||
if (stolenItemsOnCharacter.Any())
|
||||
{
|
||||
character.Speak(TextManager.Get("dialogcheckstolenitems.arrest").Value);
|
||||
HumanAIController.AddCombatObjective(AIObjectiveCombat.CombatMode.Arrest, TargetCharacter);
|
||||
character.Speak(TextManager.Get(character.IsCriminal ? "dialogcheckstolenitems.arrest.criminal" : "dialogcheckstolenitems.arrest").Value);
|
||||
Arrest(abortWhenItemsDropped: true, allowHoldFire: true);
|
||||
foreach (var stolenItem in stolenItemsOnCharacter)
|
||||
{
|
||||
HumanAIController.ApplyStealingReputationLoss(stolenItem);
|
||||
@@ -156,5 +188,44 @@ namespace Barotrauma
|
||||
currentState = State.Done;
|
||||
IsCompleted = true;
|
||||
}
|
||||
|
||||
private void ArrestFleeing()
|
||||
{
|
||||
character.Speak(TextManager.Get("dialogcheckstolenitems.arrest").Value);
|
||||
currentState = State.Done;
|
||||
IsCompleted = true;
|
||||
Arrest(abortWhenItemsDropped: false, allowHoldFire: false);
|
||||
}
|
||||
|
||||
private void Arrest(bool abortWhenItemsDropped, bool allowHoldFire)
|
||||
{
|
||||
bool isCriminal = Target.IsCriminal;
|
||||
Func<AIObjective, bool>? abortCondition = null;
|
||||
if (abortWhenItemsDropped && !isCriminal)
|
||||
{
|
||||
abortCondition = obj => Target.Inventory.FindItem(it => it.Illegitimate, recursive: true) == null;
|
||||
}
|
||||
HumanAIController.AddCombatObjective(AIObjectiveCombat.CombatMode.Arrest, Target, allowHoldFire: allowHoldFire && !isCriminal, speakWarnings: !isCriminal, abortCondition: abortCondition);
|
||||
}
|
||||
|
||||
public override void OnDeselected()
|
||||
{
|
||||
base.OnDeselected();
|
||||
character.DeselectCharacter();
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
currentState = State.GotoTarget;
|
||||
InitTimers();
|
||||
}
|
||||
|
||||
private void InitTimers()
|
||||
{
|
||||
inspectTimer = InspectTime;
|
||||
currentWarnDelay = Target.IsCriminal ? CriminalWarnDelay : NormalWarnDelay;
|
||||
warnTimer = currentWarnDelay;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -12,7 +12,7 @@ namespace Barotrauma
|
||||
public override Identifier Identifier { get; set; } = "cleanup item".ToIdentifier();
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool AllowAutomaticItemUnequipping => false;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
protected override bool AllowWhileHandcuffed => false;
|
||||
|
||||
public readonly Item item;
|
||||
public bool IsPriority { get; set; }
|
||||
@@ -24,7 +24,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Allows decontainObjective to be interrupted if this objective gets abandoned (e.g. due to the item no longer being eligible for cleanup)
|
||||
/// </summary>
|
||||
public override bool ConcurrentObjectives => true;
|
||||
protected override bool ConcurrentObjectives => true;
|
||||
|
||||
public AIObjectiveCleanupItem(Item item, Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
@@ -36,7 +36,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
HandleNonAllowed();
|
||||
HandleDisallowed();
|
||||
return Priority;
|
||||
}
|
||||
else
|
||||
|
||||
+3
-3
@@ -31,7 +31,7 @@ namespace Barotrauma
|
||||
this.prioritizedItems.AddRange(prioritizedItems.Where(i => i != null));
|
||||
}
|
||||
|
||||
protected override float TargetEvaluation()
|
||||
protected override float GetTargetPriority()
|
||||
{
|
||||
if (Targets.None()) { return 0; }
|
||||
if (objectiveManager.IsOrder(this))
|
||||
@@ -47,7 +47,7 @@ namespace Barotrauma
|
||||
return AIObjectiveManager.RunPriority - 0.5f;
|
||||
}
|
||||
|
||||
protected override bool Filter(Item target)
|
||||
protected override bool IsValidTarget(Item target)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(target.GetComponent<Pickable>() is { } pickable && !pickable.IsAttached, "Invalid target in AIObjectiveCleanUpItems - the the objective should only be checking pickable, non-attached items.");
|
||||
System.Diagnostics.Debug.Assert(target.Prefab.PreferredContainers.Any(), "Invalid target in AIObjectiveCleanUpItems - the the objective should only be checking items that have preferred containers defined.");
|
||||
@@ -100,7 +100,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
if (item.DontCleanUp) { return false; }
|
||||
if ((item.SpawnedInCurrentOutpost && !item.AllowStealing) == character.IsOnPlayerTeam) { return false; }
|
||||
if (item.Illegitimate == character.IsOnPlayerTeam) { return false; }
|
||||
if (item.ParentInventory != null)
|
||||
{
|
||||
if (item.Container == null)
|
||||
|
||||
+247
-137
@@ -16,23 +16,23 @@ namespace Barotrauma
|
||||
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool IgnoreUnsafeHulls => true;
|
||||
public override bool AllowOutsideSubmarine => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
protected override bool AllowOutsideSubmarine => true;
|
||||
protected override bool AllowInAnySub => true;
|
||||
|
||||
private readonly CombatMode initialMode;
|
||||
|
||||
private float checkWeaponsTimer;
|
||||
private const float checkWeaponsInterval = 1;
|
||||
private const float CheckWeaponsInterval = 1;
|
||||
private float ignoreWeaponTimer;
|
||||
private const float ignoredWeaponsClearTime = 10;
|
||||
private const float IgnoredWeaponsClearTime = 10;
|
||||
|
||||
private const float goodWeaponPriority = 30;
|
||||
|
||||
private const float arrestHoldFireTime = 8;
|
||||
private const float GoodWeaponPriority = 30;
|
||||
|
||||
private float holdFireTimer;
|
||||
private bool hasAimed;
|
||||
private bool isLethalWeapon;
|
||||
private bool AllowCoolDown => !IsOffensiveOrArrest || Mode != initialMode || character.TeamID == Enemy.TeamID;
|
||||
private bool AllowCoolDown => allowCooldown || !IsOffensiveOrArrest || Mode != initialMode || character.TeamID == Enemy.TeamID;
|
||||
private bool allowCooldown;
|
||||
|
||||
public Character Enemy { get; private set; }
|
||||
public bool HoldPosition { get; set; }
|
||||
@@ -45,7 +45,6 @@ namespace Barotrauma
|
||||
{
|
||||
_weapon = value;
|
||||
_weaponComponent = null;
|
||||
hasAimed = false;
|
||||
}
|
||||
}
|
||||
private ItemComponent _weaponComponent;
|
||||
@@ -58,8 +57,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override bool ConcurrentObjectives => true;
|
||||
public override bool AbandonWhenCannotCompleteSubjectives => false;
|
||||
protected override bool ConcurrentObjectives => true;
|
||||
public override bool AbandonWhenCannotCompleteSubObjectives => false;
|
||||
|
||||
private readonly AIObjectiveFindSafety findSafety;
|
||||
private readonly HashSet<ItemComponent> weapons = new HashSet<ItemComponent>();
|
||||
@@ -72,6 +71,9 @@ namespace Barotrauma
|
||||
|
||||
private Hull retreatTarget;
|
||||
private float coolDownTimer;
|
||||
private float pathBackTimer;
|
||||
private const float DefaultCoolDown = 10.0f;
|
||||
private const float PathBackCheckTime = 1.0f;
|
||||
private IEnumerable<Body> myBodies;
|
||||
private float aimTimer;
|
||||
private float reloadTimer;
|
||||
@@ -79,17 +81,25 @@ namespace Barotrauma
|
||||
|
||||
private bool canSeeTarget;
|
||||
private float visibilityCheckTimer;
|
||||
private const float visibilityCheckInterval = 0.2f;
|
||||
private const float VisibilityCheckInterval = 0.2f;
|
||||
|
||||
private float sqrDistance;
|
||||
private const float maxDistance = 2000;
|
||||
private const float distanceCheckInterval = 0.2f;
|
||||
private const float MaxDistance = 2000;
|
||||
private const float DistanceCheckInterval = 0.2f;
|
||||
private float distanceTimer;
|
||||
|
||||
private const float closeDistanceThreshold = 300;
|
||||
private const float floorHeightApproximate = 100;
|
||||
private const float CloseDistanceThreshold = 300;
|
||||
private const float FloorHeightApproximate = 100;
|
||||
|
||||
public bool AllowHoldFire;
|
||||
public bool SpeakWarnings;
|
||||
private bool firstWarningTriggered;
|
||||
private bool lastWarningTriggered;
|
||||
|
||||
public float ArrestHoldFireTime { get; init; } = 10;
|
||||
|
||||
private const float ArrestTargetDistance = 100;
|
||||
private bool arrestingRegistered;
|
||||
|
||||
/// <summary>
|
||||
/// Don't start using a weapon if this condition is true
|
||||
@@ -123,7 +133,7 @@ namespace Barotrauma
|
||||
public CombatMode Mode { get; private set; }
|
||||
|
||||
private bool IsOffensiveOrArrest => initialMode is CombatMode.Offensive or CombatMode.Arrest;
|
||||
private bool TargetEliminated => IsEnemyDisabled || Enemy.IsUnconscious && Enemy.Params.Health.ConstantHealthRegeneration <= 0.0f || Enemy.IsArrested && !character.IsInstigator;
|
||||
private bool TargetEliminated => IsEnemyDisabled || (Enemy.IsUnconscious && Enemy.Params.Health.ConstantHealthRegeneration <= 0.0f) || (!character.IsInstigator && Enemy.IsHandcuffed && Enemy.IsKnockedDown);
|
||||
private bool IsEnemyDisabled => Enemy == null || Enemy.Removed || Enemy.IsDead;
|
||||
|
||||
private float AimSpeed => HumanAIController.AimSpeed;
|
||||
@@ -141,7 +151,7 @@ namespace Barotrauma
|
||||
if (character.CurrentHull != null && Enemy.CurrentHull != null && character.CurrentHull != Enemy.CurrentHull)
|
||||
{
|
||||
// Inside, not in the same hull with the enemy
|
||||
if (Math.Abs(toEnemy.Y) > floorHeightApproximate)
|
||||
if (Math.Abs(toEnemy.Y) > FloorHeightApproximate)
|
||||
{
|
||||
// Different floor
|
||||
return false;
|
||||
@@ -156,7 +166,7 @@ namespace Barotrauma
|
||||
return Vector2.DistanceSquared(character.WorldPosition, Enemy.WorldPosition) < margin * margin;
|
||||
}
|
||||
|
||||
public AIObjectiveCombat(Character character, Character enemy, CombatMode mode, AIObjectiveManager objectiveManager, float priorityModifier = 1, float coolDown = 10.0f)
|
||||
public AIObjectiveCombat(Character character, Character enemy, CombatMode mode, AIObjectiveManager objectiveManager, float priorityModifier = 1, float coolDown = DefaultCoolDown)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
if (mode == CombatMode.None)
|
||||
@@ -187,48 +197,31 @@ namespace Barotrauma
|
||||
|
||||
protected override float GetPriority()
|
||||
{
|
||||
if (Enemy == null || Enemy.Removed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
return Priority;
|
||||
}
|
||||
if (character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
if (Enemy.Submarine == null || (Enemy.Submarine.TeamID != character.TeamID && Enemy.Submarine != character.Submarine))
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
return Priority;
|
||||
}
|
||||
}
|
||||
if (TargetEliminated)
|
||||
{
|
||||
Priority = 0;
|
||||
return Priority;
|
||||
}
|
||||
else
|
||||
// 91-100
|
||||
const float minPriority = AIObjectiveManager.EmergencyObjectivePriority + 1;
|
||||
const float maxPriority = AIObjectiveManager.MaxObjectivePriority;
|
||||
const float priorityScale = maxPriority - minPriority;
|
||||
float xDist = Math.Abs(character.WorldPosition.X - Enemy.WorldPosition.X);
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - Enemy.WorldPosition.Y);
|
||||
if (HumanAIController.VisibleHulls.Contains(Enemy.CurrentHull))
|
||||
{
|
||||
// 91-100
|
||||
const float minPriority = AIObjectiveManager.EmergencyObjectivePriority + 1;
|
||||
const float maxPriority = AIObjectiveManager.MaxObjectivePriority;
|
||||
const float priorityScale = maxPriority - minPriority;
|
||||
float xDist = Math.Abs(character.WorldPosition.X - Enemy.WorldPosition.X);
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - Enemy.WorldPosition.Y);
|
||||
if (HumanAIController.VisibleHulls.Contains(Enemy.CurrentHull))
|
||||
xDist /= 2;
|
||||
yDist /= 2;
|
||||
}
|
||||
float distanceFactor = MathUtils.InverseLerp(3000, 0, xDist + yDist * 5);
|
||||
float devotion = CumulatedDevotion / 100;
|
||||
float additionalPriority = MathHelper.Lerp(0, priorityScale, Math.Clamp(devotion + distanceFactor, 0, 1));
|
||||
Priority = Math.Min((minPriority + additionalPriority) * PriorityModifier, maxPriority);
|
||||
if (Priority > 0)
|
||||
{
|
||||
if (EnemyAIController.IsLatchedToSomeoneElse(Enemy, character))
|
||||
{
|
||||
xDist /= 2;
|
||||
yDist /= 2;
|
||||
}
|
||||
float distanceFactor = MathUtils.InverseLerp(3000, 0, xDist + yDist * 5);
|
||||
float devotion = CumulatedDevotion / 100;
|
||||
float additionalPriority = MathHelper.Lerp(0, priorityScale, Math.Clamp(devotion + distanceFactor, 0, 1));
|
||||
Priority = Math.Min((minPriority + additionalPriority) * PriorityModifier, maxPriority);
|
||||
if (Priority > 0)
|
||||
{
|
||||
if (EnemyAIController.IsLatchedToSomeoneElse(Enemy, character))
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
Priority = 0;
|
||||
}
|
||||
}
|
||||
return Priority;
|
||||
@@ -246,7 +239,7 @@ namespace Barotrauma
|
||||
if (ignoreWeaponTimer < 0)
|
||||
{
|
||||
ignoredWeapons.Clear();
|
||||
ignoreWeaponTimer = ignoredWeaponsClearTime;
|
||||
ignoreWeaponTimer = IgnoredWeaponsClearTime;
|
||||
}
|
||||
bool isFightingIntruders = objectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>();
|
||||
if (findSafety != null && isFightingIntruders)
|
||||
@@ -258,7 +251,7 @@ namespace Barotrauma
|
||||
distanceTimer -= deltaTime;
|
||||
if (distanceTimer < 0)
|
||||
{
|
||||
distanceTimer = distanceCheckInterval;
|
||||
distanceTimer = DistanceCheckInterval;
|
||||
sqrDistance = Vector2.DistanceSquared(character.WorldPosition, Enemy.WorldPosition);
|
||||
}
|
||||
}
|
||||
@@ -266,16 +259,62 @@ namespace Barotrauma
|
||||
|
||||
protected override bool CheckObjectiveSpecific()
|
||||
{
|
||||
if (character.Submarine is not { TeamID: CharacterTeamType.FriendlyNPC })
|
||||
if (character.Submarine is { TeamID: CharacterTeamType.FriendlyNPC } && character.Submarine == Enemy.Submarine)
|
||||
{
|
||||
// Can't lose the target in friendly outposts.
|
||||
if (sqrDistance > maxDistance * maxDistance)
|
||||
// Target still in the outpost
|
||||
if (character.TeamID == CharacterTeamType.FriendlyNPC && !character.IsSecurity)
|
||||
{
|
||||
// The target escaped from us.
|
||||
return true;
|
||||
// Outpost guards shouldn't lose the target in friendly outposts,
|
||||
// However, if we are not a guard, let's ensure that we allow the cooldown.
|
||||
allowCooldown = true;
|
||||
}
|
||||
}
|
||||
return IsEnemyDisabled || (AllowCoolDown && coolDownTimer <= 0);
|
||||
else
|
||||
{
|
||||
if ((Enemy.Submarine == null && character.Submarine != null) || sqrDistance > MaxDistance * MaxDistance)
|
||||
{
|
||||
// The target escaped from us.
|
||||
Abandon = true;
|
||||
if (character.TeamID == CharacterTeamType.FriendlyNPC && IsOffensiveOrArrest)
|
||||
{
|
||||
Enemy.IsCriminal = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (Enemy.Submarine != null && character.Submarine != null && character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
if (Enemy.Submarine.TeamID != character.TeamID)
|
||||
{
|
||||
allowCooldown = true;
|
||||
// Target not in the outpost anymore.
|
||||
if (character.CanSeeTarget(Enemy))
|
||||
{
|
||||
allowCooldown = false;
|
||||
coolDownTimer = DefaultCoolDown;
|
||||
}
|
||||
else if (pathBackTimer <= 0)
|
||||
{
|
||||
// Check once per sec during the cooldown whether we can find a path back to the docking port
|
||||
pathBackTimer = PathBackCheckTime;
|
||||
foreach ((Submarine sub, DockingPort dockingPort) in character.Submarine.ConnectedDockingPorts)
|
||||
{
|
||||
if (sub.TeamID != character.TeamID) { continue; }
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, character.GetRelativeSimPosition(dockingPort.Item), character.Submarine, nodeFilter: node => node.Waypoint.CurrentHull != null);
|
||||
if (path.Unreachable)
|
||||
{
|
||||
allowCooldown = false;
|
||||
coolDownTimer = DefaultCoolDown;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (IsOffensiveOrArrest)
|
||||
{
|
||||
Enemy.IsCriminal = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return TargetEliminated || (AllowCoolDown && coolDownTimer <= 0);
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
@@ -288,6 +327,10 @@ namespace Barotrauma
|
||||
if (AllowCoolDown)
|
||||
{
|
||||
coolDownTimer -= deltaTime;
|
||||
if (pathBackTimer > 0)
|
||||
{
|
||||
pathBackTimer -= deltaTime;
|
||||
}
|
||||
}
|
||||
if (seekAmmunitionObjective == null && seekWeaponObjective == null)
|
||||
{
|
||||
@@ -303,27 +346,6 @@ namespace Barotrauma
|
||||
{
|
||||
Move(deltaTime);
|
||||
}
|
||||
switch (Mode)
|
||||
{
|
||||
case CombatMode.Offensive:
|
||||
if (TargetEliminated && objectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>())
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogTargetDown").Value, null, 3.0f, "targetdown".ToIdentifier(), 30.0f);
|
||||
}
|
||||
break;
|
||||
case CombatMode.Arrest:
|
||||
if (HumanAIController.HasItem(Enemy, Tags.HandLockerItem, out _, requireEquipped: true))
|
||||
{
|
||||
IsCompleted = true;
|
||||
}
|
||||
else if (Enemy.IsKnockedDown &&
|
||||
!objectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>() &&
|
||||
!HumanAIController.HasItem(character, Tags.HandLockerItem, out _, requireEquipped: false))
|
||||
{
|
||||
IsCompleted = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,7 +411,7 @@ namespace Barotrauma
|
||||
&& !character.IsInstigator); // Instigators (= aggressive NPCs spawned with events) shouldn't seek new weapons, because we don't want them to grab e.g. an smg, if they spawn with a wrench or something.
|
||||
if (checkWeaponsTimer < 0)
|
||||
{
|
||||
checkWeaponsTimer = checkWeaponsInterval;
|
||||
checkWeaponsTimer = CheckWeaponsInterval;
|
||||
// First go through all weapons and try to reload without seeking ammunition
|
||||
HashSet<ItemComponent> allWeapons = FindWeaponsFromInventory();
|
||||
while (allWeapons.Any())
|
||||
@@ -412,7 +434,7 @@ namespace Barotrauma
|
||||
// All good, the weapon is loaded
|
||||
break;
|
||||
}
|
||||
bool seekAmmo = isAllowedToSeekWeapons && seekAmmunitionObjective == null && !IsEnemyClose(closeDistanceThreshold);
|
||||
bool seekAmmo = isAllowedToSeekWeapons && seekAmmunitionObjective == null && !IsEnemyClose(CloseDistanceThreshold);
|
||||
if (Reload(seekAmmo: seekAmmo))
|
||||
{
|
||||
// All good, we can use the weapon.
|
||||
@@ -458,7 +480,7 @@ namespace Barotrauma
|
||||
Mode = CombatMode.Retreat;
|
||||
}
|
||||
}
|
||||
else if (seekAmmunitionObjective == null && (WeaponComponent == null || (WeaponComponent.CombatPriority < goodWeaponPriority && !IsEnemyClose(closeDistanceThreshold))))
|
||||
else if (seekAmmunitionObjective == null && (WeaponComponent == null || (WeaponComponent.CombatPriority < GoodWeaponPriority && !IsEnemyClose(CloseDistanceThreshold))))
|
||||
{
|
||||
// No weapon or only a poor weapon equipped -> try to find better.
|
||||
RemoveSubObjective(ref retreatObjective);
|
||||
@@ -485,7 +507,7 @@ namespace Barotrauma
|
||||
if (range is > 0 and < float.PositiveInfinity)
|
||||
{
|
||||
// Y distance is irrelevant when we are on the same floor. If we are on a different floor, let's double it.
|
||||
float yDiff = Math.Abs(toItem.Y) > floorHeightApproximate ? toItem.Y * 2 : 0;
|
||||
float yDiff = Math.Abs(toItem.Y) > FloorHeightApproximate ? toItem.Y * 2 : 0;
|
||||
Vector2 adjustedDiff = new Vector2(toItem.X, yDiff);
|
||||
if (adjustedDiff.LengthSquared() > MathUtils.Pow2(range))
|
||||
{
|
||||
@@ -501,7 +523,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (i.CurrentHull != null && !HumanAIController.VisibleHulls.Contains(i.CurrentHull))
|
||||
{
|
||||
if (Math.Abs(toItem.Y) > floorHeightApproximate && Math.Abs(toEnemy.Y) > floorHeightApproximate)
|
||||
if (Math.Abs(toItem.Y) > FloorHeightApproximate && Math.Abs(toEnemy.Y) > FloorHeightApproximate)
|
||||
{
|
||||
if (Math.Sign(toItem.Y) == Math.Sign(toEnemy.Y))
|
||||
{
|
||||
@@ -522,7 +544,7 @@ namespace Barotrauma
|
||||
SpeakNoWeapons();
|
||||
Mode = CombatMode.Retreat;
|
||||
}
|
||||
else if (!objectiveManager.HasActiveObjective<AIObjectiveFightIntruders>())
|
||||
else if (!objectiveManager.HasObjectiveOrOrder<AIObjectiveFightIntruders>())
|
||||
{
|
||||
// Poor weapon equipped
|
||||
Mode = CombatMode.Defensive;
|
||||
@@ -642,7 +664,7 @@ namespace Barotrauma
|
||||
priority /= 2;
|
||||
}
|
||||
}
|
||||
else if (Enemy.IsKnockedDown)
|
||||
else if (Enemy.IsKnockedDown && Mode != CombatMode.Arrest)
|
||||
{
|
||||
// Enemy is stunned, reduce the priority of stunner weapons.
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
@@ -775,7 +797,7 @@ namespace Barotrauma
|
||||
float bestPriority = 0;
|
||||
float lethalDmg = -1;
|
||||
bool prioritizeMelee = IsEnemyClose(50) || EnemyAIController.IsLatchedTo(Enemy, character);
|
||||
bool isCloseToEnemy = prioritizeMelee || IsEnemyClose(closeDistanceThreshold);
|
||||
bool isCloseToEnemy = prioritizeMelee || IsEnemyClose(CloseDistanceThreshold);
|
||||
foreach (var weapon in weaponList)
|
||||
{
|
||||
float priority = GetWeaponPriority(weapon, prioritizeMelee, canSeekAmmo: !isCloseToEnemy, out lethalDmg);
|
||||
@@ -801,9 +823,28 @@ namespace Barotrauma
|
||||
}
|
||||
isLethalWeapon = lethalDmg > 1;
|
||||
}
|
||||
if (AllowHoldFire && !hasAimed && holdFireTimer <= 0)
|
||||
if (AllowHoldFire)
|
||||
{
|
||||
holdFireTimer = arrestHoldFireTime * Rand.Range(0.75f, 1.25f);
|
||||
if (!hasAimed && holdFireTimer <= 0)
|
||||
{
|
||||
holdFireTimer = ArrestHoldFireTime * Rand.Range(0.9f, 1.1f);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SpeakWarnings)
|
||||
{
|
||||
if (!lastWarningTriggered && holdFireTimer < ArrestHoldFireTime * 0.3f)
|
||||
{
|
||||
FriendlyGuardSpeak("dialogarrest.lastwarning".ToIdentifier(), delay: 0, minDurationBetweenSimilar: 0f);
|
||||
lastWarningTriggered = true;
|
||||
}
|
||||
else if (!firstWarningTriggered && holdFireTimer < ArrestHoldFireTime * 0.8f)
|
||||
{
|
||||
FriendlyGuardSpeak("dialogarrest.firstwarning".ToIdentifier(), delay: 0, minDurationBetweenSimilar: 0f);
|
||||
firstWarningTriggered = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return weaponComponent.Item;
|
||||
@@ -909,9 +950,10 @@ namespace Barotrauma
|
||||
|
||||
private void Retreat(float deltaTime)
|
||||
{
|
||||
if (!Enemy.IsHuman)
|
||||
if (!Enemy.IsHuman && !character.IsInFriendlySub)
|
||||
{
|
||||
SpeakRetreating();
|
||||
// Only relevant when we are retreating from monsters and are not inside a friendly sub.
|
||||
PlayerCrewSpeak("dialogcombatretreating".ToIdentifier(), delay: Rand.Range(0f, 1f), minDurationBetweenSimilar: 20);
|
||||
}
|
||||
RemoveFollowTarget();
|
||||
RemoveSubObjective(ref seekAmmunitionObjective);
|
||||
@@ -931,7 +973,7 @@ namespace Barotrauma
|
||||
{
|
||||
RemoveSubObjective(ref retreatObjective);
|
||||
}
|
||||
if (character.Submarine == null && sqrDistance < MathUtils.Pow2(maxDistance))
|
||||
if (character.Submarine == null && sqrDistance < MathUtils.Pow2(MaxDistance))
|
||||
{
|
||||
// Swim away
|
||||
SteeringManager.Reset();
|
||||
@@ -1017,6 +1059,16 @@ namespace Barotrauma
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (character.TeamID == CharacterTeamType.FriendlyNPC && character.Submarine != null && character.Submarine.TeamID != character.TeamID)
|
||||
{
|
||||
// An outpost guard following the target (possibly a player) to another sub -> don't go further, unless can see the enemy.
|
||||
if (!character.IsClimbing && !character.CanSeeTarget(Enemy))
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
RemoveFollowTarget();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (followTargetObjective != null && followTargetObjective.Target != Enemy)
|
||||
{
|
||||
RemoveFollowTarget();
|
||||
@@ -1045,32 +1097,27 @@ namespace Barotrauma
|
||||
}
|
||||
});
|
||||
if (followTargetObjective == null) { return; }
|
||||
if (Mode == CombatMode.Arrest && Enemy.IsKnockedDown)
|
||||
if (Mode == CombatMode.Arrest && Enemy.IsKnockedDown && !arrestingRegistered)
|
||||
{
|
||||
if (HumanAIController.HasItem(character, Tags.HandLockerItem, out _))
|
||||
bool hasHandCuffs = HumanAIController.HasItem(character, Tags.HandLockerItem, out _);
|
||||
if (!hasHandCuffs && character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
if (!arrestingRegistered)
|
||||
// Spawn handcuffs
|
||||
ItemPrefab prefab = ItemPrefab.Find(null, "handcuffs".ToIdentifier());
|
||||
if (prefab != null)
|
||||
{
|
||||
arrestingRegistered = true;
|
||||
followTargetObjective.Completed += OnArrestTargetReached;
|
||||
followTargetObjective.CloseEnough = 100;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
ItemPrefab prefab = ItemPrefab.Find(null, "handcuffs".ToIdentifier());
|
||||
if (prefab != null)
|
||||
Entity.Spawner.AddItemToSpawnQueue(prefab, character.Inventory, onSpawned: i =>
|
||||
{
|
||||
Entity.Spawner.AddItemToSpawnQueue(prefab, character.Inventory, onSpawned: (Item i) => i.SpawnedInCurrentOutpost = true);
|
||||
}
|
||||
i.SpawnedInCurrentOutpost = true;
|
||||
i.AllowStealing = false;
|
||||
});
|
||||
}
|
||||
RemoveFollowTarget();
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
arrestingRegistered = true;
|
||||
followTargetObjective.Completed += OnArrestTargetReached;
|
||||
followTargetObjective.CloseEnough = ArrestTargetDistance;
|
||||
}
|
||||
if (!arrestingRegistered && followTargetObjective != null)
|
||||
if (!arrestingRegistered)
|
||||
{
|
||||
followTargetObjective.CloseEnough =
|
||||
WeaponComponent switch
|
||||
@@ -1083,8 +1130,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool arrestingRegistered;
|
||||
|
||||
private void RemoveFollowTarget()
|
||||
{
|
||||
if (followTargetObjective != null)
|
||||
@@ -1110,9 +1155,9 @@ namespace Barotrauma
|
||||
// Confiscate stolen goods and all weapons
|
||||
foreach (var item in Enemy.Inventory.AllItemsMod)
|
||||
{
|
||||
if (character.TeamID == CharacterTeamType.FriendlyNPC && item.StolenDuringRound ||
|
||||
item.HasTag(Tags.Weapon) || item.HasTag(Tags.Poison) ||
|
||||
GetWeaponComponent(item) is { CombatPriority: > 0 })
|
||||
// Ignore handcuffs already on the target.
|
||||
if (item.HasTag(Tags.HandLockerItem) && Enemy.HasEquippedItem(item)) { continue; }
|
||||
if (item.Illegitimate || item.HasTag(Tags.Weapon) || item.HasTag(Tags.Poison) || GetWeaponComponent(item) is { CombatPriority: > 0 })
|
||||
{
|
||||
item.Drop(character);
|
||||
character.Inventory.TryPutItem(item, character, CharacterInventory.AnySlot);
|
||||
@@ -1255,7 +1300,7 @@ namespace Barotrauma
|
||||
if (visibilityCheckTimer <= 0.0f)
|
||||
{
|
||||
canSeeTarget = character.CanSeeTarget(Enemy);
|
||||
visibilityCheckTimer = visibilityCheckInterval;
|
||||
visibilityCheckTimer = VisibilityCheckInterval;
|
||||
}
|
||||
if (!canSeeTarget)
|
||||
{
|
||||
@@ -1267,7 +1312,7 @@ namespace Barotrauma
|
||||
character.SetInput(InputType.Aim, hit: false, held: true);
|
||||
}
|
||||
hasAimed = true;
|
||||
if (holdFireTimer > 0)
|
||||
if (AllowHoldFire && holdFireTimer > 0)
|
||||
{
|
||||
holdFireTimer -= deltaTime;
|
||||
return;
|
||||
@@ -1280,7 +1325,7 @@ namespace Barotrauma
|
||||
if (reloadTimer > 0) { return; }
|
||||
if (holdFireCondition != null && holdFireCondition()) { return; }
|
||||
sqrDistance = Vector2.DistanceSquared(character.WorldPosition, Enemy.WorldPosition);
|
||||
distanceTimer = distanceCheckInterval;
|
||||
distanceTimer = DistanceCheckInterval;
|
||||
if (WeaponComponent is MeleeWeapon meleeWeapon)
|
||||
{
|
||||
bool closeEnough = true;
|
||||
@@ -1354,8 +1399,8 @@ namespace Barotrauma
|
||||
|
||||
private void UseWeapon(float deltaTime)
|
||||
{
|
||||
// Never allow to attack characters with deadly weapons while trying to arrest.
|
||||
if (Mode == CombatMode.Arrest && isLethalWeapon) { return; }
|
||||
// Never allow friendly crew (bots) to attack with deadly weapons.
|
||||
if (Mode == CombatMode.Arrest && isLethalWeapon && character.IsOnPlayerTeam && Enemy.IsOnPlayerTeam) { return; }
|
||||
character.SetInput(InputType.Shoot, hit: false, held: true);
|
||||
Weapon.Use(deltaTime, user: character);
|
||||
SetReloadTime(WeaponComponent);
|
||||
@@ -1408,6 +1453,36 @@ namespace Barotrauma
|
||||
protected override void OnCompleted()
|
||||
{
|
||||
base.OnCompleted();
|
||||
if (Enemy != null)
|
||||
{
|
||||
switch (Mode)
|
||||
{
|
||||
case CombatMode.Offensive when Enemy.IsUnconscious && objectiveManager.HasObjectiveOrOrder<AIObjectiveFightIntruders>():
|
||||
character.Speak(TextManager.Get("DialogTargetDown").Value, null, 3.0f, "targetdown".ToIdentifier(), 30.0f);
|
||||
break;
|
||||
case CombatMode.Arrest when IsCompleted:
|
||||
if (!HumanAIController.IsTrueForAnyBotInTheCrew(bot =>
|
||||
(bot != HumanAIController && bot.ObjectiveManager.CurrentObjective is AIObjectiveCombat { Mode: CombatMode.Arrest } combatObj && combatObj.Enemy == Enemy) ||
|
||||
bot.ObjectiveManager.CurrentObjective is AIObjectiveGoTo { SourceObjective: AIObjectiveCombat combatObjective } && combatObjective.Enemy == Enemy))
|
||||
{
|
||||
// Go to the target and confiscate any stolen items, unless someone is already on it.
|
||||
// Added on the root level, because the lifetime of the new objective exceeds the lifetime of this objective.
|
||||
RemoveFollowTarget();
|
||||
var approachArrestTarget = new AIObjectiveGoTo(Enemy, character, objectiveManager, repeat: false, getDivingGearIfNeeded: false, closeEnough: ArrestTargetDistance)
|
||||
{
|
||||
UsePathingOutside = false,
|
||||
IgnoreIfTargetDead = true,
|
||||
TargetName = Enemy.DisplayName,
|
||||
AlwaysUseEuclideanDistance = false,
|
||||
SpeakIfFails = false,
|
||||
SourceObjective = this
|
||||
};
|
||||
approachArrestTarget.Completed += OnArrestTargetReached;
|
||||
objectiveManager.AddObjective(approachArrestTarget);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ShouldUnequipWeapon)
|
||||
{
|
||||
Unequip();
|
||||
@@ -1424,11 +1499,23 @@ namespace Barotrauma
|
||||
}
|
||||
SteeringManager?.Reset();
|
||||
}
|
||||
|
||||
|
||||
public override void OnDeselected()
|
||||
{
|
||||
base.OnDeselected();
|
||||
if (character.TeamID == CharacterTeamType.FriendlyNPC && IsOffensiveOrArrest && (!AllowHoldFire || (hasAimed && holdFireTimer <= 0)))
|
||||
{
|
||||
// Remember that the target resisted or acted offensively (we've aimed or tried to arrest/attack)
|
||||
Enemy.IsCriminal = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
hasAimed = false;
|
||||
holdFireTimer = 0;
|
||||
pathBackTimer = 0;
|
||||
isLethalWeapon = false;
|
||||
canSeeTarget = false;
|
||||
seekWeaponObjective = null;
|
||||
@@ -1436,20 +1523,43 @@ namespace Barotrauma
|
||||
retreatObjective = null;
|
||||
followTargetObjective = null;
|
||||
retreatTarget = null;
|
||||
firstWarningTriggered = false;
|
||||
lastWarningTriggered = false;
|
||||
}
|
||||
|
||||
private void SpeakNoWeapons() => Speak("dialogcombatnoweapons".ToIdentifier(), delay: 0, minDuration: 30);
|
||||
private void SpeakRetreating() => Speak("dialogcombatretreating".ToIdentifier(), delay: Rand.Range(0f, 1f), minDuration: 20);
|
||||
|
||||
private void Speak(Identifier textIdentifier, float delay, float minDuration)
|
||||
/// <summary>
|
||||
/// Speak that we don't have weapons. But only outside of friendly subs (not that relevant there, reduces spam).
|
||||
/// </summary>
|
||||
private void SpeakNoWeapons()
|
||||
{
|
||||
if (character.IsOnPlayerTeam && !character.IsInFriendlySub)
|
||||
if (!character.IsInFriendlySub)
|
||||
{
|
||||
LocalizedString msg = TextManager.Get(textIdentifier);
|
||||
if (!msg.IsNullOrEmpty())
|
||||
{
|
||||
character.Speak(msg.Value, identifier: textIdentifier, delay: delay, minDurationBetweenSimilar: minDuration);
|
||||
}
|
||||
PlayerCrewSpeak("dialogcombatnoweapons".ToIdentifier(), delay: 0, minDurationBetweenSimilar: 30);
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayerCrewSpeak(Identifier textIdentifier, float delay, float minDurationBetweenSimilar)
|
||||
{
|
||||
if (character.IsOnPlayerTeam)
|
||||
{
|
||||
Speak(textIdentifier, delay, minDurationBetweenSimilar);
|
||||
}
|
||||
}
|
||||
|
||||
private void FriendlyGuardSpeak(Identifier textIdentifier, float delay, float minDurationBetweenSimilar)
|
||||
{
|
||||
if (character.TeamID == CharacterTeamType.FriendlyNPC && character.IsSecurity)
|
||||
{
|
||||
Speak(textIdentifier, delay, minDurationBetweenSimilar);
|
||||
}
|
||||
}
|
||||
|
||||
private void Speak(Identifier textIdentifier, float delay, float minDurationBetweenSimilar)
|
||||
{
|
||||
LocalizedString msg = TextManager.Get(textIdentifier);
|
||||
if (!msg.IsNullOrEmpty())
|
||||
{
|
||||
character.Speak(msg.Value, identifier: textIdentifier, delay: delay, minDurationBetweenSimilar: minDurationBetweenSimilar);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ namespace Barotrauma
|
||||
class AIObjectiveContainItem: AIObjective
|
||||
{
|
||||
public override Identifier Identifier { get; set; } = "contain item".ToIdentifier();
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
protected override bool AllowWhileHandcuffed => false;
|
||||
|
||||
public Func<Item, float> GetItemPriority;
|
||||
|
||||
|
||||
+2
-2
@@ -6,9 +6,9 @@ namespace Barotrauma
|
||||
class AIObjectiveDeconstructItem : AIObjective
|
||||
{
|
||||
public override Identifier Identifier { get; set; } = "deconstruct item".ToIdentifier();
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
protected override bool AllowWhileHandcuffed => false;
|
||||
|
||||
public override bool AllowInFriendlySubs => true;
|
||||
protected override bool AllowInFriendlySubs => true;
|
||||
|
||||
public readonly Item Item;
|
||||
|
||||
|
||||
+3
-3
@@ -11,7 +11,7 @@ namespace Barotrauma
|
||||
//Clear periodically, because we may ending up ignoring items when all deconstructors are full
|
||||
protected override float IgnoreListClearInterval => 30;
|
||||
|
||||
public override bool AllowInFriendlySubs => true;
|
||||
protected override bool AllowInFriendlySubs => true;
|
||||
|
||||
protected override int MaxTargets => 10;
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Barotrauma
|
||||
checkedDeconstructorExists = false;
|
||||
}
|
||||
|
||||
protected override float TargetEvaluation()
|
||||
protected override float GetTargetPriority()
|
||||
{
|
||||
if (Targets.None()) { return 0; }
|
||||
if (objectiveManager.IsOrder(this))
|
||||
@@ -57,7 +57,7 @@ namespace Barotrauma
|
||||
return AIObjectiveManager.RunPriority - 0.5f;
|
||||
}
|
||||
|
||||
protected override bool Filter(Item target)
|
||||
protected override bool IsValidTarget(Item target)
|
||||
{
|
||||
// If the target was selected as a valid target, we'll have to accept it so that the objective can be completed.
|
||||
// The validity changes when a character picks the item up.
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ namespace Barotrauma
|
||||
class AIObjectiveDecontainItem : AIObjective
|
||||
{
|
||||
public override Identifier Identifier { get; set; } = "decontain item".ToIdentifier();
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
protected override bool AllowWhileHandcuffed => false;
|
||||
|
||||
public Func<Item, float> GetItemPriority;
|
||||
|
||||
|
||||
+2
-3
@@ -8,8 +8,8 @@ namespace Barotrauma
|
||||
// Used for prisoner escorts to allow them to escape their binds
|
||||
public override Identifier Identifier { get; set; } = "escape handcuffs".ToIdentifier();
|
||||
public override bool AllowAutomaticItemUnequipping => true;
|
||||
public override bool AllowOutsideSubmarine => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
protected override bool AllowOutsideSubmarine => true;
|
||||
protected override bool AllowInAnySub => true;
|
||||
|
||||
private int escapeProgress;
|
||||
private bool isBeingWatched;
|
||||
@@ -28,7 +28,6 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public override bool CanBeCompleted => true;
|
||||
public override bool IsLoop { get => true; set => throw new Exception("Trying to set the value for IsLoop from: " + Environment.StackTrace.CleanupStackTrace()); }
|
||||
protected override bool CheckObjectiveSpecific() => false;
|
||||
|
||||
// escape timer is set to 60 by default to allow players to locate prisoners in time
|
||||
|
||||
+4
-6
@@ -10,12 +10,10 @@ namespace Barotrauma
|
||||
{
|
||||
public override Identifier Identifier { get; set; } = "extinguish fire".ToIdentifier();
|
||||
public override bool ForceRun => true;
|
||||
public override bool ConcurrentObjectives => true;
|
||||
protected override bool ConcurrentObjectives => true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
|
||||
public override bool AllowInAnySub => true;
|
||||
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
protected override bool AllowInAnySub => true;
|
||||
protected override bool AllowWhileHandcuffed => false;
|
||||
|
||||
private readonly Hull targetHull;
|
||||
|
||||
@@ -32,7 +30,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
HandleNonAllowed();
|
||||
HandleDisallowed();
|
||||
return Priority;
|
||||
}
|
||||
bool isOrder = objectiveManager.HasOrder<AIObjectiveExtinguishFires>();
|
||||
|
||||
+3
-3
@@ -10,13 +10,13 @@ namespace Barotrauma
|
||||
{
|
||||
public override Identifier Identifier { get; set; } = "extinguish fires".ToIdentifier();
|
||||
public override bool ForceRun => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
protected override bool AllowInAnySub => true;
|
||||
|
||||
public AIObjectiveExtinguishFires(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
|
||||
|
||||
protected override bool Filter(Hull hull) => IsValidTarget(hull, character);
|
||||
protected override bool IsValidTarget(Hull hull) => IsValidTarget(hull, character);
|
||||
|
||||
protected override float TargetEvaluation() =>
|
||||
protected override float GetTargetPriority() =>
|
||||
// If any target is visible -> 100 priority
|
||||
Targets.Any(t => t == character.CurrentHull || HumanAIController.VisibleHulls.Contains(t)) ? 100 :
|
||||
// Else based on the fire severity
|
||||
|
||||
+7
-9
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -9,19 +9,17 @@ namespace Barotrauma
|
||||
public override Identifier Identifier { get; set; } = "fight intruders".ToIdentifier();
|
||||
protected override float IgnoreListClearInterval => 30;
|
||||
public override bool IgnoreUnsafeHulls => true;
|
||||
|
||||
protected override float TargetUpdateTimeMultiplier => 0.2f;
|
||||
|
||||
public bool TargetCharactersInOtherSubs { get; init; }
|
||||
|
||||
public AIObjectiveFightIntruders(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
public AIObjectiveFightIntruders(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier) { }
|
||||
|
||||
protected override bool Filter(Character target) => IsValidTarget(target, character, TargetCharactersInOtherSubs);
|
||||
protected override bool IsValidTarget(Character target) => IsValidTarget(target, character, TargetCharactersInOtherSubs);
|
||||
|
||||
protected override IEnumerable<Character> GetList() => Character.CharacterList;
|
||||
|
||||
protected override float TargetEvaluation()
|
||||
protected override float GetTargetPriority()
|
||||
{
|
||||
if (Targets.None()) { return 0; }
|
||||
if (!character.IsOnPlayerTeam && !character.IsOriginallyOnPlayerTeam) { return 100; }
|
||||
@@ -68,14 +66,14 @@ namespace Barotrauma
|
||||
if (HumanAIController.IsFriendly(character, target)) { return false; }
|
||||
if (!character.Submarine.IsConnectedTo(target.Submarine)) { return false; }
|
||||
if (!targetCharactersInOtherSubs)
|
||||
{
|
||||
{
|
||||
if (character.Submarine.TeamID != target.Submarine.TeamID && character.OriginalTeamID != target.Submarine.TeamID)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (target.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { return false; }
|
||||
if (target.IsArrested) { return false; }
|
||||
if (target.IsHandcuffed && target.IsKnockedDown) { return false; }
|
||||
if (EnemyAIController.IsLatchedToSomeoneElse(target, character)) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
+2
-2
@@ -11,8 +11,8 @@ namespace Barotrauma
|
||||
public override string DebugTag => $"{Identifier} ({gearTag})";
|
||||
public override bool ForceRun => true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool AbandonWhenCannotCompleteSubjectives => false;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
public override bool AbandonWhenCannotCompleteSubObjectives => false;
|
||||
protected override bool AllowWhileHandcuffed => false;
|
||||
|
||||
private readonly Identifier gearTag;
|
||||
|
||||
|
||||
+18
-20
@@ -13,18 +13,16 @@ namespace Barotrauma
|
||||
public override bool ForceRun => true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool IgnoreUnsafeHulls => true;
|
||||
public override bool ConcurrentObjectives => true;
|
||||
public override bool AllowOutsideSubmarine => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
public override bool AbandonWhenCannotCompleteSubjectives => false;
|
||||
public override bool IsLoop { get => true; set => throw new Exception("Trying to set the value for IsLoop from: " + Environment.StackTrace.CleanupStackTrace()); }
|
||||
protected override bool ConcurrentObjectives => true;
|
||||
protected override bool AllowOutsideSubmarine => true;
|
||||
protected override bool AllowInAnySub => true;
|
||||
public override bool AbandonWhenCannotCompleteSubObjectives => false;
|
||||
|
||||
// TODO: expose?
|
||||
const float priorityIncrease = 100;
|
||||
const float priorityDecrease = 10;
|
||||
const float SearchHullInterval = 3.0f;
|
||||
private const float PriorityIncrease = 100;
|
||||
private const float PriorityDecrease = 10;
|
||||
private const float SearchHullInterval = 3.0f;
|
||||
|
||||
private float currenthullSafety;
|
||||
private float currentHullSafety;
|
||||
|
||||
private float searchHullTimer;
|
||||
|
||||
@@ -111,15 +109,15 @@ namespace Barotrauma
|
||||
}
|
||||
if (character.CurrentHull == null)
|
||||
{
|
||||
currenthullSafety = 0;
|
||||
currentHullSafety = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
currenthullSafety = HumanAIController.CurrentHullSafety;
|
||||
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
currentHullSafety = HumanAIController.CurrentHullSafety;
|
||||
if (currentHullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
Priority -= priorityDecrease * deltaTime;
|
||||
if (currenthullSafety >= 100 && !character.IsLowInOxygen)
|
||||
Priority -= PriorityDecrease * deltaTime;
|
||||
if (currentHullSafety >= 100 && !character.IsLowInOxygen)
|
||||
{
|
||||
// Reduce the priority to zero so that the bot can get switch to other objectives immediately, e.g. when entering the airlock.
|
||||
Priority = 0;
|
||||
@@ -127,8 +125,8 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
float dangerFactor = (100 - currenthullSafety) / 100;
|
||||
Priority += dangerFactor * priorityIncrease * deltaTime;
|
||||
float dangerFactor = (100 - currentHullSafety) / 100;
|
||||
Priority += dangerFactor * PriorityIncrease * deltaTime;
|
||||
}
|
||||
Priority = MathHelper.Clamp(Priority, 0, AIObjectiveManager.MaxObjectivePriority);
|
||||
}
|
||||
@@ -192,7 +190,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (divingGearObjective == null || !divingGearObjective.CanBeCompleted)
|
||||
{
|
||||
if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
if (currentHullSafety < HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
searchHullTimer = Math.Min(1, searchHullTimer);
|
||||
}
|
||||
@@ -231,7 +229,7 @@ namespace Barotrauma
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD ||
|
||||
if (currentHullSafety > HumanAIController.HULL_SAFETY_THRESHOLD ||
|
||||
HumanAIController.NeedsDivingGear(currentHull, out bool needsSuit) && (needsSuit ? HumanAIController.HasDivingSuit(character) : HumanAIController.HasDivingMask(character)))
|
||||
{
|
||||
resetPriority = true;
|
||||
@@ -299,7 +297,7 @@ namespace Barotrauma
|
||||
}
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
if (!HumanAIController.IsActive(enemy) || HumanAIController.IsFriendly(enemy) || enemy.IsArrested) { continue; }
|
||||
if (!HumanAIController.IsActive(enemy) || HumanAIController.IsFriendly(enemy) || enemy.IsHandcuffed) { continue; }
|
||||
if (HumanAIController.VisibleHulls.Contains(enemy.CurrentHull))
|
||||
{
|
||||
Vector2 dir = character.Position - enemy.Position;
|
||||
|
||||
+67
-16
@@ -2,6 +2,7 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Items.Components;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -13,17 +14,33 @@ namespace Barotrauma
|
||||
|
||||
protected override float TargetUpdateTimeMultiplier => 1.0f;
|
||||
|
||||
const float DefaultInspectDistance = 200.0f;
|
||||
/// <summary>
|
||||
/// How long the round must have ran before NPCs can start doing inspections
|
||||
/// (prevents "unfair" inspections you have no chance to react to if you happen to spawn right next to a security NPC with stolen items on you)
|
||||
/// </summary>
|
||||
private const float DelayOnRoundStart = 30.0f;
|
||||
|
||||
private const float DefaultInspectDistance = 200.0f;
|
||||
/// <summary>
|
||||
/// Used when something is stolen and when the guards decide to inspect everyone.
|
||||
/// </summary>
|
||||
private const float ExtendedInspectDistance = 400.0f;
|
||||
/// <summary>
|
||||
/// Used when the target is tagged as a criminal (= suspective).
|
||||
/// </summary>
|
||||
private const float CriminalInspectDistance = 500.0f;
|
||||
|
||||
private const float CriminalInspectProbability = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// How close the NPC must be to the target to the inspect them? You can use high values to make the NPC
|
||||
/// systematically go through targets no matter where they are, and low values to check targets they happen to come across.
|
||||
/// </summary>
|
||||
public float InspectDistance = DefaultInspectDistance;
|
||||
private float inspectDistance = DefaultInspectDistance;
|
||||
|
||||
private float? overrideInspectProbability;
|
||||
/// <summary>
|
||||
/// Chance of inspecting a valid target. The NPC won't try to inspect that target again for <see cref="inspectionInterval"/>
|
||||
/// Chance of inspecting a valid target. The NPC won't try to inspect that target again for <see cref="NormalInspectionInterval"/>
|
||||
/// regardless if the target is inspected or not.
|
||||
/// </summary>
|
||||
public float InspectProbability
|
||||
@@ -53,18 +70,25 @@ namespace Barotrauma
|
||||
/// When did the character last inspect whether some other character has stolen items on them?
|
||||
/// </summary>
|
||||
private static readonly Dictionary<Character, double> lastInspectionTimes = new Dictionary<Character, double>();
|
||||
|
||||
private readonly float inspectionInterval = 120.0f;
|
||||
|
||||
|
||||
private const float NormalInspectionInterval = 120.0f;
|
||||
private const float CriminalInspectionInterval = 30.0f;
|
||||
|
||||
public AIObjectiveFindThieves(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier) { }
|
||||
|
||||
protected override bool Filter(Character target)
|
||||
protected override bool IsValidTarget(Character target)
|
||||
{
|
||||
if (GameMain.GameSession is not { RoundDuration: > DelayOnRoundStart })
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!IsValidTarget(target, character)) { return false; }
|
||||
if (Vector2.DistanceSquared(target.WorldPosition, character.WorldPosition) > InspectDistance * InspectDistance) { return false; }
|
||||
float inspectDist = target.IsCriminal ? CriminalInspectDistance : inspectDistance;
|
||||
if (Vector2.DistanceSquared(target.WorldPosition, character.WorldPosition) > inspectDist * inspectDist) { return false; }
|
||||
if (lastInspectionTimes.TryGetValue(target, out double lastInspectionTime))
|
||||
{
|
||||
float inspectionInterval = target.IsCriminal ? CriminalInspectionInterval : NormalInspectionInterval;
|
||||
if (Timing.TotalTime < lastInspectionTime + inspectionInterval)
|
||||
{
|
||||
return false;
|
||||
@@ -75,8 +99,14 @@ namespace Barotrauma
|
||||
|
||||
protected override IEnumerable<Character> GetList() => Character.CharacterList;
|
||||
|
||||
protected override float TargetEvaluation()
|
||||
protected override float GetTargetPriority()
|
||||
{
|
||||
if (character.IsClimbing)
|
||||
{
|
||||
// Don't inspect while climbing, because cannot grab while holding the ladders.
|
||||
// Can lead to abandoning the objective when we need to climb the ladders to get to the target, but I think that's acceptable.
|
||||
return 0;
|
||||
}
|
||||
return subObjectives.Any() ? 50 : 0;
|
||||
}
|
||||
|
||||
@@ -84,13 +114,14 @@ namespace Barotrauma
|
||||
{
|
||||
lastInspectionTimes.Clear();
|
||||
overrideInspectProbability = 1.0f;
|
||||
InspectDistance = DefaultInspectDistance * 2;
|
||||
inspectDistance = ExtendedInspectDistance;
|
||||
}
|
||||
|
||||
protected override AIObjective ObjectiveConstructor(Character target)
|
||||
{
|
||||
var checkStolenItemsObjective = new AIObjectiveCheckStolenItems(character, target, objectiveManager);
|
||||
if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Unsynced) >= InspectProbability)
|
||||
float probabity = target.IsCriminal ? CriminalInspectProbability : InspectProbability;
|
||||
if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Unsynced) >= probabity)
|
||||
{
|
||||
checkStolenItemsObjective.ForceComplete();
|
||||
lastInspectionTimes[target] = Timing.TotalTime;
|
||||
@@ -104,26 +135,30 @@ namespace Barotrauma
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
if (checkVisibleStolenItemsTimer > 0.0f)
|
||||
if (checkVisibleStolenItemsTimer > 0.0f || character.IsClimbing)
|
||||
{
|
||||
checkVisibleStolenItemsTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
if (character.SelectedSecondaryItem?.GetComponent<Controller>() != null)
|
||||
{
|
||||
// Might be e.g. sitting on a chair.
|
||||
character.SelectedSecondaryItem = null;
|
||||
}
|
||||
foreach (var target in Character.CharacterList)
|
||||
{
|
||||
if (!IsValidTarget(target, character)) { continue; }
|
||||
//if we spot someone wearing or holding stolen items, immediately check them (with 100% chance of spotting the stolen items)
|
||||
if (target.Inventory.AllItems.Any(it => it.SpawnedInCurrentOutpost && !it.AllowStealing && target.HasEquippedItem(it)) &&
|
||||
if (target.Inventory.AllItems.Any(it => it.Illegitimate && target.HasEquippedItem(it)) &&
|
||||
character.CanSeeTarget(target, seeThroughWindows: true))
|
||||
{
|
||||
AIObjectiveCheckStolenItems? existingObjective =
|
||||
objectiveManager.GetActiveObjectives<AIObjectiveCheckStolenItems>().FirstOrDefault(o => o.TargetCharacter == target);
|
||||
objectiveManager.GetActiveObjectives<AIObjectiveCheckStolenItems>().FirstOrDefault(o => o.Target == target);
|
||||
if (existingObjective == null)
|
||||
{
|
||||
objectiveManager.AddObjective(new AIObjectiveCheckStolenItems(character, target, objectiveManager));
|
||||
lastInspectionTimes[target] = Timing.TotalTime;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
checkVisibleStolenItemsTimer = CheckVisibleStolenItemsInterval;
|
||||
@@ -140,7 +175,17 @@ namespace Barotrauma
|
||||
if (target.Submarine != character.Submarine) { return false; }
|
||||
//only player's crew can steal, ignore other teams
|
||||
if (!target.IsOnPlayerTeam) { return false; }
|
||||
if (target.IsArrested) { return false; }
|
||||
if (target.IsHandcuffed) { return false; }
|
||||
// Ignore targets that are climbing, because might need to use ladders to get to them.
|
||||
if (target.IsClimbing) { return false; }
|
||||
if (HumanAIController.IsTrueForAnyBotInTheCrew(bot =>
|
||||
bot != HumanAIController &&
|
||||
((bot.ObjectiveManager.GetActiveObjective() is AIObjectiveCheckStolenItems checkObj && checkObj.Target == target) ||
|
||||
(bot.ObjectiveManager.GetActiveObjective() is AIObjectiveCombat combatObj && combatObj.Enemy == target))))
|
||||
{
|
||||
// Already being inspected by someone or fighting with someone in our team.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -148,5 +193,11 @@ namespace Barotrauma
|
||||
{
|
||||
lastInspectionTimes[target] = Timing.TotalTime;
|
||||
}
|
||||
|
||||
public override void OnDeselected()
|
||||
{
|
||||
base.OnDeselected();
|
||||
character.DeselectCharacter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -12,9 +12,9 @@ namespace Barotrauma
|
||||
public override Identifier Identifier { get; set; } = "fix leak".ToIdentifier();
|
||||
public override bool ForceRun => true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool AllowInFriendlySubs => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
protected override bool AllowInFriendlySubs => true;
|
||||
protected override bool AllowInAnySub => true;
|
||||
protected override bool AllowWhileHandcuffed => false;
|
||||
|
||||
public Gap Leak { get; private set; }
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
HandleNonAllowed();
|
||||
HandleDisallowed();
|
||||
return Priority;
|
||||
}
|
||||
float coopMultiplier = 1;
|
||||
|
||||
+3
-3
@@ -9,7 +9,7 @@ namespace Barotrauma
|
||||
public override Identifier Identifier { get; set; } = "fix leaks".ToIdentifier();
|
||||
public override bool ForceRun => true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool AllowInFriendlySubs => true;
|
||||
protected override bool AllowInFriendlySubs => true;
|
||||
|
||||
private Hull PrioritizedHull { get; set; }
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Barotrauma
|
||||
PrioritizedHull = prioritizedHull;
|
||||
}
|
||||
|
||||
protected override bool Filter(Gap gap) => IsValidTarget(gap, character);
|
||||
protected override bool IsValidTarget(Gap gap) => IsValidTarget(gap, character);
|
||||
|
||||
public static float GetLeakSeverity(Gap leak)
|
||||
{
|
||||
@@ -37,7 +37,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
protected override float TargetEvaluation()
|
||||
protected override float GetTargetPriority()
|
||||
{
|
||||
int totalLeaks = Targets.Count;
|
||||
if (totalLeaks == 0) { return 0; }
|
||||
|
||||
+3
-3
@@ -13,9 +13,9 @@ namespace Barotrauma
|
||||
{
|
||||
public override Identifier Identifier { get; set; } = "get item".ToIdentifier();
|
||||
|
||||
public override bool AbandonWhenCannotCompleteSubjectives => false;
|
||||
public override bool AbandonWhenCannotCompleteSubObjectives => false;
|
||||
public override bool AllowMultipleInstances => true;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
protected override bool AllowWhileHandcuffed => false;
|
||||
|
||||
public HashSet<Item> ignoredItems = new HashSet<Item>();
|
||||
|
||||
@@ -444,7 +444,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (!AllowStealing && character.IsOnPlayerTeam)
|
||||
{
|
||||
if (item.SpawnedInCurrentOutpost && !item.AllowStealing) { continue; }
|
||||
if (item.Illegitimate) { continue; }
|
||||
}
|
||||
if (!CheckItem(item)) { continue; }
|
||||
if (item.Container != null)
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ namespace Barotrauma
|
||||
public override string DebugTag => $"{Identifier}";
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool AllowMultipleInstances => true;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
protected override bool AllowWhileHandcuffed => false;
|
||||
|
||||
public bool AllowStealing { get; set; }
|
||||
public bool TakeWholeStack { get; set; }
|
||||
|
||||
+27
-21
@@ -16,7 +16,7 @@ namespace Barotrauma
|
||||
private readonly bool repeat;
|
||||
//how long until the path to the target is declared unreachable
|
||||
private float waitUntilPathUnreachable;
|
||||
private bool getDivingGearIfNeeded;
|
||||
private readonly bool getDivingGearIfNeeded;
|
||||
|
||||
/// <summary>
|
||||
/// Doesn't allow the objective to complete if this condition is false
|
||||
@@ -34,11 +34,6 @@ namespace Barotrauma
|
||||
public bool DebugLogWhenFails { get; set; } = true;
|
||||
public bool UsePathingOutside { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Which event action created this objective (if any)
|
||||
/// </summary>
|
||||
public EventAction SourceEventAction;
|
||||
|
||||
public float ExtraDistanceWhileSwimming;
|
||||
public float ExtraDistanceOutsideSub;
|
||||
private float _closeEnoughMultiplier = 1;
|
||||
@@ -94,10 +89,10 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public bool UseDistanceRelativeToAimSourcePos { get; set; } = false;
|
||||
|
||||
public override bool AbandonWhenCannotCompleteSubjectives => false;
|
||||
public override bool AbandonWhenCannotCompleteSubObjectives => false;
|
||||
|
||||
public override bool AllowOutsideSubmarine => AllowGoingOutside;
|
||||
public override bool AllowInAnySub => true;
|
||||
protected override bool AllowOutsideSubmarine => AllowGoingOutside;
|
||||
protected override bool AllowInAnySub => true;
|
||||
|
||||
public Identifier DialogueIdentifier { get; set; } = "dialogcannotreachtarget".ToIdentifier();
|
||||
public LocalizedString TargetName { get; set; }
|
||||
@@ -287,10 +282,12 @@ namespace Barotrauma
|
||||
if (waitUntilPathUnreachable < 0)
|
||||
{
|
||||
waitUntilPathUnreachable = pathWaitingTime;
|
||||
if (repeat)
|
||||
if (repeat && !IsCompleted)
|
||||
{
|
||||
SpeakCannotReach();
|
||||
return;
|
||||
if (!IsDoneFollowing())
|
||||
{
|
||||
SpeakCannotReach();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -374,16 +371,10 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (repeat && IsCloseEnough)
|
||||
if (IsDoneFollowing())
|
||||
{
|
||||
if (requiredCondition == null || requiredCondition())
|
||||
{
|
||||
if (character.CanSeeTarget(Target) && (!character.IsClimbing || IsFollowOrder))
|
||||
{
|
||||
OnCompleted();
|
||||
return;
|
||||
}
|
||||
}
|
||||
OnCompleted();
|
||||
return;
|
||||
}
|
||||
float maxGapDistance = 500;
|
||||
Character targetCharacter = Target as Character;
|
||||
@@ -653,6 +644,21 @@ namespace Barotrauma
|
||||
character.SetInput(InputType.Aim, false, true);
|
||||
character.SetInput(InputType.Shoot, false, true);
|
||||
}
|
||||
|
||||
bool IsDoneFollowing()
|
||||
{
|
||||
if (repeat && IsCloseEnough)
|
||||
{
|
||||
if (requiredCondition == null || requiredCondition())
|
||||
{
|
||||
if (character.CanSeeTarget(Target) && (!character.IsClimbing || IsFollowOrder))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool useScooter;
|
||||
|
||||
+3
-5
@@ -12,7 +12,7 @@ namespace Barotrauma
|
||||
{
|
||||
public override Identifier Identifier { get; set; } = "idle".ToIdentifier();
|
||||
public override bool AllowAutomaticItemUnequipping => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
protected override bool AllowInAnySub => true;
|
||||
|
||||
private BehaviorType behavior;
|
||||
public BehaviorType Behavior
|
||||
@@ -91,8 +91,6 @@ namespace Barotrauma
|
||||
protected override bool CheckObjectiveSpecific() => false;
|
||||
public override bool CanBeCompleted => true;
|
||||
|
||||
public override bool IsLoop { get => true; set => throw new Exception("Trying to set the value for IsLoop from: " + Environment.StackTrace.CleanupStackTrace()); }
|
||||
|
||||
public readonly HashSet<Identifier> PreferredOutpostModuleTypes = new HashSet<Identifier>();
|
||||
|
||||
public void CalculatePriority(float max = 0)
|
||||
@@ -266,7 +264,7 @@ namespace Barotrauma
|
||||
if (node.Waypoint.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(node.Waypoint.CurrentHull)) { return false; }
|
||||
return true;
|
||||
//don't stop at ladders when idling
|
||||
}, endNodeFilter: node => node.Waypoint.Ladders == null && (!isCurrentHullAllowed || !IsForbidden(node.Waypoint.CurrentHull)));
|
||||
}, endNodeFilter: node => node.Waypoint.Stairs == null && node.Waypoint.Ladders == null && (!isCurrentHullAllowed || !IsForbidden(node.Waypoint.CurrentHull)));
|
||||
if (path.Unreachable)
|
||||
{
|
||||
//can't go to this room, remove it from the list and try another room
|
||||
@@ -299,7 +297,7 @@ namespace Barotrauma
|
||||
{
|
||||
PathSteering.SteeringSeek(character.GetRelativeSimPosition(currentTarget), weight: 1,
|
||||
nodeFilter: node => node.Waypoint.CurrentHull != null,
|
||||
endNodeFilter: node => node.Waypoint.Ladders == null);
|
||||
endNodeFilter: node => node.Waypoint.Ladders == null && node.Waypoint.Stairs == null);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveInspectNoises : AIObjective
|
||||
{
|
||||
public override Identifier Identifier { get; set; } = "inspect noises".ToIdentifier();
|
||||
|
||||
private AIObjectiveGoTo inspectNoiseObjective;
|
||||
|
||||
/// <summary>
|
||||
/// Initial priority of the objective to check noises made by enemies
|
||||
/// </summary>
|
||||
const float InspectNoisePriority = 10.0f;
|
||||
/// <summary>
|
||||
/// How much the priority of the objective to check noises made by enemies increases per noise
|
||||
/// </summary>
|
||||
const float InspectNoisePriorityIncrease = 10.0f;
|
||||
private const float InspectNoiseInterval = 1.0f;
|
||||
private float inspectNoiseTimer;
|
||||
|
||||
/// <summary>
|
||||
/// If the character is not currently inspecting the noise (= if some other objective is taking priority)
|
||||
/// it forgets about it after this delay runs out. Otherwise they might unnecessarily go and inspect some
|
||||
/// noise that was emitted a long time ago once done with the higher-prio objective.
|
||||
/// </summary>
|
||||
private const float InspectNoiseExpirationDelay = 60.0f;
|
||||
private float inspectNoiseExpirationTimer = 0.0f;
|
||||
|
||||
protected override float GetPriority() => inspectNoiseObjective?.Priority ?? 0.0f;
|
||||
|
||||
public AIObjectiveInspectNoises(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
inspectNoiseTimer = Rand.Range(0.0f, InspectNoiseInterval);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
inspectNoiseTimer -= deltaTime;
|
||||
if (inspectNoiseTimer <= 0.0f)
|
||||
{
|
||||
CheckEnemyNoises();
|
||||
inspectNoiseTimer = InspectNoiseInterval;
|
||||
}
|
||||
//if we're not currently inspecting the noise (something else taking priority), forget about it after a while
|
||||
if (inspectNoiseObjective != null && objectiveManager.GetActiveObjective() != inspectNoiseObjective)
|
||||
{
|
||||
inspectNoiseExpirationTimer += deltaTime;
|
||||
if (inspectNoiseExpirationTimer > InspectNoiseExpirationDelay)
|
||||
{
|
||||
inspectNoiseObjective.Abandon = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if there's any loud provocative items used by enemies nearby (= if someone fired a gun somewhere), and go inspect them
|
||||
/// </summary>
|
||||
private void CheckEnemyNoises()
|
||||
{
|
||||
if (character.CurrentHull == null) { return; }
|
||||
|
||||
//forget about inspecting if we're doing another subobjective (= fighting something)
|
||||
if (inspectNoiseObjective != null &&
|
||||
CurrentSubObjective != inspectNoiseObjective)
|
||||
{
|
||||
inspectNoiseObjective.Abandon = true;
|
||||
}
|
||||
|
||||
foreach (var aiTarget in AITarget.List)
|
||||
{
|
||||
if (aiTarget.ShouldBeIgnored()) { continue; }
|
||||
if (!aiTarget.IsWithinSector(character.WorldPosition)) { continue; }
|
||||
if (aiTarget.Entity is not Item item) { continue; }
|
||||
if (!item.HasTag(Tags.ProvocativeToHumanAI)) { continue; }
|
||||
if (item.GetRootInventoryOwner() is Character targetCharacter &&
|
||||
AIObjectiveFightIntruders.IsValidTarget(targetCharacter, character, targetCharactersInOtherSubs: false))
|
||||
{
|
||||
float dist = character.CurrentHull.GetApproximateDistance(character.Position, targetCharacter.Position, targetCharacter.CurrentHull, aiTarget.SoundRange, distanceMultiplierPerClosedDoor: 2);
|
||||
if (dist * HumanAIController.Hearing > aiTarget.SoundRange) { continue; }
|
||||
|
||||
character.Speak(TextManager.Get("dialogheardenemy").Value, identifier: "heardenemy".ToIdentifier(), minDurationBetweenSimilar: 30.0f);
|
||||
if (inspectNoiseObjective != null && subObjectives.Contains(inspectNoiseObjective))
|
||||
{
|
||||
//priority of inspecting noises increases with each noise
|
||||
//but orders still remain a higher priority
|
||||
inspectNoiseObjective.Priority = Math.Min(inspectNoiseObjective.Priority + InspectNoisePriorityIncrease, AIObjectiveManager.LowestOrderPriority - 1);
|
||||
//only refresh the target if the character hasn't yet started inspecting the noise
|
||||
//(if it has, it should not switch the target, otherwise you could e.g. bounce an NPC back and forth by firing guns at different sides of an outpost)
|
||||
if (objectiveManager.GetActiveObjective() != inspectNoiseObjective &&
|
||||
inspectNoiseObjective.Target != targetCharacter.CurrentHull)
|
||||
{
|
||||
CreateInspectNoiseObjective(targetCharacter.CurrentHull, priority: inspectNoiseObjective.Priority);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CreateInspectNoiseObjective(targetCharacter.CurrentHull, priority: InspectNoisePriority);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CreateInspectNoiseObjective(ISpatialEntity target, float priority)
|
||||
{
|
||||
RemoveSubObjective(ref inspectNoiseObjective);
|
||||
inspectNoiseObjective = new AIObjectiveGoTo(target, character, objectiveManager)
|
||||
{
|
||||
Priority = priority,
|
||||
SourceObjective = this
|
||||
};
|
||||
inspectNoiseObjective.Completed += () => { inspectNoiseObjective = null; inspectNoiseExpirationTimer = 0.0f; };
|
||||
inspectNoiseObjective.Abandoned += () => { inspectNoiseObjective = null; inspectNoiseExpirationTimer = 0.0f; };
|
||||
AddSubObjective(inspectNoiseObjective);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific() => false;
|
||||
|
||||
}
|
||||
}
|
||||
+8
-11
@@ -1,4 +1,4 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
@@ -11,13 +11,8 @@ namespace Barotrauma
|
||||
class AIObjectiveLoadItem : AIObjective
|
||||
{
|
||||
public override Identifier Identifier { get; set; } = "load item".ToIdentifier();
|
||||
public override bool IsLoop
|
||||
{
|
||||
get => true;
|
||||
set => throw new Exception("Trying to set the value for AIObjectiveLoadItem.IsLoop from: " + Environment.StackTrace.CleanupStackTrace());
|
||||
}
|
||||
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
protected override bool AllowWhileHandcuffed => false;
|
||||
|
||||
private AIObjectiveLoadItems.ItemCondition TargetItemCondition { get; }
|
||||
private Item Container { get; }
|
||||
@@ -163,7 +158,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
HandleNonAllowed();
|
||||
HandleDisallowed();
|
||||
return Priority;
|
||||
}
|
||||
else if (!AIObjectiveLoadItems.IsValidTarget(Container, character, targetCondition: TargetItemCondition))
|
||||
@@ -298,12 +293,14 @@ namespace Barotrauma
|
||||
if (item.Removed) { return false; }
|
||||
if (!ValidContainableItemIdentifiers.Contains(item.Prefab.Identifier)) { return false; }
|
||||
if (ignoredItems.Contains(item)) { return false; }
|
||||
if ((item.Illegitimate) == character.IsOnPlayerTeam) { return false; }
|
||||
if ((item.SpawnedInCurrentOutpost && !item.AllowStealing) == character.IsOnPlayerTeam) { return false; }
|
||||
var rootInventoryOwner = item.GetRootInventoryOwner();
|
||||
if (rootInventoryOwner is Character owner && owner != character) { return false; }
|
||||
if (rootInventoryOwner is Item parentItem)
|
||||
if (item.GetRootInventoryOwner() is Character owner && owner != character) { return false; }
|
||||
Item parentItem = item.Container;
|
||||
while (parentItem != null)
|
||||
{
|
||||
if (parentItem.HasTag(Tags.DontTakeItems)) { return false; }
|
||||
parentItem = parentItem.Container;
|
||||
}
|
||||
if (!item.HasAccess(character)) { return false; }
|
||||
if (!character.HasItem(item) && !CanEquip(item, allowWearing: false)) { return false; }
|
||||
|
||||
+2
-2
@@ -50,7 +50,7 @@ namespace Barotrauma
|
||||
TargetCondition = option == "turretammo" ? ItemCondition.Empty : ItemCondition.Full;
|
||||
}
|
||||
|
||||
protected override bool Filter(Item target)
|
||||
protected override bool IsValidTarget(Item target)
|
||||
{
|
||||
//don't pass TargetContainerTags to the method (no need to filter by tags anymore, it's already done when populating TargetContainers)
|
||||
if (!IsValidTarget(target, character, null, TargetCondition)) { return false; }
|
||||
@@ -104,7 +104,7 @@ namespace Barotrauma
|
||||
protected override void OnObjectiveCompleted(AIObjective objective, Item target)
|
||||
=> HumanAIController.RemoveTargets<AIObjectiveLoadItems, Item>(character, target);
|
||||
|
||||
protected override float TargetEvaluation()
|
||||
protected override float GetTargetPriority()
|
||||
{
|
||||
if (Targets.None()) { return 0; }
|
||||
if (objectiveManager.IsOrder(this))
|
||||
|
||||
+48
-25
@@ -4,21 +4,34 @@ using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
/// <summary>
|
||||
/// An objective that creates specific kinds of subobjectives for specific types of targets, and loops through those targets.
|
||||
/// For example, a cleanup objective that loops through items that need to be cleaned up, or a "fix leaks" objective that loops through leaks that need welding.
|
||||
/// </summary>
|
||||
abstract class AIObjectiveLoop<T> : AIObjective
|
||||
{
|
||||
public HashSet<T> Targets { get; private set; } = new HashSet<T>();
|
||||
public Dictionary<T, AIObjective> Objectives { get; private set; } = new Dictionary<T, AIObjective>();
|
||||
protected HashSet<T> ignoreList = new HashSet<T>();
|
||||
private float ignoreListTimer;
|
||||
private float ignoreListClearTimer;
|
||||
protected float targetUpdateTimer;
|
||||
protected virtual float TargetUpdateTimeMultiplier { get; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// How often are the subobjectives synced based on the available targets?
|
||||
/// </summary>
|
||||
private float syncTimer;
|
||||
private readonly float syncTime = 1;
|
||||
|
||||
// By default, doesn't clear the list automatically
|
||||
/// <summary>
|
||||
/// By default, doesn't clear the list automatically
|
||||
/// </summary>
|
||||
protected virtual float IgnoreListClearInterval => 0;
|
||||
|
||||
/// <summary>
|
||||
/// Contains targets that anyone in the same crew has reported about. Used for automatic the target has to be reported before it can be can be targeted, so characters don't magically know where e.g. enemies are.
|
||||
/// Ignored on orders: a bot explicitly ordered to repair leaks or fight intruders can find targets that haven't been reported.
|
||||
/// </summary>
|
||||
public HashSet<T> ReportedTargets { get; private set; } = new HashSet<T>();
|
||||
|
||||
public bool AddTarget(T target)
|
||||
@@ -28,7 +41,7 @@ namespace Barotrauma
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (Filter(target))
|
||||
if (IsValidTarget(target))
|
||||
{
|
||||
ReportedTargets.Add(target);
|
||||
return true;
|
||||
@@ -42,24 +55,27 @@ namespace Barotrauma
|
||||
protected override void Act(float deltaTime) { }
|
||||
protected override bool CheckObjectiveSpecific() => false;
|
||||
public override bool CanBeCompleted => true;
|
||||
public override bool AbandonWhenCannotCompleteSubjectives => false;
|
||||
public override bool AbandonWhenCannotCompleteSubObjectives => false;
|
||||
public override bool AllowSubObjectiveSorting => true;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
protected override bool AllowWhileHandcuffed => false;
|
||||
protected override bool AbandonIfDisallowed => false;
|
||||
|
||||
public virtual bool InverseTargetEvaluation => false;
|
||||
/// <summary>
|
||||
/// Makes the priority inversely proportional to the value returned by <see cref="GetTargetPriority"/>.
|
||||
/// In other words, gives this objective a high priority when priority of the targets is low.
|
||||
/// </summary>
|
||||
public virtual bool InverseTargetPriority => false;
|
||||
protected virtual bool ResetWhenClearingIgnoreList => true;
|
||||
protected virtual bool ForceOrderPriority => true;
|
||||
|
||||
protected virtual int MaxTargets => int.MaxValue;
|
||||
|
||||
public override bool IsLoop { get => true; set => throw new Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace.CleanupStackTrace()); }
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
if (IgnoreListClearInterval > 0)
|
||||
{
|
||||
if (ignoreListTimer > IgnoreListClearInterval)
|
||||
if (ignoreListClearTimer > IgnoreListClearInterval)
|
||||
{
|
||||
if (ResetWhenClearingIgnoreList)
|
||||
{
|
||||
@@ -68,12 +84,12 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
ignoreList.Clear();
|
||||
ignoreListTimer = 0;
|
||||
ignoreListClearTimer = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ignoreListTimer += deltaTime;
|
||||
ignoreListClearTimer += deltaTime;
|
||||
}
|
||||
}
|
||||
if (targetUpdateTimer <= 0)
|
||||
@@ -104,14 +120,17 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
// the timer is set between 1 and 10 seconds, depending on the priority modifier and a random +-25%
|
||||
//
|
||||
/// <summary>
|
||||
/// The timer is set between 1 and 10 seconds, depending on the priority modifier and a random +-25%
|
||||
/// </summary>
|
||||
private float CalculateTargetUpdateTimer() => targetUpdateTimer = 1 / MathHelper.Clamp(PriorityModifier * Rand.Range(0.75f, 1.25f), 0.1f, 1) * TargetUpdateTimeMultiplier;
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
ignoreList.Clear();
|
||||
ignoreListTimer = 0;
|
||||
ignoreListClearTimer = 0;
|
||||
UpdateTargets();
|
||||
}
|
||||
|
||||
@@ -119,25 +138,25 @@ namespace Barotrauma
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
HandleNonAllowed();
|
||||
HandleDisallowed();
|
||||
return Priority;
|
||||
}
|
||||
// Allow the target value to be more than 100.
|
||||
float targetValue = TargetEvaluation();
|
||||
if (InverseTargetEvaluation)
|
||||
float targetPriority = GetTargetPriority();
|
||||
if (InverseTargetPriority)
|
||||
{
|
||||
targetValue = 100 - targetValue;
|
||||
targetPriority = 100 - targetPriority;
|
||||
}
|
||||
var currentSubObjective = CurrentSubObjective;
|
||||
if (currentSubObjective != null && currentSubObjective.Priority > targetValue)
|
||||
if (currentSubObjective != null && currentSubObjective.Priority > targetPriority)
|
||||
{
|
||||
// If the priority is higher than the target value, let's just use it.
|
||||
// The priority calculation is more precise, but it takes into account things like distances,
|
||||
// so it's better not to use it if it's lower than the rougher targetValue.
|
||||
targetValue = currentSubObjective.Priority;
|
||||
targetPriority = currentSubObjective.Priority;
|
||||
}
|
||||
// If the target value is less than 1% of the max value, let's just treat it as zero.
|
||||
if (targetValue < 1)
|
||||
if (targetPriority < 1)
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
@@ -145,7 +164,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (objectiveManager.IsOrder(this))
|
||||
{
|
||||
Priority = ForceOrderPriority ? objectiveManager.GetOrderPriority(this) : targetValue;
|
||||
Priority = ForceOrderPriority ? objectiveManager.GetOrderPriority(this) : targetPriority;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -155,7 +174,7 @@ namespace Barotrauma
|
||||
// Allow higher prio
|
||||
max = AIObjectiveManager.EmergencyObjectivePriority;
|
||||
}
|
||||
float value = MathHelper.Clamp((CumulatedDevotion + (targetValue * PriorityModifier)) / 100, 0, 1);
|
||||
float value = MathHelper.Clamp((CumulatedDevotion + (targetPriority * PriorityModifier)) / 100, 0, 1);
|
||||
Priority = MathHelper.Lerp(0, max, value);
|
||||
}
|
||||
}
|
||||
@@ -181,7 +200,7 @@ namespace Barotrauma
|
||||
bool ignore = this is AIObjectiveChargeBatteries || this is AIObjectivePumpWater || this is AIObjectiveFindThieves;
|
||||
if (!ignore && !ReportedTargets.Contains(target)) { continue; }
|
||||
}
|
||||
if (!Filter(target)) { continue; }
|
||||
if (!IsValidTarget(target)) { continue; }
|
||||
if (!ignoreList.Contains(target))
|
||||
{
|
||||
Targets.Add(target);
|
||||
@@ -228,9 +247,13 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
protected abstract IEnumerable<T> GetList();
|
||||
|
||||
protected abstract float TargetEvaluation();
|
||||
/// <summary>
|
||||
/// Returns a priority value based on the current targets (e.g. high prio when there's lots of severe fires or leaks).
|
||||
/// The priority of this objective is based on the target priority.
|
||||
/// </summary>
|
||||
protected abstract float GetTargetPriority();
|
||||
|
||||
protected abstract AIObjective ObjectiveConstructor(T target);
|
||||
protected abstract bool Filter(T target);
|
||||
protected abstract bool IsValidTarget(T target);
|
||||
}
|
||||
}
|
||||
|
||||
+79
-32
@@ -20,10 +20,27 @@ namespace Barotrauma
|
||||
MaxValue = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Highest possible priority for any objective. Used in certain cases where the character needs to react immediately to survive,
|
||||
/// such as finding a suit when under pressure or getting out of a burning room.
|
||||
/// </summary>
|
||||
public const float MaxObjectivePriority = 100;
|
||||
/// <summary>
|
||||
/// Priority of objectives such as finding safety, rescuing someone in a critical state or defending against an attacker
|
||||
/// (= objectives that are critical for saving the character's or someone else's life)
|
||||
/// </summary>
|
||||
public const float EmergencyObjectivePriority = 90;
|
||||
/// <summary>
|
||||
/// Maximum priority of an order given to the character (forced order, or the leftmost order in the crew list)
|
||||
/// </summary>
|
||||
public const float HighestOrderPriority = 70;
|
||||
/// <summary>
|
||||
/// Maximum priority of an order given to the character (rightmost order in the crew list)
|
||||
/// </summary>
|
||||
public const float LowestOrderPriority = 60;
|
||||
/// <summary>
|
||||
/// Objectives with a priority equal to or higher than this make the character run.
|
||||
/// </summary>
|
||||
public const float RunPriority = 50;
|
||||
// Constantly increases the priority of the selected objective, unless overridden
|
||||
public const float baseDevotion = 5;
|
||||
@@ -138,6 +155,7 @@ namespace Barotrauma
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
|
||||
foreach (var delayedObjective in DelayedObjectives)
|
||||
{
|
||||
CoroutineManager.StopCoroutines(delayedObjective.Value);
|
||||
@@ -159,36 +177,46 @@ namespace Barotrauma
|
||||
AddObjective(newIdleObjective);
|
||||
|
||||
int objectiveCount = Objectives.Count;
|
||||
foreach (var autonomousObjective in character.Info.Job.Prefab.AutonomousObjectives)
|
||||
if (character.Info?.Job != null)
|
||||
{
|
||||
var orderPrefab = OrderPrefab.Prefabs[autonomousObjective.Identifier];
|
||||
if (orderPrefab == null) { throw new Exception($"Could not find a matching prefab by the identifier: '{autonomousObjective.Identifier}'"); }
|
||||
Item item = null;
|
||||
if (orderPrefab.MustSetTarget)
|
||||
foreach (var autonomousObjective in character.Info.Job.Prefab.AutonomousObjectives)
|
||||
{
|
||||
item = orderPrefab.GetMatchingItems(character.Submarine, mustBelongToPlayerSub: false, requiredTeam: character.Info.TeamID, interactableFor: character)?.GetRandomUnsynced();
|
||||
}
|
||||
var order = new Order(orderPrefab, autonomousObjective.Option, item ?? character.CurrentHull as Entity, orderPrefab.GetTargetItemComponent(item), orderGiver: character);
|
||||
if (order == null) { continue; }
|
||||
if ((order.IgnoreAtOutpost || autonomousObjective.IgnoreAtOutpost) &&
|
||||
Level.IsLoadedFriendlyOutpost && character.TeamID != CharacterTeamType.FriendlyNPC && !character.IsFriendlyNPCTurnedHostile)
|
||||
{
|
||||
if (Submarine.MainSub != null && Submarine.MainSub.DockedTo.None(s => s.TeamID != CharacterTeamType.FriendlyNPC && s.TeamID != character.TeamID))
|
||||
var orderPrefab = OrderPrefab.Prefabs[autonomousObjective.Identifier] ?? throw new Exception($"Could not find a matching prefab by the identifier: '{autonomousObjective.Identifier}'");
|
||||
Item item = null;
|
||||
if (orderPrefab.MustSetTarget)
|
||||
{
|
||||
item = orderPrefab.GetMatchingItems(character.Submarine, mustBelongToPlayerSub: false, requiredTeam: character.Info.TeamID, interactableFor: character)?.GetRandomUnsynced();
|
||||
}
|
||||
var order = new Order(orderPrefab, autonomousObjective.Option, item ?? character.CurrentHull as Entity, orderPrefab.GetTargetItemComponent(item), orderGiver: character);
|
||||
if (order == null) { continue; }
|
||||
if ((order.IgnoreAtOutpost || autonomousObjective.IgnoreAtOutpost) &&
|
||||
Level.IsLoadedFriendlyOutpost && character.TeamID != CharacterTeamType.FriendlyNPC && !character.IsFriendlyNPCTurnedHostile)
|
||||
{
|
||||
if (Submarine.MainSub != null && Submarine.MainSub.DockedTo.None(s => s.TeamID != CharacterTeamType.FriendlyNPC && s.TeamID != character.TeamID))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (autonomousObjective.IgnoreAtNonOutpost && !Level.IsLoadedFriendlyOutpost)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (autonomousObjective.IgnoreAtNonOutpost && !Level.IsLoadedFriendlyOutpost)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var objective = CreateObjective(order, autonomousObjective.PriorityModifier);
|
||||
if (objective != null && objective.CanBeCompleted)
|
||||
{
|
||||
AddObjective(objective, delay: Rand.Value() / 2);
|
||||
objectiveCount++;
|
||||
}
|
||||
}
|
||||
var objective = CreateObjective(order, autonomousObjective.PriorityModifier);
|
||||
if (objective != null && objective.CanBeCompleted)
|
||||
{
|
||||
AddObjective(objective, delay: Rand.Value() / 2);
|
||||
objectiveCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string warningMsg = character.Info == null ?
|
||||
$"The character {character.DisplayName} has been set to use human ai, but has no {nameof(CharacterInfo)}. This may cause issues with the AI. Consider adding {nameof(CharacterPrefab.HasCharacterInfo)}=\"True\" to the character config." :
|
||||
$"The character {character.DisplayName} has been set to use human ai, but has no job. This may cause issues with the AI. Consider configuring some jobs for the character type.";
|
||||
DebugConsole.AddWarning(warningMsg, character.Prefab.ContentPackage);
|
||||
}
|
||||
|
||||
_waitTimer = Math.Max(_waitTimer, Rand.Range(0.5f, 1f) * objectiveCount);
|
||||
}
|
||||
|
||||
@@ -501,7 +529,6 @@ namespace Barotrauma
|
||||
if (!order.TargetItemComponent.Item.IsInteractable(character)) { return null; }
|
||||
newObjective = new AIObjectiveOperateItem(targetPump, character, this, order.Option, false, priorityModifier: priorityModifier)
|
||||
{
|
||||
IsLoop = false,
|
||||
Override = order.OrderGiver is { IsCommanding: true }
|
||||
};
|
||||
newObjective.Completed += () => DismissSelf(order);
|
||||
@@ -531,7 +558,7 @@ namespace Barotrauma
|
||||
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, order.Option,
|
||||
requireEquip: false, useController: order.UseController, controller: order.ConnectedController, priorityModifier: priorityModifier)
|
||||
{
|
||||
IsLoop = true,
|
||||
Repeat = true,
|
||||
// Don't override unless it's an order by a player
|
||||
Override = order.OrderGiver != null && order.OrderGiver.IsCommanding
|
||||
};
|
||||
@@ -539,7 +566,6 @@ namespace Barotrauma
|
||||
case "setchargepct":
|
||||
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, order.Option, false, priorityModifier: priorityModifier)
|
||||
{
|
||||
IsLoop = false,
|
||||
Override = !character.IsDismissed,
|
||||
completionCondition = () =>
|
||||
{
|
||||
@@ -603,7 +629,6 @@ namespace Barotrauma
|
||||
{
|
||||
prepareObjective = new AIObjectivePrepare(character, this, order.GetTargetItems(order.Option), order.RequireItems)
|
||||
{
|
||||
KeepActiveWhenReady = false,
|
||||
CheckInventory = false,
|
||||
EvaluateCombatPriority = true,
|
||||
FindAllItems = false,
|
||||
@@ -621,13 +646,16 @@ namespace Barotrauma
|
||||
case "deconstructitems":
|
||||
newObjective = new AIObjectiveDeconstructItems(character, this, priorityModifier);
|
||||
break;
|
||||
case "inspectnoises":
|
||||
newObjective = new AIObjectiveInspectNoises(character, this, priorityModifier);
|
||||
break;
|
||||
default:
|
||||
if (order.TargetItemComponent == null) { return null; }
|
||||
if (!order.TargetItemComponent.Item.IsInteractable(character)) { return null; }
|
||||
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, order.Option,
|
||||
requireEquip: false, useController: order.UseController, controller: order.ConnectedController, priorityModifier: priorityModifier)
|
||||
{
|
||||
IsLoop = true,
|
||||
Repeat = true,
|
||||
// Don't override unless it's an order by a player
|
||||
Override = order.OrderGiver != null && order.OrderGiver.IsCommanding
|
||||
};
|
||||
@@ -689,10 +717,16 @@ namespace Barotrauma
|
||||
/// Only checks the current order. Deprecated, use pattern matching instead.
|
||||
/// </summary>
|
||||
public bool IsCurrentOrder<T>() where T : AIObjective => CurrentOrder is T;
|
||||
|
||||
/// <summary>
|
||||
/// Checks the current objective (which can be an order too). Deprecated, use pattern matching instead.
|
||||
/// </summary>
|
||||
public bool IsCurrentObjective<T>() where T : AIObjective => CurrentObjective is T;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if any objectives or orders are of the specified type. Regardless of whether the objective is active or inactive.
|
||||
/// </summary>
|
||||
public bool HasObjectiveOrOrder<T>() where T : AIObjective => Objectives.Any(o => o is T) || HasOrder<T>();
|
||||
|
||||
public AIObjective GetActiveObjective() => CurrentObjective?.GetActiveObjective();
|
||||
|
||||
@@ -705,10 +739,23 @@ namespace Barotrauma
|
||||
/// Return the first order with the specified objective. Can return null.
|
||||
/// </summary>
|
||||
public Order GetOrder(AIObjective objective) => CurrentOrders.FirstOrDefault(o => o.Objective == objective);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the last active objective of the specified objective type.
|
||||
/// Should generally be used to get the active objective (or subobjective) of objectives that don't sort their subobjectives by priority (see <see cref="AIObjective.AllowSubObjectiveSorting"/>.
|
||||
/// </summary>
|
||||
/// <returns>The last active objective of the specified type if found.
|
||||
/// </returns>
|
||||
public T GetLastActiveObjective<T>() where T : AIObjective
|
||||
=> CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).LastOrDefault(so => so is T) as T;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the first active objective of the specified objective type.
|
||||
/// Should generally be used to get the active objective (or subobjective) of objectives that sort their subobjectives by priority, such as those that inherit <see cref="AIObjectiveLoop"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The first active objective of the specified type if found.
|
||||
/// </returns>
|
||||
public T GetFirstActiveObjective<T>() where T : AIObjective
|
||||
=> CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).FirstOrDefault(so => so is T) as T;
|
||||
|
||||
|
||||
+10
-5
@@ -13,8 +13,8 @@ namespace Barotrauma
|
||||
|
||||
public override bool AllowAutomaticItemUnequipping => true;
|
||||
public override bool AllowMultipleInstances => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
protected override bool AllowInAnySub => true;
|
||||
protected override bool AllowWhileHandcuffed => false;
|
||||
public override bool PrioritizeIfSubObjectivesActive => component != null && (component is Reactor || component is Turret);
|
||||
|
||||
private readonly ItemComponent component, controller;
|
||||
@@ -29,7 +29,12 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public Func<PathNode, bool> EndNodeFilter;
|
||||
|
||||
public bool Override { get; set; } = true;
|
||||
public bool Override { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// When true, the operate objective is never completed, unless it's abandoned.
|
||||
/// </summary>
|
||||
public bool Repeat { get; init; }
|
||||
|
||||
public override bool CanBeCompleted => base.CanBeCompleted && (!useController || controller != null);
|
||||
|
||||
@@ -50,7 +55,7 @@ namespace Barotrauma
|
||||
bool isOrder = objectiveManager.IsOrder(this);
|
||||
if (!IsAllowed)
|
||||
{
|
||||
HandleNonAllowed();
|
||||
HandleDisallowed();
|
||||
return Priority;
|
||||
}
|
||||
if (!isOrder && component.Item.ConditionPercentage <= 0)
|
||||
@@ -307,7 +312,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific() => isDoneOperating && !IsLoop;
|
||||
protected override bool CheckObjectiveSpecific() => isDoneOperating && !Repeat;
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
|
||||
+6
-32
@@ -13,7 +13,7 @@ namespace Barotrauma
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool KeepDivingGearOnAlsoWhenInactive => true;
|
||||
public override bool PrioritizeIfSubObjectivesActive => true;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
protected override bool AllowWhileHandcuffed => false;
|
||||
|
||||
private AIObjectiveGetItem getSingleItemObjective;
|
||||
private AIObjectiveGetItems getAllItemsObjective;
|
||||
@@ -22,7 +22,6 @@ namespace Barotrauma
|
||||
private readonly Item targetItem;
|
||||
private readonly ImmutableArray<Identifier> requiredItems;
|
||||
private readonly ImmutableArray<Identifier> optionalItems;
|
||||
private readonly HashSet<Item> items = new HashSet<Item>();
|
||||
public bool KeepActiveWhenReady { get; set; }
|
||||
public bool CheckInventory { get; set; }
|
||||
public bool FindAllItems { get; set; }
|
||||
@@ -61,12 +60,12 @@ namespace Barotrauma
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
HandleNonAllowed();
|
||||
HandleDisallowed();
|
||||
return Priority;
|
||||
}
|
||||
Priority = objectiveManager.GetOrderPriority(this);
|
||||
var subObjective = GetSubObjective();
|
||||
if (subObjective != null && subObjective.IsCompleted)
|
||||
if (subObjective is { IsCompleted: true })
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
@@ -113,20 +112,7 @@ namespace Barotrauma
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
if (KeepActiveWhenReady)
|
||||
{
|
||||
if (objectiveReference != null)
|
||||
{
|
||||
foreach (var item in objectiveReference.achievedItems)
|
||||
{
|
||||
if (item?.IsOwnedBy(character) != null)
|
||||
{
|
||||
items.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
if (!KeepActiveWhenReady)
|
||||
{
|
||||
IsCompleted = true;
|
||||
}
|
||||
@@ -165,22 +151,11 @@ namespace Barotrauma
|
||||
if (!TryAddSubObjective(ref getSingleItemObjective, getItemConstructor,
|
||||
onCompleted: () =>
|
||||
{
|
||||
if (KeepActiveWhenReady)
|
||||
{
|
||||
if (getSingleItemObjective != null)
|
||||
{
|
||||
var item = getSingleItemObjective?.TargetItem;
|
||||
if (item?.IsOwnedBy(character) != null)
|
||||
{
|
||||
items.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
if (!KeepActiveWhenReady)
|
||||
{
|
||||
IsCompleted = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
onAbandon: () => Abandon = true))
|
||||
{
|
||||
Abandon = true;
|
||||
@@ -193,7 +168,6 @@ namespace Barotrauma
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
items.Clear();
|
||||
subObjectivesCreated = false;
|
||||
getMultipleItemsObjective = null;
|
||||
getSingleItemObjective = null;
|
||||
|
||||
+3
-4
@@ -12,7 +12,7 @@ namespace Barotrauma
|
||||
public override Identifier Identifier { get; set; } = "pump water".ToIdentifier();
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool AllowAutomaticItemUnequipping => true;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
protected override bool AllowWhileHandcuffed => false;
|
||||
|
||||
private List<Pump> pumpList;
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Barotrauma
|
||||
base.FindTargets();
|
||||
}
|
||||
|
||||
protected override bool Filter(Pump pump)
|
||||
protected override bool IsValidTarget(Pump pump)
|
||||
{
|
||||
if (pump?.Item == null || pump.Item.Removed) { return false; }
|
||||
if (pump.Item.IgnoreByAI(character)) { return false; }
|
||||
@@ -62,7 +62,7 @@ namespace Barotrauma
|
||||
return pumpList;
|
||||
}
|
||||
|
||||
protected override float TargetEvaluation()
|
||||
protected override float GetTargetPriority()
|
||||
{
|
||||
if (Targets.None()) { return 0; }
|
||||
if (Option == "stoppumping")
|
||||
@@ -90,7 +90,6 @@ namespace Barotrauma
|
||||
protected override AIObjective ObjectiveConstructor(Pump pump)
|
||||
=> new AIObjectiveOperateItem(pump, character, objectiveManager, Option, false)
|
||||
{
|
||||
IsLoop = false,
|
||||
completionCondition = () => IsReady(pump)
|
||||
};
|
||||
|
||||
|
||||
+3
-3
@@ -10,9 +10,9 @@ namespace Barotrauma
|
||||
{
|
||||
public override Identifier Identifier { get; set; } = "repair item".ToIdentifier();
|
||||
|
||||
public override bool AllowInFriendlySubs => true;
|
||||
protected override bool AllowInFriendlySubs => true;
|
||||
public override bool KeepDivingGearOn => Item?.CurrentHull == null;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
protected override bool AllowWhileHandcuffed => false;
|
||||
|
||||
public Item Item { get; private set; }
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Barotrauma
|
||||
|
||||
protected override float GetPriority()
|
||||
{
|
||||
if (!IsAllowed) { HandleNonAllowed(); }
|
||||
if (!IsAllowed) { HandleDisallowed(); }
|
||||
if (Item.IgnoreByAI(character))
|
||||
{
|
||||
Abandon = true;
|
||||
|
||||
+4
-4
@@ -19,9 +19,9 @@ namespace Barotrauma
|
||||
public Item PrioritizedItem { get; private set; }
|
||||
|
||||
public override bool AllowMultipleInstances => true;
|
||||
public override bool AllowInFriendlySubs => true;
|
||||
protected override bool AllowInFriendlySubs => true;
|
||||
|
||||
public readonly static float RequiredSuccessFactor = 0.4f;
|
||||
public const float RequiredSuccessFactor = 0.4f;
|
||||
|
||||
public override bool IsDuplicate<T>(T otherObjective) => otherObjective is AIObjectiveRepairItems repairObjective && objectiveManager.IsOrder(repairObjective) == objectiveManager.IsOrder(this);
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool Filter(Item item)
|
||||
protected override bool IsValidTarget(Item item)
|
||||
{
|
||||
if (!ViableForRepair(item, character, HumanAIController)) { return false; };
|
||||
if (!Objectives.ContainsKey(item))
|
||||
@@ -94,7 +94,7 @@ namespace Barotrauma
|
||||
return item.Repairables.All(r => !r.IsBelowRepairThreshold);
|
||||
}
|
||||
|
||||
protected override float TargetEvaluation()
|
||||
protected override float GetTargetPriority()
|
||||
{
|
||||
var selectedItem = character.SelectedItem;
|
||||
if (selectedItem != null && AIObjectiveRepairItem.IsRepairing(character, selectedItem) && selectedItem.ConditionPercentage < 100)
|
||||
|
||||
+5
-6
@@ -13,10 +13,9 @@ namespace Barotrauma
|
||||
public override Identifier Identifier { get; set; } = "rescue".ToIdentifier();
|
||||
public override bool ForceRun => true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
|
||||
public override bool AllowOutsideSubmarine => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
protected override bool AllowOutsideSubmarine => true;
|
||||
protected override bool AllowInAnySub => true;
|
||||
protected override bool AllowWhileHandcuffed => false;
|
||||
|
||||
const float TreatmentDelay = 0.5f;
|
||||
|
||||
@@ -484,7 +483,7 @@ namespace Barotrauma
|
||||
protected override float GetPriority()
|
||||
{
|
||||
if (Target == null) { Abandon = true; }
|
||||
if (!IsAllowed) { HandleNonAllowed(); }
|
||||
if (!IsAllowed) { HandleDisallowed(); }
|
||||
if (Abandon)
|
||||
{
|
||||
return Priority;
|
||||
@@ -531,8 +530,8 @@ namespace Barotrauma
|
||||
|
||||
public override void OnDeselected()
|
||||
{
|
||||
character.SelectedCharacter = null;
|
||||
base.OnDeselected();
|
||||
character.DeselectCharacter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -9,9 +9,9 @@ namespace Barotrauma
|
||||
{
|
||||
public override Identifier Identifier { get; set; } = "rescue all".ToIdentifier();
|
||||
public override bool ForceRun => true;
|
||||
public override bool InverseTargetEvaluation => true;
|
||||
public override bool AllowOutsideSubmarine => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
public override bool InverseTargetPriority => true;
|
||||
protected override bool AllowOutsideSubmarine => true;
|
||||
protected override bool AllowInAnySub => true;
|
||||
|
||||
private readonly HashSet<Character> charactersWithMinorInjuries = new HashSet<Character>();
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Barotrauma
|
||||
public AIObjectiveRescueAll(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier) { }
|
||||
|
||||
protected override bool Filter(Character target)
|
||||
protected override bool IsValidTarget(Character target)
|
||||
{
|
||||
if (!IsValidTarget(target, character, out bool ignoredasMinorWounds))
|
||||
{
|
||||
@@ -61,7 +61,7 @@ namespace Barotrauma
|
||||
|
||||
protected override IEnumerable<Character> GetList() => Character.CharacterList;
|
||||
|
||||
protected override float TargetEvaluation()
|
||||
protected override float GetTargetPriority()
|
||||
{
|
||||
if (Targets.None()) { return 100; }
|
||||
if (!objectiveManager.IsOrder(this))
|
||||
|
||||
+2
-2
@@ -12,8 +12,8 @@ namespace Barotrauma
|
||||
private AIObjectiveGoTo moveInsideObjective, moveOutsideObjective;
|
||||
private bool usingEscapeBehavior, isSteeringThroughGap;
|
||||
|
||||
public override bool AllowOutsideSubmarine => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
protected override bool AllowOutsideSubmarine => true;
|
||||
protected override bool AllowInAnySub => true;
|
||||
|
||||
public AIObjectiveReturn(Character character, Character orderGiver, AIObjectiveManager objectiveManager, float priorityModifier = 1.0f) : base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user