v0.13.0.11
This commit is contained in:
@@ -68,7 +68,7 @@ namespace Barotrauma
|
||||
if (_abandon)
|
||||
{
|
||||
#if DEBUG
|
||||
if (HumanAIController.debugai && objectiveManager.CurrentOrder == this)
|
||||
if (HumanAIController.debugai && objectiveManager.IsOrder(this) && !objectiveManager.IsCurrentOrder<AIObjectiveGoTo>())
|
||||
{
|
||||
throw new Exception("Order abandoned!");
|
||||
}
|
||||
@@ -230,7 +230,7 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public virtual float GetPriority()
|
||||
{
|
||||
bool isOrder = objectiveManager.CurrentOrder == this;
|
||||
bool isOrder = objectiveManager.IsOrder(this);
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
@@ -239,7 +239,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (isOrder)
|
||||
{
|
||||
Priority = AIObjectiveManager.OrderPriority;
|
||||
Priority = objectiveManager.GetOrderPriority(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -261,7 +261,7 @@ namespace Barotrauma
|
||||
|
||||
public virtual void Update(float deltaTime)
|
||||
{
|
||||
if (objectiveManager.CurrentOrder != this && objectiveManager.WaitTimer <= 0)
|
||||
if (!objectiveManager.IsOrder(this) && objectiveManager.WaitTimer <= 0)
|
||||
{
|
||||
UpdateDevotion(deltaTime);
|
||||
}
|
||||
@@ -430,7 +430,7 @@ namespace Barotrauma
|
||||
subObjectives.Remove(subObjective);
|
||||
if (AbandonWhenCannotCompleteSubjectives)
|
||||
{
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
if (objectiveManager.IsOrder(this))
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
+2
-2
@@ -64,7 +64,7 @@ namespace Barotrauma
|
||||
|
||||
private bool IsReady(PowerContainer battery)
|
||||
{
|
||||
if (battery.HasBeenTuned && character.CurrentOrder == null) { return true; }
|
||||
if (battery.HasBeenTuned && character.IsDismissed) { return true; }
|
||||
if (Option == "charge")
|
||||
{
|
||||
return battery.RechargeRatio >= PowerContainer.aiRechargeTargetRatio;
|
||||
@@ -79,7 +79,7 @@ namespace Barotrauma
|
||||
new AIObjectiveOperateItem(battery, character, objectiveManager, Option, false, priorityModifier: PriorityModifier)
|
||||
{
|
||||
IsLoop = false,
|
||||
Override = character.CurrentOrder != null,
|
||||
Override = !character.IsDismissed,
|
||||
completionCondition = () => IsReady(battery)
|
||||
};
|
||||
|
||||
|
||||
+26
-3
@@ -48,21 +48,35 @@ namespace Barotrauma
|
||||
float selectedBonus = isSelected ? 100 - MaxDevotion : 0;
|
||||
float devotion = (CumulatedDevotion + selectedBonus) / 100;
|
||||
float reduction = IsPriority ? 1 : isSelected ? 2 : 3;
|
||||
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - reduction, 90);
|
||||
float max = AIObjectiveManager.LowestOrderPriority - reduction;
|
||||
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (distanceFactor * PriorityModifier), 0, 1));
|
||||
if (decontainObjective == null)
|
||||
{
|
||||
// Halve the priority until there's a decontain objective (a valid container was found).
|
||||
Priority /= 2;
|
||||
}
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
// Only continue when the get item sub objectives have been completed.
|
||||
if (subObjectives.Any()) { return; }
|
||||
if (item.IgnoreByAI)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (item.ParentInventory != null)
|
||||
{
|
||||
if (item.Container != null && !AIObjectiveCleanupItems.IsValidContainer(item.Container, character, allowUnloading: objectiveManager.HasOrders()))
|
||||
{
|
||||
// Target was picked up or moved by someone.
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Only continue when the get item sub objectives have been completed.
|
||||
if (subObjectives.Any()) { return; }
|
||||
if (HumanAIController.FindSuitableContainer(character, item, ignoredContainers, ref itemIndex, out Item suitableContainer))
|
||||
{
|
||||
itemIndex = 0;
|
||||
@@ -79,6 +93,7 @@ namespace Barotrauma
|
||||
TryAddSubObjective(ref decontainObjective, () => new AIObjectiveDecontainItem(character, item, objectiveManager, targetContainer: suitableContainer.GetComponent<ItemContainer>())
|
||||
{
|
||||
Equip = equip,
|
||||
TakeWholeStack = true,
|
||||
DropIfFails = true
|
||||
},
|
||||
onCompleted: () =>
|
||||
@@ -125,5 +140,13 @@ namespace Barotrauma
|
||||
itemIndex = 0;
|
||||
decontainObjective = null;
|
||||
}
|
||||
|
||||
public void DropTarget()
|
||||
{
|
||||
if (item != null && character.HasItem(item))
|
||||
{
|
||||
item.Drop(character);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+32
-5
@@ -2,6 +2,7 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -29,7 +30,21 @@ namespace Barotrauma
|
||||
this.prioritizedItems.AddRange(prioritizedItems.Where(i => i != null));
|
||||
}
|
||||
|
||||
protected override float TargetEvaluation() => Targets.Any() ? (objectiveManager.CurrentOrder == this ? AIObjectiveManager.OrderPriority : AIObjectiveManager.RunPriority - 1) : 0;
|
||||
protected override float TargetEvaluation()
|
||||
{
|
||||
if (Targets.None()) { return 0; }
|
||||
if (objectiveManager.IsOrder(this))
|
||||
{
|
||||
float prio = objectiveManager.GetOrderPriority(this);
|
||||
if (subObjectives.All(so => so.SubObjectives.None()))
|
||||
{
|
||||
// If none of the subobjectives have subobjectives, no valid container was found. In this case, let's reduce the priority below the run threshold.
|
||||
prio = Math.Min(prio, AIObjectiveManager.RunPriority - 1);
|
||||
}
|
||||
return prio;
|
||||
}
|
||||
return AIObjectiveManager.RunPriority - 0.5f;
|
||||
}
|
||||
|
||||
protected override bool Filter(Item target)
|
||||
{
|
||||
@@ -65,10 +80,10 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool IsValidContainer(Item item, Character character) =>
|
||||
!item.IgnoreByAI && item.IsInteractable(character) && item.HasTag("allowcleanup") && item.ParentInventory == null && item.OwnInventory != null && item.OwnInventory.AllItems.Any() && IsItemInsideValidSubmarine(item, character);
|
||||
public static bool IsValidContainer(Item item, Character character, bool allowUnloading = true) =>
|
||||
!item.IgnoreByAI && item.IsInteractable(character) && item.HasTag("allowcleanup") && allowUnloading && item.ParentInventory == null && item.OwnInventory != null && item.OwnInventory.AllItems.Any() && IsItemInsideValidSubmarine(item, character);
|
||||
|
||||
public static bool IsValidTarget(Item item, Character character, bool checkInventory)
|
||||
public static bool IsValidTarget(Item item, Character character, bool checkInventory, bool allowUnloading = true)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
if (item.IgnoreByAI) { return false; }
|
||||
@@ -76,7 +91,7 @@ namespace Barotrauma
|
||||
if (item.SpawnedInOutpost) { return false; }
|
||||
if (item.ParentInventory != null)
|
||||
{
|
||||
if (item.Container == null || !IsValidContainer(item.Container, character)) { return false; }
|
||||
if (item.Container == null || !IsValidContainer(item.Container, character, allowUnloading)) { return false; }
|
||||
}
|
||||
if (character != null && !IsItemInsideValidSubmarine(item, character)) { return false; }
|
||||
var pickable = item.GetComponent<Pickable>();
|
||||
@@ -127,5 +142,17 @@ namespace Barotrauma
|
||||
}
|
||||
return canEquip;
|
||||
}
|
||||
|
||||
public override void OnDeselected()
|
||||
{
|
||||
base.OnDeselected();
|
||||
foreach (var subObjective in SubObjectives)
|
||||
{
|
||||
if (subObjective is AIObjectiveCleanupItem cleanUpObjective)
|
||||
{
|
||||
cleanUpObjective.DropTarget();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+123
-73
@@ -30,6 +30,7 @@ namespace Barotrauma
|
||||
private float holdFireTimer;
|
||||
private bool hasAimed;
|
||||
private bool isLethalWeapon;
|
||||
private bool AllowCoolDown => !IsOffensiveOrArrest || Mode != initialMode;
|
||||
|
||||
public Character Enemy { get; private set; }
|
||||
public bool HoldPosition { get; set; }
|
||||
@@ -79,11 +80,18 @@ namespace Barotrauma
|
||||
private float coolDownTimer;
|
||||
private IEnumerable<Body> myBodies;
|
||||
private float aimTimer;
|
||||
private float reloadTimer;
|
||||
private float spreadTimer;
|
||||
|
||||
private bool canSeeTarget;
|
||||
private float visibilityCheckTimer;
|
||||
private readonly float visibilityCheckInterval = 0.2f;
|
||||
|
||||
private float sqrDistance;
|
||||
private readonly float maxDistance = 2000;
|
||||
private readonly float distanceCheckInterval = 0.2f;
|
||||
private float distanceTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Aborts the objective when this condition is true
|
||||
/// </summary>
|
||||
@@ -108,8 +116,12 @@ namespace Barotrauma
|
||||
public CombatMode Mode { get; private set; }
|
||||
|
||||
private bool IsOffensiveOrArrest => initialMode == CombatMode.Offensive || initialMode == CombatMode.Arrest;
|
||||
private bool TargetEliminated => Enemy == null || Enemy.Removed || Enemy.IsUnconscious;
|
||||
private bool TargetEliminated => IsEnemyDisabled || Enemy.IsUnconscious;
|
||||
private bool IsEnemyDisabled => Enemy == null || Enemy.Removed || Enemy.IsDead;
|
||||
|
||||
private float AimSpeed => HumanAIController.AimSpeed;
|
||||
private float AimAccuracy => HumanAIController.AimAccuracy;
|
||||
|
||||
private bool EnemyIsClose() => Enemy != null && character.CurrentHull != null && character.CurrentHull == Enemy.CurrentHull || Vector2.DistanceSquared(character.Position, Enemy.Position) < 500;
|
||||
|
||||
public AIObjectiveCombat(Character character, Character enemy, CombatMode mode, AIObjectiveManager objectiveManager, float priorityModifier = 1, float coolDown = 10.0f)
|
||||
@@ -136,6 +148,8 @@ namespace Barotrauma
|
||||
{
|
||||
Mode = CombatMode.Retreat;
|
||||
}
|
||||
spreadTimer = Rand.Range(-10, 10);
|
||||
HumanAIController.SortTimer = 0;
|
||||
}
|
||||
|
||||
public override float GetPriority()
|
||||
@@ -159,6 +173,10 @@ namespace Barotrauma
|
||||
base.Update(deltaTime);
|
||||
ignoreWeaponTimer -= deltaTime;
|
||||
checkWeaponsTimer -= deltaTime;
|
||||
if (reloadTimer > 0)
|
||||
{
|
||||
reloadTimer -= deltaTime;
|
||||
}
|
||||
if (ignoreWeaponTimer < 0)
|
||||
{
|
||||
ignoredWeapons.Clear();
|
||||
@@ -168,17 +186,25 @@ namespace Barotrauma
|
||||
{
|
||||
findSafety.Priority = 0;
|
||||
}
|
||||
if (!character.IsOnPlayerTeam && !objectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>())
|
||||
{
|
||||
distanceTimer -= deltaTime;
|
||||
if (distanceTimer < 0)
|
||||
{
|
||||
distanceTimer = distanceCheckInterval;
|
||||
sqrDistance = Vector2.DistanceSquared(character.WorldPosition, Enemy.WorldPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool Check()
|
||||
{
|
||||
if (IsOffensiveOrArrest && Mode != initialMode)
|
||||
if (sqrDistance > maxDistance * maxDistance)
|
||||
{
|
||||
Abandon = true;
|
||||
SteeringManager.Reset();
|
||||
return false;
|
||||
// The target escaped from us.
|
||||
return true;
|
||||
}
|
||||
return IsEnemyDisabled || (!IsOffensiveOrArrest && coolDownTimer <= 0);
|
||||
return IsEnemyDisabled || (AllowCoolDown && coolDownTimer <= 0);
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
@@ -186,10 +212,9 @@ namespace Barotrauma
|
||||
if (abortCondition != null && abortCondition())
|
||||
{
|
||||
Abandon = true;
|
||||
SteeringManager.Reset();
|
||||
return;
|
||||
}
|
||||
if (!IsOffensiveOrArrest)
|
||||
if (AllowCoolDown)
|
||||
{
|
||||
coolDownTimer -= deltaTime;
|
||||
}
|
||||
@@ -199,7 +224,11 @@ namespace Barotrauma
|
||||
{
|
||||
OperateWeapon(deltaTime);
|
||||
}
|
||||
if (!HoldPosition && seekAmmunitionObjective == null && seekWeaponObjective == null)
|
||||
if (HoldPosition)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
else if (seekAmmunitionObjective == null && seekWeaponObjective == null)
|
||||
{
|
||||
Move(deltaTime);
|
||||
}
|
||||
@@ -431,7 +460,7 @@ namespace Barotrauma
|
||||
priority /= 2;
|
||||
}
|
||||
}
|
||||
if (Enemy.Stun > 1)
|
||||
if (Enemy.IsKnockedDown)
|
||||
{
|
||||
// Enemy is stunned, reduce the priority of stunner weapons.
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
@@ -621,7 +650,7 @@ namespace Barotrauma
|
||||
var slots = Weapon.AllowedSlots.Where(s => s == InvSlotType.LeftHand || s == InvSlotType.RightHand || s == (InvSlotType.LeftHand | InvSlotType.RightHand));
|
||||
if (character.Inventory.TryPutItem(Weapon, character, slots))
|
||||
{
|
||||
aimTimer = Rand.Range(0.5f, 1f);
|
||||
aimTimer = Rand.Range(0.2f, 0.4f) / AimSpeed;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -704,15 +733,12 @@ namespace Barotrauma
|
||||
{
|
||||
IgnoreIfTargetDead = true,
|
||||
DialogueIdentifier = "dialogcannotreachtarget",
|
||||
TargetName = Enemy.DisplayName
|
||||
TargetName = Enemy.DisplayName,
|
||||
AlwaysUseEuclideanDistance = false
|
||||
},
|
||||
onAbandon: () =>
|
||||
{
|
||||
Abandon = true;
|
||||
SteeringManager.Reset();
|
||||
});
|
||||
onAbandon: () => Abandon = true);
|
||||
if (followTargetObjective == null) { return; }
|
||||
if (Mode == CombatMode.Arrest && Enemy.Stun > 2)
|
||||
if (Mode == CombatMode.Arrest && (Enemy.Stun > 1 || Enemy.IsKnockedDown))
|
||||
{
|
||||
if (HumanAIController.HasItem(character, "handlocker", out _))
|
||||
{
|
||||
@@ -720,8 +746,8 @@ namespace Barotrauma
|
||||
{
|
||||
arrestingRegistered = true;
|
||||
followTargetObjective.Completed += OnArrestTargetReached;
|
||||
followTargetObjective.CloseEnough = 100;
|
||||
}
|
||||
followTargetObjective.CloseEnough = 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -737,7 +763,7 @@ namespace Barotrauma
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
}
|
||||
if (followTargetObjective != null)
|
||||
if (!arrestingRegistered && followTargetObjective != null)
|
||||
{
|
||||
followTargetObjective.CloseEnough =
|
||||
WeaponComponent is RangedWeapon ? 1000 :
|
||||
@@ -760,7 +786,7 @@ namespace Barotrauma
|
||||
|
||||
private void OnArrestTargetReached()
|
||||
{
|
||||
if (HumanAIController.HasItem(character, "handlocker", out IEnumerable<Item> matchingItems) && Enemy.Stun > 0 && character.CanInteractWith(Enemy))
|
||||
if (HumanAIController.HasItem(character, "handlocker", out IEnumerable<Item> matchingItems) && !Enemy.IsUnconscious && Enemy.IsKnockedDown && character.CanInteractWith(Enemy))
|
||||
{
|
||||
var handCuffs = matchingItems.First();
|
||||
if (!HumanAIController.TakeItem(handCuffs, Enemy.Inventory, equip: true))
|
||||
@@ -780,8 +806,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
character.Speak(TextManager.Get("DialogTargetArrested"), null, 3.0f, "targetarrested", 30.0f);
|
||||
IsCompleted = true;
|
||||
}
|
||||
IsCompleted = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -818,25 +844,7 @@ namespace Barotrauma
|
||||
if (WeaponComponent == null) { return false; }
|
||||
if (Weapon.OwnInventory == null) { return true; }
|
||||
// Eject empty ammo
|
||||
if (Weapon.OwnInventory.AllItems.Any(it => it.Condition <= 0.0f))
|
||||
{
|
||||
foreach (Item containedItem in Weapon.OwnInventory.AllItemsMod)
|
||||
{
|
||||
if (containedItem.Condition <= 0)
|
||||
{
|
||||
if (character.Submarine == null)
|
||||
{
|
||||
// If we are outside of main sub, try to put the ammo in the inventory instead dropping it in the sea.
|
||||
if (character.Inventory.TryPutItem(containedItem, character, CharacterInventory.anySlot))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
containedItem.Drop(character);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HumanAIController.UnequipEmptyItems(Weapon);
|
||||
RelatedItem item = null;
|
||||
Item ammunition = null;
|
||||
string[] ammunitionIdentifiers = null;
|
||||
@@ -869,22 +877,13 @@ namespace Barotrauma
|
||||
if (ammunition != null)
|
||||
{
|
||||
var container = Weapon.GetComponent<ItemContainer>();
|
||||
if (container.Item.ParentInventory == character.Inventory)
|
||||
if (!container.Inventory.TryPutItem(ammunition, null))
|
||||
{
|
||||
if (!container.Inventory.CanBePut(ammunition))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
character.Inventory.RemoveItem(ammunition);
|
||||
if (!container.Inventory.TryPutItem(ammunition, null))
|
||||
if (ammunition.ParentInventory == character.Inventory)
|
||||
{
|
||||
ammunition.Drop(character);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
container.Combine(ammunition, character);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -902,6 +901,15 @@ namespace Barotrauma
|
||||
private void Attack(float deltaTime)
|
||||
{
|
||||
character.CursorPosition = Enemy.WorldPosition;
|
||||
if (AimAccuracy < 1)
|
||||
{
|
||||
spreadTimer += deltaTime * Rand.Range(0.01f, 1f);
|
||||
float shake = Rand.Range(0.95f, 1.05f);
|
||||
float offsetAmount = (1 - AimAccuracy) * Rand.Range(300f, 500f);
|
||||
float distanceFactor = MathUtils.InverseLerp(0, 1000 * 1000, sqrDistance);
|
||||
float offset = (float)Math.Sin(spreadTimer * shake) * offsetAmount * distanceFactor;
|
||||
character.CursorPosition += new Vector2(0, offset);
|
||||
}
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
character.CursorPosition -= character.Submarine.Position;
|
||||
@@ -912,7 +920,11 @@ namespace Barotrauma
|
||||
canSeeTarget = character.CanSeeTarget(Enemy);
|
||||
visibilityCheckTimer = visibilityCheckInterval;
|
||||
}
|
||||
if (!canSeeTarget) { return; }
|
||||
if (!canSeeTarget)
|
||||
{
|
||||
aimTimer = Rand.Range(0.2f, 0.4f) / AimSpeed;
|
||||
return;
|
||||
}
|
||||
if (Weapon.RequireAimToUse)
|
||||
{
|
||||
character.SetInput(InputType.Aim, false, true);
|
||||
@@ -928,7 +940,15 @@ namespace Barotrauma
|
||||
aimTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
if (Mode == CombatMode.Arrest && isLethalWeapon && Enemy.Stun > 1) { return; }
|
||||
if (reloadTimer > 0) { return; }
|
||||
if (Mode == CombatMode.Arrest)
|
||||
{
|
||||
// If the target is arrested or if it's stunned and we can't lock the target up, consider the objective done.
|
||||
if (Enemy.IsKnockedDown && !HumanAIController.HasItem(character, "handlocker", out _, requireEquipped: false) || HumanAIController.HasItem(Enemy, "handlocker", out _, requireEquipped: true))
|
||||
{
|
||||
IsCompleted = true;
|
||||
}
|
||||
}
|
||||
if (holdFireCondition != null && holdFireCondition()) { return; }
|
||||
float sqrDist = Vector2.DistanceSquared(character.Position, Enemy.Position);
|
||||
if (WeaponComponent is MeleeWeapon meleeWeapon)
|
||||
@@ -963,14 +983,12 @@ namespace Barotrauma
|
||||
}
|
||||
if (closeEnough)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
character.SetInput(InputType.Shoot, false, true);
|
||||
Weapon.Use(deltaTime, character);
|
||||
UseWeapon(deltaTime);
|
||||
}
|
||||
else if (!character.IsFacing(Enemy.WorldPosition))
|
||||
{
|
||||
// Don't do the facing check if we are close to the target, because it easily causes the character to get stuck here when it flips around.
|
||||
aimTimer = Rand.Range(1f, 1.5f);
|
||||
aimTimer = Rand.Range(1f, 1.5f) / AimSpeed;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -979,14 +997,15 @@ namespace Barotrauma
|
||||
{
|
||||
if (sqrDist > repairTool.Range * repairTool.Range) { return; }
|
||||
}
|
||||
if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - Weapon.Position) < MathHelper.PiOver4)
|
||||
float aimFactor = MathHelper.PiOver2 * (1 - AimAccuracy);
|
||||
if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - Weapon.Position) < MathHelper.PiOver4 + aimFactor)
|
||||
{
|
||||
if (myBodies == null)
|
||||
{
|
||||
myBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody);
|
||||
}
|
||||
var collisionCategories = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel;
|
||||
var pickedBody = Submarine.PickBody(Weapon.SimPosition, Enemy.SimPosition, myBodies, collisionCategories);
|
||||
var pickedBody = Submarine.PickBody(Weapon.SimPosition, Enemy.SimPosition, myBodies, collisionCategories, allowInsideFixture: true);
|
||||
if (pickedBody != null)
|
||||
{
|
||||
Character target = null;
|
||||
@@ -1000,31 +1019,62 @@ namespace Barotrauma
|
||||
}
|
||||
if (target != null && (target == Enemy || !HumanAIController.IsFriendly(target)))
|
||||
{
|
||||
character.SetInput(InputType.Shoot, false, true);
|
||||
Weapon.Use(deltaTime, character);
|
||||
float reloadTime = 0;
|
||||
if (WeaponComponent is RangedWeapon rangedWeapon)
|
||||
{
|
||||
reloadTime = rangedWeapon.Reload;
|
||||
}
|
||||
if (WeaponComponent is MeleeWeapon mw)
|
||||
{
|
||||
reloadTime = mw.Reload;
|
||||
}
|
||||
aimTimer = reloadTime * Rand.Range(1f, 1.5f);
|
||||
UseWeapon(deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UseWeapon(float deltaTime)
|
||||
{
|
||||
// Never allow to attack characters with deadly weapons while trying to arrest.
|
||||
if (Mode == CombatMode.Arrest && isLethalWeapon) { return; }
|
||||
float reloadTime = 0;
|
||||
if (WeaponComponent is RangedWeapon rangedWeapon)
|
||||
{
|
||||
// If the weapon is just equipped, we can't shoot just yet.
|
||||
if (rangedWeapon.ReloadTimer <= 0)
|
||||
{
|
||||
reloadTime = rangedWeapon.Reload;
|
||||
}
|
||||
}
|
||||
if (WeaponComponent is MeleeWeapon mw)
|
||||
{
|
||||
if (!((HumanoidAnimController)character.AnimController).Crouching)
|
||||
{
|
||||
reloadTime = mw.Reload;
|
||||
}
|
||||
}
|
||||
character.SetInput(InputType.Shoot, false, true);
|
||||
Weapon.Use(deltaTime, character);
|
||||
reloadTimer = Math.Max(reloadTime, reloadTime * Rand.Range(1f, 1.25f) / AimSpeed);
|
||||
}
|
||||
|
||||
private bool ShouldUnequipWeapon =>
|
||||
Weapon != null &&
|
||||
character.Submarine != null &&
|
||||
character.Submarine.TeamID == character.TeamID &&
|
||||
Character.CharacterList.None(c => c.Submarine == character.Submarine && HumanAIController.IsActive(c) && !HumanAIController.IsFriendly(character, c) && HumanAIController.VisibleHulls.Contains(c.CurrentHull));
|
||||
|
||||
protected override void OnCompleted()
|
||||
{
|
||||
base.OnCompleted();
|
||||
if (Weapon != null)
|
||||
if (ShouldUnequipWeapon)
|
||||
{
|
||||
Unequip();
|
||||
}
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
|
||||
protected override void OnAbandon()
|
||||
{
|
||||
base.OnAbandon();
|
||||
if (ShouldUnequipWeapon)
|
||||
{
|
||||
Unequip();
|
||||
}
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
|
||||
+26
-30
@@ -34,6 +34,10 @@ namespace Barotrauma
|
||||
public float ConditionLevel { get; set; } = 1;
|
||||
public bool Equip { get; set; }
|
||||
public bool RemoveEmpty { get; set; } = true;
|
||||
public bool RemoveExisting { get; set; }
|
||||
|
||||
public bool MoveWholeStack { get; set; }
|
||||
|
||||
|
||||
public AIObjectiveContainItem(Character character, Item item, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
@@ -102,47 +106,38 @@ namespace Barotrauma
|
||||
}
|
||||
if (character.CanInteractWith(container.Item, checkLinked: false))
|
||||
{
|
||||
if (RemoveEmpty && container.Inventory.AllItems.Any(it => it.Condition <= 0.0f))
|
||||
if (RemoveExisting)
|
||||
{
|
||||
foreach (var emptyItem in container.Inventory.AllItemsMod)
|
||||
{
|
||||
if (emptyItem.Condition <= 0)
|
||||
{
|
||||
emptyItem.Drop(character);
|
||||
}
|
||||
}
|
||||
HumanAIController.UnequipContainedItems(container.Item);
|
||||
}
|
||||
// Contain the item
|
||||
if (ItemToContain.ParentInventory == character.Inventory)
|
||||
else if (RemoveEmpty)
|
||||
{
|
||||
if (!container.Inventory.CanBePut(ItemToContain))
|
||||
HumanAIController.UnequipEmptyItems(container.Item);
|
||||
}
|
||||
Inventory originalInventory = ItemToContain.ParentInventory;
|
||||
var slots = originalInventory?.FindIndices(ItemToContain);
|
||||
if (container.Inventory.TryPutItem(ItemToContain, null))
|
||||
{
|
||||
if (MoveWholeStack && slots != null)
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
character.Inventory.RemoveItem(ItemToContain);
|
||||
if (container.Inventory.TryPutItem(ItemToContain, null))
|
||||
foreach (int slot in slots)
|
||||
{
|
||||
IsCompleted = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemToContain.Drop(character);
|
||||
Abandon = true;
|
||||
foreach (Item item in originalInventory.GetItemsAt(slot).ToList())
|
||||
{
|
||||
container.Inventory.TryPutItem(item, null);
|
||||
}
|
||||
}
|
||||
|
||||
IsCompleted = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (container.Combine(ItemToContain, character))
|
||||
if (ItemToContain.ParentInventory == character.Inventory && character.Submarine == Submarine.MainSub)
|
||||
{
|
||||
IsCompleted = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Abandon = true;
|
||||
ItemToContain.Drop(character);
|
||||
}
|
||||
Abandon = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -151,7 +146,8 @@ namespace Barotrauma
|
||||
{
|
||||
DialogueIdentifier = "dialogcannotreachtarget",
|
||||
TargetName = container.Item.Name,
|
||||
abortCondition = () => !ItemToContain.IsOwnedBy(character)
|
||||
abortCondition = obj => !ItemToContain.IsOwnedBy(character),
|
||||
SpeakIfFails = !objectiveManager.IsCurrentOrder<AIObjectiveCleanupItems>()
|
||||
},
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () => RemoveSubObjective(ref goToObjective));
|
||||
|
||||
+7
-1
@@ -22,8 +22,13 @@ namespace Barotrauma
|
||||
public AIObjectiveGetItem GetItemObjective => getItemObjective;
|
||||
public AIObjectiveContainItem ContainObjective => containObjective;
|
||||
|
||||
public Item TargetItem => targetItem;
|
||||
public ItemContainer TargetContainer => targetContainer;
|
||||
|
||||
public bool Equip { get; set; }
|
||||
|
||||
public bool TakeWholeStack { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true drops the item when containing the item fails.
|
||||
/// In both cases abandons the objective.
|
||||
@@ -90,7 +95,7 @@ namespace Barotrauma
|
||||
if (getItemObjective == null && !itemToDecontain.IsOwnedBy(character))
|
||||
{
|
||||
TryAddSubObjective(ref getItemObjective,
|
||||
constructor: () => new AIObjectiveGetItem(character, targetItem, objectiveManager, Equip),
|
||||
constructor: () => new AIObjectiveGetItem(character, targetItem, objectiveManager, Equip) { TakeWholeStack = this.TakeWholeStack },
|
||||
onAbandon: () => Abandon = true);
|
||||
return;
|
||||
}
|
||||
@@ -99,6 +104,7 @@ namespace Barotrauma
|
||||
TryAddSubObjective(ref containObjective,
|
||||
constructor: () => new AIObjectiveContainItem(character, itemToDecontain, targetContainer, objectiveManager)
|
||||
{
|
||||
MoveWholeStack = TakeWholeStack,
|
||||
Equip = Equip,
|
||||
RemoveEmpty = false,
|
||||
GetItemPriority = GetItemPriority,
|
||||
|
||||
+3
-3
@@ -35,7 +35,7 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
return Priority;
|
||||
}
|
||||
bool isOrder = objectiveManager.IsCurrentOrder<AIObjectiveExtinguishFires>();
|
||||
bool isOrder = objectiveManager.HasOrder<AIObjectiveExtinguishFires>();
|
||||
if (!isOrder && Character.CharacterList.Any(c => c.CurrentHull == targetHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c)))
|
||||
{
|
||||
// Don't go into rooms with any enemies, unless it's an order
|
||||
@@ -78,7 +78,7 @@ namespace Barotrauma
|
||||
{
|
||||
TryAddSubObjective(ref getExtinguisherObjective, () =>
|
||||
{
|
||||
if (!character.HasEquippedItem("fireextinguisher", allowBroken: false))
|
||||
if (character.IsOnPlayerTeam && !character.HasEquippedItem("fireextinguisher", allowBroken: false))
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogFindExtinguisher"), null, 2.0f, "findextinguisher", 30.0f);
|
||||
}
|
||||
@@ -88,7 +88,7 @@ namespace Barotrauma
|
||||
// If the item is inside an unsafe hull, decrease the priority
|
||||
GetItemPriority = i => HumanAIController.UnsafeHulls.Contains(i.CurrentHull) ? 0.1f : 1
|
||||
};
|
||||
if (objectiveManager.IsCurrentOrder<AIObjectiveExtinguishFires>())
|
||||
if (objectiveManager.HasOrder<AIObjectiveExtinguishFires>())
|
||||
{
|
||||
getItemObjective.Abandoned += () => character.Speak(TextManager.Get("dialogcannotfindfireextinguisher"), null, 0.0f, "dialogcannotfindfireextinguisher", 10.0f);
|
||||
};
|
||||
|
||||
+9
@@ -42,6 +42,15 @@ namespace Barotrauma
|
||||
if (hull.Submarine == null) { return false; }
|
||||
if (character.Submarine == null) { return false; }
|
||||
if (!character.Submarine.IsEntityFoundOnThisSub(hull, includingConnectedSubs: true)) { return false; }
|
||||
if (hull.BallastFlora != null) { return false; }
|
||||
foreach (var ballastFlora in MapCreatures.Behavior.BallastFloraBehavior.EntityList)
|
||||
{
|
||||
if (ballastFlora.Parent?.Submarine != character.Submarine) { continue; }
|
||||
if (ballastFlora.Branches.Any(b => !b.Removed && b.Health > 0 && b.CurrentHull == hull))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+7
-7
@@ -10,6 +10,8 @@ namespace Barotrauma
|
||||
protected override float IgnoreListClearInterval => 30;
|
||||
public override bool IgnoreUnsafeHulls => true;
|
||||
|
||||
protected override float TargetUpdateTimeMultiplier => 0.2f;
|
||||
|
||||
public AIObjectiveFightIntruders(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier) { }
|
||||
|
||||
@@ -48,16 +50,14 @@ namespace Barotrauma
|
||||
|
||||
public static bool IsValidTarget(Character target, Character character)
|
||||
{
|
||||
if (target == null || target.IsDead || target.Removed) { return false; }
|
||||
if (target == null || target.Removed) { return false; }
|
||||
if (target.IsDead || target.IsUnconscious) { return false; }
|
||||
if (target == character) { return false; }
|
||||
if (HumanAIController.IsFriendly(character, target)) { return false; }
|
||||
if (target.Submarine == null) { return false; }
|
||||
if (target.Submarine.TeamID != character.TeamID) { return false; }
|
||||
if (character.Submarine == null) { return false; }
|
||||
if (target.CurrentHull == null) { return false; }
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
if (!character.Submarine.IsConnectedTo(target.Submarine)) { return false; }
|
||||
}
|
||||
if (HumanAIController.IsFriendly(character, target)) { return false; }
|
||||
if (!character.Submarine.IsConnectedTo(target.Submarine)) { return false; }
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+54
-35
@@ -1,6 +1,7 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -37,11 +38,11 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
targetItem = character.Inventory.FindItemByTag(gearTag, true);
|
||||
if (targetItem == null || !character.HasEquippedItem(targetItem))
|
||||
if (targetItem == null || !character.HasEquippedItem(targetItem) && targetItem.ContainedItems.Any(i => i.HasTag(OXYGEN_SOURCE) && i.Condition > 0))
|
||||
{
|
||||
TryAddSubObjective(ref getDivingGear, () =>
|
||||
{
|
||||
if (targetItem == null)
|
||||
if (targetItem == null && character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogGetDivingGear"), null, 0.0f, "getdivinggear", 30.0f);
|
||||
}
|
||||
@@ -57,46 +58,78 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!EjectEmptyTanks(character, targetItem, out var containedItems))
|
||||
HumanAIController.UnequipContainedItems(targetItem, it => !it.HasTag("oxygensource"));
|
||||
HumanAIController.UnequipEmptyItems(targetItem);
|
||||
// Seek oxygen that has at least 10% condition left, if we are inside a friendly sub.
|
||||
// The margin helps us to survive, because we might need some oxygen before we can find more oxygen.
|
||||
// When we are venturing outside of our sub, let's just suppose that we have enough oxygen with us and optimize it so that we don't keep switching off half used tanks.
|
||||
float min = character.Submarine != Submarine.MainSub ? 0.01f : MIN_OXYGEN;
|
||||
if (targetItem.OwnInventory != null && targetItem.OwnInventory.AllItems.None(it => it != null && it.HasTag(OXYGEN_SOURCE) && it.Condition > min))
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError($"{character.Name}: AIObjectiveFindDivingGear failed - the item \"" + targetItem + "\" has no proper inventory");
|
||||
#endif
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (containedItems.None(it => it != null && it.HasTag(OXYGEN_SOURCE) && it.Condition > MIN_OXYGEN))
|
||||
{
|
||||
// No valid oxygen source loaded.
|
||||
// Seek oxygen that has min 10% condition left.
|
||||
TryAddSubObjective(ref getOxygen, () =>
|
||||
{
|
||||
if (!HumanAIController.HasItem(character, "oxygensource", out _, conditionPercentage: 10))
|
||||
if (character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f);
|
||||
if (HumanAIController.HasItem(character, "oxygensource", out _, conditionPercentage: min))
|
||||
{
|
||||
character.Speak(TextManager.Get("dialogswappingoxygentank"), null, 0, "swappingoxygentank", 30.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f);
|
||||
}
|
||||
}
|
||||
return new AIObjectiveContainItem(character, OXYGEN_SOURCE, targetItem.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
AllowToFindDivingGear = false,
|
||||
AllowDangerousPressure = true,
|
||||
ConditionLevel = MIN_OXYGEN
|
||||
ConditionLevel = MIN_OXYGEN,
|
||||
RemoveExisting = true
|
||||
};
|
||||
},
|
||||
onAbandon: () =>
|
||||
{
|
||||
// Try to seek any oxygen sources.
|
||||
getOxygen = null;
|
||||
int remainingTanks = ReportOxygenTankCount();
|
||||
// Try to seek any oxygen sources, even if they have minimal amount of oxygen.
|
||||
TryAddSubObjective(ref getOxygen, () =>
|
||||
{
|
||||
return new AIObjectiveContainItem(character, OXYGEN_SOURCE, targetItem.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
AllowToFindDivingGear = false,
|
||||
AllowDangerousPressure = true
|
||||
AllowDangerousPressure = true,
|
||||
RemoveExisting = true
|
||||
};
|
||||
},
|
||||
onAbandon: () => Abandon = true,
|
||||
onAbandon: () =>
|
||||
{
|
||||
Abandon = true;
|
||||
if (remainingTanks > 0 && !HumanAIController.HasItem(character, "oxygensource", out _, conditionPercentage: 0.01f))
|
||||
{
|
||||
character.Speak(TextManager.Get("dialogcantfindtoxygen"), null, 0, "cantfindoxygen", 30.0f);
|
||||
}
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref getOxygen));
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref getOxygen));
|
||||
onCompleted: () =>
|
||||
{
|
||||
RemoveSubObjective(ref getOxygen);
|
||||
ReportOxygenTankCount();
|
||||
});
|
||||
|
||||
int ReportOxygenTankCount()
|
||||
{
|
||||
int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag("oxygensource") && i.Condition > 1);
|
||||
if (remainingOxygenTanks == 0)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogOutOfOxygenTanks"), null, 0.0f, "outofoxygentanks", 30.0f);
|
||||
}
|
||||
else if (remainingOxygenTanks < 10)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogLowOnOxygenTanks"), null, 0.0f, "lowonoxygentanks", 30.0f);
|
||||
}
|
||||
return remainingOxygenTanks;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,21 +141,7 @@ namespace Barotrauma
|
||||
{
|
||||
containedItems = target.OwnInventory?.AllItems;
|
||||
if (containedItems == null) { return false; }
|
||||
foreach (Item containedItem in target.OwnInventory.AllItemsMod)
|
||||
{
|
||||
if (containedItem.Condition <= 0.0f)
|
||||
{
|
||||
if (actor.Submarine == null)
|
||||
{
|
||||
// If we are outside of main sub, try to put the tank in the inventory instead dropping it in the sea.
|
||||
if (actor.Inventory.TryPutItem(containedItem, actor, CharacterInventory.anySlot))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
containedItem.Drop(actor);
|
||||
}
|
||||
}
|
||||
AIController.UnequipEmptyItems(actor, target);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+11
-3
@@ -46,19 +46,27 @@ namespace Barotrauma
|
||||
}
|
||||
if (character.CurrentHull == null)
|
||||
{
|
||||
Priority = (objectiveManager.IsCurrentOrder<AIObjectiveGoTo>() || objectiveManager.Objectives.Any(o => o is AIObjectiveCombat)) && HumanAIController.HasDivingSuit(character) ? 0 : 100;
|
||||
Priority = (objectiveManager.IsCurrentOrder<AIObjectiveGoTo>() || objectiveManager.HasActiveObjective<AIObjectiveCombat>()) && HumanAIController.HasDivingSuit(character) ? 0 : 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (HumanAIController.NeedsDivingGear(character.CurrentHull, out _) && !HumanAIController.HasDivingGear(character))
|
||||
if (HumanAIController.NeedsDivingGear(character.CurrentHull, out bool needsSuit) &&
|
||||
(needsSuit ?
|
||||
!HumanAIController.HasDivingSuit(character, conditionPercentage: AIObjectiveFindDivingGear.MIN_OXYGEN) :
|
||||
!HumanAIController.HasDivingMask(character, conditionPercentage: AIObjectiveFindDivingGear.MIN_OXYGEN)))
|
||||
{
|
||||
Priority = 100;
|
||||
}
|
||||
else if (objectiveManager.IsCurrentOrder<AIObjectiveGoTo>() && character.Submarine != null && !HumanAIController.IsOnFriendlyTeam(character.TeamID, character.Submarine.TeamID))
|
||||
{
|
||||
// Ordered to follow/hold position inside a hostile sub -> ignore find safety unless we need to find a diving gear
|
||||
Priority = 0;
|
||||
}
|
||||
Priority = MathHelper.Clamp(Priority, 0, 100);
|
||||
if (divingGearObjective != null && !divingGearObjective.IsCompleted && divingGearObjective.CanBeCompleted)
|
||||
{
|
||||
// Boost the priority while seeking the diving gear
|
||||
Priority = Math.Max(Priority, Math.Min(AIObjectiveManager.OrderPriority + 20, 100));
|
||||
Priority = Math.Max(Priority, Math.Min(AIObjectiveManager.HighestOrderPriority + 20, 100));
|
||||
}
|
||||
}
|
||||
return Priority;
|
||||
|
||||
+27
-16
@@ -38,7 +38,7 @@ namespace Barotrauma
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
}
|
||||
else if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.ObjectiveManager.GetActiveObjective<AIObjectiveFixLeak>()?.Leak == Leak))
|
||||
else if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.Character.IsBot && other.ObjectiveManager.GetActiveObjective<AIObjectiveFixLeak>()?.Leak == Leak))
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
@@ -52,7 +52,7 @@ namespace Barotrauma
|
||||
float distanceFactor = isPriority || xDist < 200 && yDist < 100 ? 1 : MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 3000, xDist + yDist * 3.0f));
|
||||
float severity = isPriority ? 1 : AIObjectiveFixLeaks.GetLeakSeverity(Leak) / 100;
|
||||
float reduction = isPriority ? 1 : 2;
|
||||
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - reduction, 90);
|
||||
float max = AIObjectiveManager.LowestOrderPriority - reduction;
|
||||
float devotion = CumulatedDevotion / 100;
|
||||
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
|
||||
}
|
||||
@@ -67,7 +67,7 @@ namespace Barotrauma
|
||||
TryAddSubObjective(ref getWeldingTool, () => new AIObjectiveGetItem(character, "weldingequipment", objectiveManager, equip: true, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC),
|
||||
onAbandon: () =>
|
||||
{
|
||||
if (objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>())
|
||||
if (character.IsOnPlayerTeam && objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>())
|
||||
{
|
||||
character.Speak(TextManager.Get("dialogcannotfindweldingequipment"), null, 0.0f, "dialogcannotfindweldingequipment", 10.0f);
|
||||
}
|
||||
@@ -86,23 +86,34 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
// Drop empty tanks
|
||||
if (weldingTool.OwnInventory.AllItems.Any(it => it.Condition <= 0.0f))
|
||||
HumanAIController.UnequipContainedItems(weldingTool, it => !it.HasTag("weldingfuel"));
|
||||
HumanAIController.UnequipEmptyItems(weldingTool);
|
||||
if (weldingTool.OwnInventory != null && weldingTool.OwnInventory.AllItems.None(i => i.HasTag("weldingfuel") && i.Condition > 0.0f))
|
||||
{
|
||||
foreach (Item containedItem in weldingTool.OwnInventory.AllItemsMod)
|
||||
{
|
||||
if (containedItem.Condition <= 0.0f)
|
||||
TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, "weldingfuel", weldingTool.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC),
|
||||
onAbandon: () =>
|
||||
{
|
||||
containedItem.Drop(character);
|
||||
Abandon = true;
|
||||
ReportWeldingFuelTankCount();
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
RemoveSubObjective(ref refuelObjective);
|
||||
ReportWeldingFuelTankCount();
|
||||
});
|
||||
|
||||
void ReportWeldingFuelTankCount()
|
||||
{
|
||||
int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag("weldingfuel") && i.Condition > 1);
|
||||
if (remainingOxygenTanks == 0)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogOutOfWeldingFuel"), null, 0.0f, "outofweldingfuel", 30.0f);
|
||||
}
|
||||
else if (remainingOxygenTanks < 4)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogLowOnWeldingFuel"), null, 0.0f, "lowonweldingfuel", 30.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (weldingTool.OwnInventory.AllItems.None(i => i.HasTag("weldingfuel") && i.Condition > 0.0f))
|
||||
{
|
||||
TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, "weldingfuel", weldingTool.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC),
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () => RemoveSubObjective(ref refuelObjective));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -42,7 +42,7 @@ namespace Barotrauma
|
||||
if (totalLeaks == 0) { return 0; }
|
||||
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveFixLeaks>() && !c.Character.IsIncapacitated, onlyBots: true);
|
||||
bool anyFixers = otherFixers > 0;
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
if (objectiveManager.IsOrder(this))
|
||||
{
|
||||
float ratio = anyFixers ? totalLeaks / (float)otherFixers : 1;
|
||||
return Targets.Sum(t => GetLeakSeverity(t)) * ratio;
|
||||
@@ -72,7 +72,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (gap == null) { return false; }
|
||||
// Don't fix a leak on a wall section set to be ignored
|
||||
if (gap.ConnectedWall?.Sections?.Any(s => s.gap == gap && s.IgnoreByAI) ?? false) { return false; }
|
||||
if (gap.ConnectedWall != null)
|
||||
{
|
||||
if (gap.ConnectedWall.Sections.Any(s => s.gap == gap && s.IgnoreByAI)) { return false; }
|
||||
if (gap.ConnectedWall.MaxHealth <= 0.0f) { return false; }
|
||||
}
|
||||
if (gap.ConnectedWall == null || gap.ConnectedDoor != null || gap.Open <= 0 || gap.linkedTo.All(l => l == null)) { return false; }
|
||||
if (gap.Submarine == null || character.Submarine == null) { return false; }
|
||||
// Don't allow going into another sub, unless it's connected and of the same team and type.
|
||||
|
||||
+83
-11
@@ -10,6 +10,8 @@ namespace Barotrauma
|
||||
{
|
||||
public override string DebugTag => "get item";
|
||||
|
||||
public override bool AbandonWhenCannotCompleteSubjectives => false;
|
||||
|
||||
private readonly bool equip;
|
||||
public HashSet<Item> ignoredItems = new HashSet<Item>();
|
||||
|
||||
@@ -44,6 +46,8 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public bool AllowStealing { get; set; }
|
||||
|
||||
public bool TakeWholeStack { get; set; }
|
||||
|
||||
public AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip = true, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
@@ -191,8 +195,20 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
Inventory itemInventory = targetItem.ParentInventory;
|
||||
var slots = itemInventory?.FindIndices(targetItem);
|
||||
if (HumanAIController.TakeItem(targetItem, character.Inventory, equip, storeUnequipped: true))
|
||||
{
|
||||
if (TakeWholeStack && slots != null)
|
||||
{
|
||||
foreach (int slot in slots)
|
||||
{
|
||||
foreach (Item item in itemInventory.GetItemsAt(slot).ToList())
|
||||
{
|
||||
HumanAIController.TakeItem(item, character.Inventory, equip: false, storeUnequipped: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
IsCompleted = true;
|
||||
}
|
||||
else
|
||||
@@ -211,9 +227,8 @@ namespace Barotrauma
|
||||
return new AIObjectiveGoTo(moveToTarget, character, objectiveManager, repeat: false, getDivingGearIfNeeded: AllowToFindDivingGear, closeEnough: DefaultReach)
|
||||
{
|
||||
// If the root container changes, the item is no longer where it was (taken by someone -> need to find another item)
|
||||
abortCondition = () => targetItem == null || targetItem.GetRootInventoryOwner() != moveToTarget,
|
||||
DialogueIdentifier = "dialogcannotreachtarget",
|
||||
TargetName = (moveToTarget as MapEntity)?.Name ?? (moveToTarget as Character)?.Name ?? moveToTarget.ToString()
|
||||
abortCondition = obj => targetItem == null || targetItem.GetRootInventoryOwner() != moveToTarget,
|
||||
SpeakIfFails = false
|
||||
};
|
||||
},
|
||||
onAbandon: () =>
|
||||
@@ -240,13 +255,18 @@ namespace Barotrauma
|
||||
if (targetItem == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot find the item, because neither identifiers nor item was defined.", Color.Red);
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot find an item, because neither identifiers nor item was defined.", Color.Red);
|
||||
#endif
|
||||
Abandon = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < 10 && currSearchIndex < Item.ItemList.Count - 1; i++)
|
||||
|
||||
float priority = Math.Clamp(objectiveManager.GetCurrentPriority(), 10, 100);
|
||||
bool checkPath = priority >= AIObjectiveManager.LowestOrderPriority && (objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>() || objectiveManager.CurrentOrder is AIObjectiveGoTo gotoOrder && gotoOrder.followControlledCharacter);
|
||||
bool hasCalledPathFinder = false;
|
||||
int itemsPerFrame = (int)priority;
|
||||
for (int i = 0; i < itemsPerFrame && currSearchIndex < Item.ItemList.Count - 1; i++)
|
||||
{
|
||||
currSearchIndex++;
|
||||
var item = Item.ItemList[currSearchIndex];
|
||||
@@ -259,9 +279,13 @@ namespace Barotrauma
|
||||
if (character.TeamID == CharacterTeamType.FriendlyNPC != item.SpawnedInOutpost) { continue; }
|
||||
}
|
||||
if (!CheckItem(item)) { continue; }
|
||||
if (ignoredContainerIdentifiers != null && item.Container != null)
|
||||
if (item.Container != null)
|
||||
{
|
||||
if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; }
|
||||
if (item.Container.HasTag("donttakeitems")) { continue; }
|
||||
if (ignoredContainerIdentifiers != null)
|
||||
{
|
||||
if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; }
|
||||
}
|
||||
}
|
||||
// Don't allow going into another sub, unless it's connected and of the same team and type.
|
||||
if (!character.Submarine.IsEntityFoundOnThisSub(item, includingConnectedSubs: true)) { continue; }
|
||||
@@ -287,8 +311,18 @@ namespace Barotrauma
|
||||
float distanceFactor = MathHelper.Lerp(1, 0, MathUtils.InverseLerp(0, 10000, dist));
|
||||
itemPriority *= distanceFactor;
|
||||
itemPriority *= item.Condition / item.MaxCondition;
|
||||
//ignore if the item has a lower priority than the currently selected one
|
||||
// Ignore if the item has a lower priority than the currently selected one
|
||||
if (itemPriority < currItemPriority) { continue; }
|
||||
if (!hasCalledPathFinder && PathSteering != null && checkPath)
|
||||
{
|
||||
// While following the player, let's ensure that there's a valid path to the target before accepting it.
|
||||
// Otherwise it will take some time for us to find a valid item when there are multiple items that we can't reach and some that we can.
|
||||
// This is relatively expensive, so let's do this only when it significantly improves the behavior.
|
||||
// Only allow one path find call per frame.
|
||||
hasCalledPathFinder = true;
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, item.SimPosition, errorMsgStr: $"AIObjectiveGetItem {character.DisplayName}", nodeFilter: node => node.Waypoint.CurrentHull != null);
|
||||
if (path.Unreachable) { continue; }
|
||||
}
|
||||
currItemPriority = itemPriority;
|
||||
targetItem = item;
|
||||
moveToTarget = rootInventoryOwner ?? item;
|
||||
@@ -303,7 +337,7 @@ namespace Barotrauma
|
||||
if (!(MapEntityPrefab.List.FirstOrDefault(me => me is ItemPrefab ip && identifiersOrTags.Any(id => id == ip.Identifier || ip.Tags.Contains(id))) is ItemPrefab prefab))
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}, tried to spawn the item but no matching item prefabs were found.", Color.Yellow);
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}, tried to spawn the item but no matching item prefabs were found.", Color.Yellow);
|
||||
#endif
|
||||
Abandon = true;
|
||||
}
|
||||
@@ -322,8 +356,9 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}", Color.Yellow);
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}", Color.Yellow);
|
||||
#endif
|
||||
SpeakCannotFind();
|
||||
Abandon = true;
|
||||
}
|
||||
}
|
||||
@@ -370,11 +405,48 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
private void ResetInternal()
|
||||
{
|
||||
goToObjective = null;
|
||||
RemoveSubObjective(ref goToObjective);
|
||||
targetItem = originalTarget;
|
||||
moveToTarget = targetItem?.GetRootInventoryOwner();
|
||||
isDoneSeeking = false;
|
||||
currSearchIndex = 0;
|
||||
currItemPriority = 0;
|
||||
}
|
||||
|
||||
protected override void OnAbandon()
|
||||
{
|
||||
base.OnAbandon();
|
||||
if (moveToTarget == null) { return; }
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Get item failed to reach {moveToTarget}", Color.Yellow);
|
||||
#endif
|
||||
}
|
||||
|
||||
private void SpeakCannotFind()
|
||||
{
|
||||
// TODO: Use the item name as the variable here.
|
||||
if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder == objectiveManager.CurrentObjective)
|
||||
{
|
||||
string msg = TextManager.Get("dialogcannotfinditem", true);
|
||||
if (msg != null)
|
||||
{
|
||||
character.Speak(msg, identifier: "dialogcannotfinditem", minDurationBetweenSimilar: 20.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove?
|
||||
private void SpeakCannotReach()
|
||||
{
|
||||
if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder == objectiveManager.CurrentObjective)
|
||||
{
|
||||
string TargetName = (moveToTarget as MapEntity)?.Name ?? (moveToTarget as Character)?.Name ?? moveToTarget.ToString();
|
||||
string msg = TargetName == null ? TextManager.Get("dialogcannotreachtarget", true) : TextManager.GetWithVariable("dialogcannotreachtarget", "[name]", TargetName, formatCapitals: !(moveToTarget is Character));
|
||||
if (msg != null)
|
||||
{
|
||||
character.Speak(msg, identifier: "dialogcannotreachtarget", minDurationBetweenSimilar: 20.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+49
-29
@@ -23,13 +23,14 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Aborts the objective when this condition is true
|
||||
/// </summary>
|
||||
public Func<bool> abortCondition;
|
||||
public Func<AIObjectiveGoTo, bool> abortCondition;
|
||||
public Func<PathNode, bool> endNodeFilter;
|
||||
|
||||
public Func<float> priorityGetter;
|
||||
|
||||
public bool followControlledCharacter;
|
||||
public bool mimic;
|
||||
public bool SpeakIfFails { get; set; } = true;
|
||||
|
||||
public float extraDistanceWhileSwimming;
|
||||
public float extraDistanceOutsideSub;
|
||||
@@ -66,6 +67,8 @@ namespace Barotrauma
|
||||
public bool IgnoreIfTargetDead { get; set; }
|
||||
public bool AllowGoingOutside { get; set; }
|
||||
|
||||
public bool AlwaysUseEuclideanDistance { get; set; } = true;
|
||||
|
||||
public override bool AbandonWhenCannotCompleteSubjectives => !repeat;
|
||||
|
||||
public override bool AllowOutsideSubmarine => AllowGoingOutside;
|
||||
@@ -80,19 +83,14 @@ namespace Barotrauma
|
||||
|
||||
public override float GetPriority()
|
||||
{
|
||||
bool isOrder = objectiveManager.CurrentOrder == this;
|
||||
bool isOrder = objectiveManager.IsOrder(this);
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = !isOrder;
|
||||
return Priority;
|
||||
}
|
||||
if (followControlledCharacter && Character.Controlled == null)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = !isOrder;
|
||||
}
|
||||
if (Target is Entity e && e.Removed)
|
||||
if (Target == null || Target is Entity e && e.Removed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = !isOrder;
|
||||
@@ -114,7 +112,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
Priority = isOrder ? AIObjectiveManager.OrderPriority : 10;
|
||||
Priority = isOrder ? objectiveManager.GetOrderPriority(this) : 10;
|
||||
}
|
||||
}
|
||||
return Priority;
|
||||
@@ -149,7 +147,7 @@ namespace Barotrauma
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {Target}", Color.Yellow);
|
||||
#endif
|
||||
if (objectiveManager.CurrentOrder != null && DialogueIdentifier != null)
|
||||
if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder == objectiveManager.CurrentObjective && DialogueIdentifier != null && SpeakIfFails)
|
||||
{
|
||||
string msg = TargetName == null ? TextManager.Get(DialogueIdentifier, true) : TextManager.GetWithVariable(DialogueIdentifier, "[name]", TargetName, formatCapitals: !(Target is Character));
|
||||
if (msg != null)
|
||||
@@ -163,13 +161,15 @@ namespace Barotrauma
|
||||
{
|
||||
if (followControlledCharacter)
|
||||
{
|
||||
if (Character.Controlled == null)
|
||||
if (Character.Controlled != null && HumanAIController.IsFriendly(Character.Controlled))
|
||||
{
|
||||
Target = Character.Controlled;
|
||||
}
|
||||
if (Target == null)
|
||||
{
|
||||
Abandon = true;
|
||||
SteeringManager.Reset();
|
||||
return;
|
||||
}
|
||||
Target = Character.Controlled;
|
||||
}
|
||||
if (Target == character || character.SelectedBy != null && HumanAIController.IsFriendly(character.SelectedBy))
|
||||
{
|
||||
@@ -187,7 +187,6 @@ namespace Barotrauma
|
||||
if (e.Removed)
|
||||
{
|
||||
Abandon = true;
|
||||
SteeringManager.Reset();
|
||||
return;
|
||||
}
|
||||
else
|
||||
@@ -199,7 +198,7 @@ namespace Barotrauma
|
||||
if (!followControlledCharacter)
|
||||
{
|
||||
// Abandon if going through unsafe paths. Note ignores unsafe nodes when following an order or when the objective is set to ignore unsafe hulls.
|
||||
bool containsUnsafeNodes = HumanAIController.CurrentOrder == null && !HumanAIController.ObjectiveManager.CurrentObjective.IgnoreUnsafeHulls
|
||||
bool containsUnsafeNodes = character.IsDismissed && !HumanAIController.ObjectiveManager.CurrentObjective.IgnoreUnsafeHulls
|
||||
&& PathSteering != null && PathSteering.CurrentPath != null
|
||||
&& PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull));
|
||||
if (containsUnsafeNodes || HumanAIController.UnreachableHulls.Contains(targetHull))
|
||||
@@ -249,16 +248,18 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
bool needsEquipment = false;
|
||||
float minOxygen = character.Submarine == null ? 0 : AIObjectiveFindDivingGear.MIN_OXYGEN;
|
||||
if (needsDivingSuit)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingSuit(character, AIObjectiveFindDivingGear.MIN_OXYGEN);
|
||||
needsEquipment = !HumanAIController.HasDivingSuit(character, minOxygen);
|
||||
}
|
||||
else if (needsDivingGear)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingGear(character, AIObjectiveFindDivingGear.MIN_OXYGEN);
|
||||
needsEquipment = !HumanAIController.HasDivingGear(character, minOxygen);
|
||||
}
|
||||
if (needsEquipment)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
if (findDivingGear != null && !findDivingGear.CanBeCompleted)
|
||||
{
|
||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: false, objectiveManager),
|
||||
@@ -287,9 +288,14 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
float maxGapDistance = 500;
|
||||
Character targetCharacter = Target as Character;
|
||||
if (character.AnimController.InWater)
|
||||
{
|
||||
if (character.CurrentHull == null)
|
||||
if (character.CurrentHull == null ||
|
||||
followControlledCharacter &&
|
||||
targetCharacter != null && (targetCharacter.CurrentHull == null) != (character.CurrentHull == null) &&
|
||||
Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition) < maxGapDistance * maxGapDistance)
|
||||
{
|
||||
if (seekGapsTimer > 0)
|
||||
{
|
||||
@@ -297,7 +303,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
SeekGaps(maxDistance: 500);
|
||||
SeekGaps(maxGapDistance);
|
||||
seekGapsTimer = seekGapsInterval * Rand.Range(0.1f, 1.1f);
|
||||
if (TargetGap != null)
|
||||
{
|
||||
@@ -326,7 +332,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (TargetGap != null)
|
||||
{
|
||||
if (TargetGap.FlowTargetHull != null && HumanAIController.SteerThroughGap(TargetGap, TargetGap.FlowTargetHull.WorldPosition, deltaTime))
|
||||
if (TargetGap.FlowTargetHull != null && HumanAIController.SteerThroughGap(TargetGap, followControlledCharacter ? Target.WorldPosition : TargetGap.FlowTargetHull.WorldPosition, deltaTime))
|
||||
{
|
||||
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 1);
|
||||
return;
|
||||
@@ -346,7 +352,7 @@ namespace Barotrauma
|
||||
float closeEnough = 250;
|
||||
float squaredDistance = Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition);
|
||||
bool shouldUseScooter = squaredDistance > closeEnough * closeEnough && (!mimic ||
|
||||
(Target is Character targetCharacter && targetCharacter.HasEquippedItem(scooterTag, allowBroken: false)) || squaredDistance > Math.Pow(closeEnough * 2, 2));
|
||||
(targetCharacter != null && targetCharacter.HasEquippedItem(scooterTag, allowBroken: false)) || squaredDistance > Math.Pow(closeEnough * 2, 2));
|
||||
if (HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> equippedScooters, recursive: false, requireEquipped: true))
|
||||
{
|
||||
// Currently equipped scooter
|
||||
@@ -527,17 +533,24 @@ namespace Barotrauma
|
||||
{
|
||||
Gap selectedGap = null;
|
||||
float selectedDistance = -1;
|
||||
Vector2 toTargetNormalized = Vector2.Normalize(Target.WorldPosition - character.WorldPosition);
|
||||
foreach (Gap gap in Gap.GapList)
|
||||
{
|
||||
if (gap.Open < 1) { continue; }
|
||||
if (gap.FlowTargetHull == null) { continue; }
|
||||
if (gap.Submarine != Target.Submarine) { continue; }
|
||||
float distance = Vector2.DistanceSquared(character.WorldPosition, gap.WorldPosition);
|
||||
if (distance > maxDistance * maxDistance) { continue; }
|
||||
if (selectedGap == null || distance < selectedDistance)
|
||||
if (gap.Submarine == null) { continue; }
|
||||
if (!followControlledCharacter)
|
||||
{
|
||||
if (gap.FlowTargetHull == null) { continue; }
|
||||
if (gap.Submarine != Target.Submarine) { continue; }
|
||||
}
|
||||
Vector2 toGap = gap.WorldPosition - character.WorldPosition;
|
||||
if (Vector2.Dot(Vector2.Normalize(toGap), toTargetNormalized) < 0) { continue; }
|
||||
float squaredDistance = toGap.LengthSquared();
|
||||
if (squaredDistance > maxDistance * maxDistance) { continue; }
|
||||
if (selectedGap == null || squaredDistance < selectedDistance)
|
||||
{
|
||||
selectedGap = gap;
|
||||
selectedDistance = distance;
|
||||
selectedDistance = squaredDistance;
|
||||
}
|
||||
}
|
||||
TargetGap = selectedGap;
|
||||
@@ -554,6 +567,13 @@ namespace Barotrauma
|
||||
//otherwise characters can let go of the ladders too soon once they're close enough to the target
|
||||
if (PathSteering.CurrentPath.NextNode != null) { return false; }
|
||||
}
|
||||
if (!AlwaysUseEuclideanDistance && !character.AnimController.InWater)
|
||||
{
|
||||
float yDiff = Math.Abs(Target.WorldPosition.Y - character.WorldPosition.Y);
|
||||
if (yDiff > CloseEnough) { return false; }
|
||||
float xDiff = Math.Abs(Target.WorldPosition.X - character.WorldPosition.X);
|
||||
return xDiff <= CloseEnough;
|
||||
}
|
||||
return Vector2.DistanceSquared(Target.WorldPosition, character.WorldPosition) < CloseEnough * CloseEnough;
|
||||
}
|
||||
}
|
||||
@@ -569,7 +589,7 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
return false;
|
||||
}
|
||||
if (abortCondition != null && abortCondition())
|
||||
if (abortCondition != null && abortCondition(this))
|
||||
{
|
||||
Abandon = true;
|
||||
return false;
|
||||
@@ -617,7 +637,7 @@ namespace Barotrauma
|
||||
|
||||
private void StopMovement()
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
SteeringManager.Reset();
|
||||
if (Target != null)
|
||||
{
|
||||
character.AnimController.TargetDir = Target.WorldPosition.X > character.WorldPosition.X ? Direction.Right : Direction.Left;
|
||||
|
||||
+15
-3
@@ -21,9 +21,9 @@ namespace Barotrauma
|
||||
set
|
||||
{
|
||||
behavior = value;
|
||||
if (behavior == BehaviorType.StayInHull && character.TeamID != CharacterTeamType.FriendlyNPC)
|
||||
if (behavior == BehaviorType.StayInHull && TargetHull == null)
|
||||
{
|
||||
DebugConsole.NewMessage($"AIObjectiveIdle.BehaviorType.StayInHull is implemented only for outpost NPCs. Using passive behavior for {character.Name} ({character.Info.Job.Prefab.Identifier})", color: Color.Red);
|
||||
DebugConsole.AddWarning($"Trying to set a character's behavior type to StayInHull, but target hull is not set. {character.Name} ({character.Info.Job.Prefab.Identifier})");
|
||||
behavior = BehaviorType.Passive;
|
||||
}
|
||||
switch (behavior)
|
||||
@@ -495,7 +495,7 @@ namespace Barotrauma
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.CurrentHull != hull) { continue; }
|
||||
if (AIObjectiveCleanupItems.IsValidTarget(item, character, checkInventory: true) && !ignoredItems.Contains(item))
|
||||
if (AIObjectiveCleanupItems.IsValidTarget(item, character, checkInventory: true, allowUnloading: false) && !ignoredItems.Contains(item))
|
||||
{
|
||||
itemsToClean.Add(item);
|
||||
}
|
||||
@@ -540,5 +540,17 @@ namespace Barotrauma
|
||||
ignoredItems.Clear();
|
||||
autonomousObjectiveRetryTimer = 10;
|
||||
}
|
||||
|
||||
public override void OnDeselected()
|
||||
{
|
||||
base.OnDeselected();
|
||||
foreach (var subObjective in SubObjectives)
|
||||
{
|
||||
if (subObjective is AIObjectiveCleanupItem cleanUpObjective)
|
||||
{
|
||||
cleanUpObjective.DropTarget();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-9
@@ -11,6 +11,7 @@ namespace Barotrauma
|
||||
protected HashSet<T> ignoreList = new HashSet<T>();
|
||||
private float ignoreListTimer;
|
||||
protected float targetUpdateTimer;
|
||||
protected virtual float TargetUpdateTimeMultiplier { get; } = 1;
|
||||
|
||||
private float syncTimer;
|
||||
private readonly float syncTime = 1;
|
||||
@@ -61,7 +62,7 @@ namespace Barotrauma
|
||||
ignoreListTimer += deltaTime;
|
||||
}
|
||||
}
|
||||
if (targetUpdateTimer < 0)
|
||||
if (targetUpdateTimer <= 0)
|
||||
{
|
||||
UpdateTargets();
|
||||
}
|
||||
@@ -69,9 +70,9 @@ namespace Barotrauma
|
||||
{
|
||||
targetUpdateTimer -= deltaTime;
|
||||
}
|
||||
if (syncTimer < 0)
|
||||
if (syncTimer <= 0)
|
||||
{
|
||||
syncTimer = syncTime * Rand.Range(0.9f, 1.1f);
|
||||
syncTimer = Math.Min(syncTime * Rand.Range(0.9f, 1.1f), targetUpdateTimer);
|
||||
// Sync objectives, subobjectives and targets
|
||||
foreach (var objective in Objectives)
|
||||
{
|
||||
@@ -95,7 +96,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
// the timer is set between 1 and 10 seconds, depending on the priority modifier and a random +-25%
|
||||
private float SetTargetUpdateTimer() => targetUpdateTimer = 1 / MathHelper.Clamp(PriorityModifier * Rand.Range(0.75f, 1.25f), 0.1f, 1);
|
||||
private float CalculateTargetUpdateTimer() => targetUpdateTimer = 1 / MathHelper.Clamp(PriorityModifier * Rand.Range(0.75f, 1.25f), 0.1f, 1) * TargetUpdateTimeMultiplier;
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
@@ -139,13 +140,13 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
if (objectiveManager.IsOrder(this))
|
||||
{
|
||||
Priority = ForceOrderPriority ? AIObjectiveManager.OrderPriority : targetValue;
|
||||
Priority = ForceOrderPriority ? objectiveManager.GetOrderPriority(this) : targetValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
|
||||
float max = AIObjectiveManager.LowestOrderPriority - 1;
|
||||
float value = MathHelper.Clamp((CumulatedDevotion + (targetValue * PriorityModifier)) / 100, 0, 1);
|
||||
Priority = MathHelper.Lerp(0, max, value);
|
||||
}
|
||||
@@ -156,7 +157,7 @@ namespace Barotrauma
|
||||
|
||||
protected void UpdateTargets()
|
||||
{
|
||||
SetTargetUpdateTimer();
|
||||
CalculateTargetUpdateTimer();
|
||||
Targets.Clear();
|
||||
FindTargets();
|
||||
CreateObjectives();
|
||||
@@ -167,7 +168,7 @@ namespace Barotrauma
|
||||
foreach (T target in GetList())
|
||||
{
|
||||
// The bots always find targets when the objective is an order.
|
||||
if (objectiveManager.CurrentOrder != this)
|
||||
if (!objectiveManager.IsOrder(this))
|
||||
{
|
||||
// Battery or pump states cannot currently be reported (not implemented) and therefore we must ignore them -> the bots always know if they require attention.
|
||||
bool ignore = this is AIObjectiveChargeBatteries || this is AIObjectivePumpWater;
|
||||
|
||||
+232
-92
@@ -1,6 +1,6 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Networking; // used by the server
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -10,8 +10,8 @@ namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveManager
|
||||
{
|
||||
// TODO: expose
|
||||
public const float OrderPriority = 70;
|
||||
public const float HighestOrderPriority = 70;
|
||||
public const float LowestOrderPriority = 60;
|
||||
public const float RunPriority = 50;
|
||||
// Constantly increases the priority of the selected objective, unless overridden
|
||||
public const float baseDevotion = 5;
|
||||
@@ -25,7 +25,6 @@ namespace Barotrauma
|
||||
|
||||
public HumanAIController HumanAIController => character.AIController as HumanAIController;
|
||||
|
||||
|
||||
private float _waitTimer;
|
||||
/// <summary>
|
||||
/// When set above zero, the character will stand still doing nothing until the timer runs out. Does not affect orders, find safety or combat.
|
||||
@@ -39,26 +38,25 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public AIObjective CurrentOrder { get; private set; }
|
||||
public List<OrderInfo> CurrentOrders { get; } = new List<OrderInfo>();
|
||||
/// <summary>
|
||||
/// The AIObjective in <see cref="CurrentOrders"/> with the highest <see cref="AIObjective.Priority"/>
|
||||
/// </summary>
|
||||
public AIObjective CurrentOrder
|
||||
{
|
||||
get
|
||||
{
|
||||
return ForcedOrder ?? currentOrder;
|
||||
}
|
||||
private set
|
||||
{
|
||||
currentOrder = value;
|
||||
}
|
||||
}
|
||||
private AIObjective currentOrder;
|
||||
public AIObjective ForcedOrder { get; private set; }
|
||||
public AIObjective CurrentObjective { get; private set; }
|
||||
|
||||
public bool IsCurrentOrder<T>() where T : AIObjective => CurrentOrder is T;
|
||||
public bool IsCurrentObjective<T>() where T : AIObjective => CurrentObjective is T;
|
||||
public bool IsActiveObjective<T>() where T : AIObjective => GetActiveObjective() is T;
|
||||
|
||||
public AIObjective GetActiveObjective() => CurrentObjective?.GetActiveObjective();
|
||||
/// <summary>
|
||||
/// Returns the last active objective of the specific type.
|
||||
/// </summary>
|
||||
public T GetActiveObjective<T>() where T : AIObjective => CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).LastOrDefault(so => so is T) as T;
|
||||
|
||||
/// <summary>
|
||||
/// Returns all active objectives of the specific type. Creates a new collection -> don't use too frequently.
|
||||
/// </summary>
|
||||
public IEnumerable<T> GetActiveObjectives<T>() where T : AIObjective => CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).Where(so => so is T).Select(so => so as T);
|
||||
|
||||
public bool HasActiveObjective<T>() where T : AIObjective => CurrentObjective is T || CurrentObjective != null && CurrentObjective.GetSubObjectivesRecursive().Any(so => so is T);
|
||||
|
||||
public AIObjectiveManager(Character character)
|
||||
{
|
||||
this.character = character;
|
||||
@@ -134,7 +132,13 @@ namespace Barotrauma
|
||||
}
|
||||
var order = new Order(orderPrefab, item ?? character.CurrentHull as Entity, orderPrefab.GetTargetItemComponent(item), orderGiver: character);
|
||||
if (order == null) { continue; }
|
||||
if (autonomousObjective.ignoreAtOutpost && Level.IsLoadedOutpost && character.TeamID != CharacterTeamType.FriendlyNPC) { continue; }
|
||||
if (autonomousObjective.ignoreAtOutpost && Level.IsLoadedOutpost && character.TeamID != CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
if (Submarine.MainSub != null && Submarine.MainSub.DockedTo.None(s => s.TeamID != CharacterTeamType.FriendlyNPC && s.TeamID != character.TeamID))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
var objective = CreateObjective(order, autonomousObjective.option, character, isAutonomous: true, autonomousObjective.priorityModifier);
|
||||
if (objective != null && objective.CanBeCompleted)
|
||||
{
|
||||
@@ -162,7 +166,11 @@ namespace Barotrauma
|
||||
coroutine = CoroutineManager.InvokeAfter(() =>
|
||||
{
|
||||
//round ended before the coroutine finished
|
||||
#if CLIENT
|
||||
if (GameMain.GameSession == null || Level.Loaded == null && !(GameMain.GameSession.GameMode is TestGameMode)) { return; }
|
||||
#else
|
||||
if (GameMain.GameSession == null || Level.Loaded == null) { return; }
|
||||
#endif
|
||||
DelayedObjectives.Remove(objective);
|
||||
AddObjective(objective);
|
||||
callback?.Invoke();
|
||||
@@ -200,21 +208,34 @@ namespace Barotrauma
|
||||
|
||||
public void UpdateObjectives(float deltaTime)
|
||||
{
|
||||
if (CurrentOrder != null)
|
||||
UpdateOrderObjective(ForcedOrder);
|
||||
|
||||
if (CurrentOrders.Any())
|
||||
{
|
||||
foreach(var order in CurrentOrders)
|
||||
{
|
||||
var orderObjective = order.Objective;
|
||||
UpdateOrderObjective(orderObjective);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateOrderObjective(AIObjective orderObjective)
|
||||
{
|
||||
if (orderObjective == null) { return; }
|
||||
#if DEBUG
|
||||
// Note: don't automatically remove orders here. Removing orders needs to be done via dismissing.
|
||||
if (CurrentOrder.IsCompleted)
|
||||
if (orderObjective.IsCompleted)
|
||||
{
|
||||
DebugConsole.NewMessage($"{character.Name}: ORDER {CurrentOrder.DebugTag} IS COMPLETED. CURRENTLY ALL ORDERS SHOULD BE LOOPING.", Color.Red);
|
||||
DebugConsole.NewMessage($"{character.Name}: ORDER {orderObjective.DebugTag} IS COMPLETED. CURRENTLY ALL ORDERS SHOULD BE LOOPING.", Color.Red);
|
||||
}
|
||||
else if (!CurrentOrder.CanBeCompleted)
|
||||
else if (!orderObjective.CanBeCompleted)
|
||||
{
|
||||
DebugConsole.NewMessage($"{character.Name}: ORDER {CurrentOrder.DebugTag}, CANNOT BE COMPLETED.", Color.Red);
|
||||
DebugConsole.NewMessage($"{character.Name}: ORDER {orderObjective.DebugTag}, CANNOT BE COMPLETED.", Color.Red);
|
||||
}
|
||||
#endif
|
||||
CurrentOrder.Update(deltaTime);
|
||||
orderObjective.Update(deltaTime);
|
||||
}
|
||||
|
||||
if (WaitTimer > 0)
|
||||
{
|
||||
WaitTimer -= deltaTime;
|
||||
@@ -248,7 +269,29 @@ namespace Barotrauma
|
||||
|
||||
public void SortObjectives()
|
||||
{
|
||||
CurrentOrder?.GetPriority();
|
||||
ForcedOrder?.GetPriority();
|
||||
|
||||
AIObjective orderWithHighestPriority = null;
|
||||
float highestPriority = 0;
|
||||
foreach (var currentOrder in CurrentOrders)
|
||||
{
|
||||
var orderObjective = currentOrder.Objective;
|
||||
if (orderObjective == null) { continue; }
|
||||
orderObjective.GetPriority();
|
||||
if (orderWithHighestPriority == null || orderObjective.Priority > highestPriority)
|
||||
{
|
||||
orderWithHighestPriority = orderObjective;
|
||||
highestPriority = orderObjective.Priority;
|
||||
}
|
||||
}
|
||||
#if SERVER
|
||||
if (orderWithHighestPriority != null && orderWithHighestPriority != currentOrder)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(character, new object[] { NetEntityEvent.Type.ObjectiveManagerOrderState });
|
||||
}
|
||||
#endif
|
||||
CurrentOrder = orderWithHighestPriority;
|
||||
|
||||
for (int i = Objectives.Count - 1; i >= 0; i--)
|
||||
{
|
||||
Objectives[i].GetPriority();
|
||||
@@ -257,6 +300,7 @@ namespace Barotrauma
|
||||
{
|
||||
Objectives.Sort((x, y) => y.Priority.CompareTo(x.Priority));
|
||||
}
|
||||
|
||||
GetCurrentObjective()?.SortSubObjectives();
|
||||
}
|
||||
|
||||
@@ -272,13 +316,19 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void SetOrder(AIObjective objective)
|
||||
public void SetForcedOrder(AIObjective objective)
|
||||
{
|
||||
CurrentOrder = objective;
|
||||
ForcedOrder = objective;
|
||||
}
|
||||
|
||||
public void ClearForcedOrder()
|
||||
{
|
||||
ForcedOrder = null;
|
||||
SortObjectives();
|
||||
}
|
||||
|
||||
private CoroutineHandle speakRoutine;
|
||||
public void SetOrder(Order order, string option, Character orderGiver, bool speak)
|
||||
public void SetOrder(Order order, string option, int priority, Character orderGiver, bool speak)
|
||||
{
|
||||
if (character.IsDead)
|
||||
{
|
||||
@@ -289,8 +339,53 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
ClearIgnored();
|
||||
CurrentOrder = CreateObjective(order, option, orderGiver, isAutonomous: false);
|
||||
if (CurrentOrder == null)
|
||||
|
||||
if (order == null || order.Identifier == "dismissed")
|
||||
{
|
||||
if (!string.IsNullOrEmpty(option))
|
||||
{
|
||||
if (CurrentOrders.Any(o => o.MatchesDismissedOrder(option)))
|
||||
{
|
||||
var dismissedOrderInfo = CurrentOrders.First(o => o.MatchesDismissedOrder(option));
|
||||
CurrentOrders.Remove(dismissedOrderInfo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentOrders.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the order priorities reflect those set by the player
|
||||
for (int i = CurrentOrders.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var currentOrder = CurrentOrders[i];
|
||||
if (currentOrder.Objective == null || currentOrder.MatchesOrder(order, option))
|
||||
{
|
||||
CurrentOrders.RemoveAt(i);
|
||||
continue;
|
||||
}
|
||||
var currentOrderInfo = character.GetCurrentOrder(currentOrder.Order, currentOrder.OrderOption);
|
||||
if (currentOrderInfo.HasValue)
|
||||
{
|
||||
int currentPriority = currentOrderInfo.Value.ManualPriority;
|
||||
if (currentOrder.ManualPriority != currentPriority)
|
||||
{
|
||||
CurrentOrders[i] = new OrderInfo(currentOrder, currentPriority);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentOrders.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
var newCurrentOrder = CreateObjective(order, option, orderGiver, isAutonomous: false);
|
||||
if (newCurrentOrder != null)
|
||||
{
|
||||
CurrentOrders.Add(new OrderInfo(order, option, priority, newCurrentOrder));
|
||||
}
|
||||
if (!HasOrders())
|
||||
{
|
||||
// Recreate objectives, because some of them may be removed, if impossible to complete (e.g. due to path finding)
|
||||
CreateAutonomousObjectives();
|
||||
@@ -298,56 +393,57 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
// This should be redundant, because all the objectives are reset when they are selected as active.
|
||||
CurrentOrder.Reset();
|
||||
if (speak)
|
||||
newCurrentOrder?.Reset();
|
||||
|
||||
if (speak && character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogAffirmative"), null, 1.0f);
|
||||
if (speakRoutine != null)
|
||||
{
|
||||
CoroutineManager.StopCoroutines(speakRoutine);
|
||||
}
|
||||
speakRoutine = CoroutineManager.InvokeAfter(() =>
|
||||
{
|
||||
if (GameMain.GameSession == null || Level.Loaded == null) { return; }
|
||||
if (CurrentOrder != null && character.SpeechImpediment < 100.0f)
|
||||
{
|
||||
if (CurrentOrder is AIObjectiveRepairItems repairItems && repairItems.Targets.None())
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNoRepairTargets"), null, 3.0f, "norepairtargets");
|
||||
}
|
||||
else if (CurrentOrder is AIObjectiveChargeBatteries chargeBatteries && chargeBatteries.Targets.None())
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNoBatteries"), null, 3.0f, "nobatteries");
|
||||
}
|
||||
else if (CurrentOrder is AIObjectiveExtinguishFires extinguishFires && extinguishFires.Targets.None())
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNoFire"), null, 3.0f, "nofire");
|
||||
}
|
||||
else if (CurrentOrder is AIObjectiveFixLeaks fixLeaks && fixLeaks.Targets.None())
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNoLeaks"), null, 3.0f, "noleaks");
|
||||
}
|
||||
else if (CurrentOrder is AIObjectiveFightIntruders fightIntruders && fightIntruders.Targets.None())
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNoEnemies"), null, 3.0f, "noenemies");
|
||||
}
|
||||
else if (CurrentOrder is AIObjectiveRescueAll rescueAll && rescueAll.Targets.None())
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNoRescueTargets"), null, 3.0f, "norescuetargets");
|
||||
}
|
||||
else if (CurrentOrder is AIObjectivePumpWater pumpWater && pumpWater.Targets.None())
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNoPumps"), null, 3.0f, "nopumps");
|
||||
}
|
||||
}
|
||||
}, 3);
|
||||
//if (speakRoutine != null)
|
||||
//{
|
||||
// CoroutineManager.StopCoroutines(speakRoutine);
|
||||
//}
|
||||
//speakRoutine = CoroutineManager.InvokeAfter(() =>
|
||||
//{
|
||||
// if (GameMain.GameSession == null || Level.Loaded == null) { return; }
|
||||
// if (newCurrentOrder != null && character.SpeechImpediment < 100.0f)
|
||||
// {
|
||||
// if (newCurrentOrder is AIObjectiveRepairItems repairItems && repairItems.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoRepairTargets"), null, 3.0f, "norepairtargets");
|
||||
// }
|
||||
// else if (newCurrentOrder is AIObjectiveChargeBatteries chargeBatteries && chargeBatteries.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoBatteries"), null, 3.0f, "nobatteries");
|
||||
// }
|
||||
// else if (newCurrentOrder is AIObjectiveExtinguishFires extinguishFires && extinguishFires.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoFire"), null, 3.0f, "nofire");
|
||||
// }
|
||||
// else if (newCurrentOrder is AIObjectiveFixLeaks fixLeaks && fixLeaks.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoLeaks"), null, 3.0f, "noleaks");
|
||||
// }
|
||||
// else if (newCurrentOrder is AIObjectiveFightIntruders fightIntruders && fightIntruders.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoEnemies"), null, 3.0f, "noenemies");
|
||||
// }
|
||||
// else if (newCurrentOrder is AIObjectiveRescueAll rescueAll && rescueAll.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoRescueTargets"), null, 3.0f, "norescuetargets");
|
||||
// }
|
||||
// else if (newCurrentOrder is AIObjectivePumpWater pumpWater && pumpWater.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoPumps"), null, 3.0f, "nopumps");
|
||||
// }
|
||||
// }
|
||||
//}, 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AIObjective CreateObjective(Order order, string option, Character orderGiver, bool isAutonomous, float priorityModifier = 1)
|
||||
{
|
||||
if (order == null) { return null; }
|
||||
if (order == null || order.Identifier == "dismissed") { return null; }
|
||||
AIObjective newObjective;
|
||||
switch (order.Identifier.ToLowerInvariant())
|
||||
{
|
||||
@@ -360,7 +456,7 @@ namespace Barotrauma
|
||||
extraDistanceWhileSwimming = 100,
|
||||
AllowGoingOutside = true,
|
||||
IgnoreIfTargetDead = true,
|
||||
followControlledCharacter = orderGiver == character,
|
||||
followControlledCharacter = true,
|
||||
mimic = true,
|
||||
DialogueIdentifier = "dialogcannotreachplace"
|
||||
};
|
||||
@@ -430,7 +526,7 @@ namespace Barotrauma
|
||||
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, false, priorityModifier: priorityModifier)
|
||||
{
|
||||
IsLoop = false,
|
||||
Override = character.CurrentOrder != null,
|
||||
Override = !character.IsDismissed,
|
||||
completionCondition = () =>
|
||||
{
|
||||
if (float.TryParse(option, out float pct))
|
||||
@@ -483,21 +579,9 @@ namespace Barotrauma
|
||||
return newObjective;
|
||||
}
|
||||
|
||||
private void DismissSelf()
|
||||
{
|
||||
#if CLIENT
|
||||
if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.IsSinglePlayer)
|
||||
{
|
||||
GameMain.GameSession?.CrewManager?.SetCharacterOrder(character, Order.GetPrefab("dismissed"), null, character);
|
||||
}
|
||||
#else
|
||||
GameMain.Server?.SendOrderChatMessage(new OrderChatMessage(Order.GetPrefab("dismissed"), null, null, character, character));
|
||||
#endif
|
||||
}
|
||||
|
||||
private bool IsAllowedToWait()
|
||||
{
|
||||
if (CurrentOrder != null) { return false; }
|
||||
if (HasOrders()) { return false; }
|
||||
if (CurrentObjective is AIObjectiveCombat || CurrentObjective is AIObjectiveFindSafety) { return false; }
|
||||
if (character.AnimController.InWater) { return false; }
|
||||
if (character.IsClimbing) { return false; }
|
||||
@@ -508,5 +592,61 @@ namespace Barotrauma
|
||||
if (AIObjectiveIdle.IsForbidden(character.CurrentHull)) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsCurrentOrder<T>() where T : AIObjective => CurrentOrder is T;
|
||||
public bool IsCurrentObjective<T>() where T : AIObjective => CurrentObjective is T;
|
||||
public bool IsActiveObjective<T>() where T : AIObjective => GetActiveObjective() is T;
|
||||
|
||||
public AIObjective GetActiveObjective() => CurrentObjective?.GetActiveObjective();
|
||||
/// <summary>
|
||||
/// Returns the last active objective of the specific type.
|
||||
/// </summary>
|
||||
public T GetActiveObjective<T>() where T : AIObjective => CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).LastOrDefault(so => so is T) as T;
|
||||
|
||||
/// <summary>
|
||||
/// Returns all active objectives of the specific type. Creates a new collection -> don't use too frequently.
|
||||
/// </summary>
|
||||
public IEnumerable<T> GetActiveObjectives<T>() where T : AIObjective => CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).Where(so => so is T).Select(so => so as T);
|
||||
|
||||
public bool HasActiveObjective<T>() where T : AIObjective => CurrentObjective is T || CurrentObjective != null && CurrentObjective.GetSubObjectivesRecursive().Any(so => so is T);
|
||||
|
||||
public bool IsOrder(AIObjective objective)
|
||||
{
|
||||
return objective == ForcedOrder || CurrentOrders.Any(o => o.Objective == objective);
|
||||
}
|
||||
|
||||
public bool HasOrders()
|
||||
{
|
||||
return ForcedOrder != null || CurrentOrders.Any();
|
||||
}
|
||||
|
||||
public bool HasOrder<T>() where T : AIObjective
|
||||
{
|
||||
return ForcedOrder is T || CurrentOrders.Any(o => o.Objective is T);
|
||||
}
|
||||
|
||||
public float GetOrderPriority(AIObjective objective)
|
||||
{
|
||||
if (objective == ForcedOrder) { return HighestOrderPriority; }
|
||||
var currentOrder = CurrentOrders.FirstOrDefault(o => o.Objective == objective);
|
||||
if (currentOrder.Objective == null)
|
||||
{
|
||||
return HighestOrderPriority;
|
||||
}
|
||||
else if (currentOrder.ManualPriority > 0)
|
||||
{
|
||||
return MathHelper.Lerp(LowestOrderPriority, HighestOrderPriority, MathUtils.InverseLerp(1, CharacterInfo.HighestManualOrderPriority, currentOrder.ManualPriority));
|
||||
}
|
||||
#if DEBUG
|
||||
DebugConsole.AddWarning("Error in order priority: shouldn't return 0!");
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
public OrderInfo? GetCurrentOrderInfo()
|
||||
{
|
||||
if (currentOrder == null) { return null; }
|
||||
return CurrentOrders.FirstOrDefault(o => o.Objective == CurrentOrder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+30
-15
@@ -36,7 +36,7 @@ namespace Barotrauma
|
||||
|
||||
public override float GetPriority()
|
||||
{
|
||||
bool isOrder = objectiveManager.CurrentOrder == this;
|
||||
bool isOrder = objectiveManager.IsOrder(this);
|
||||
if (!IsAllowed || character.LockHands)
|
||||
{
|
||||
Priority = 0;
|
||||
@@ -51,7 +51,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (isOrder)
|
||||
{
|
||||
Priority = AIObjectiveManager.OrderPriority;
|
||||
Priority = objectiveManager.GetOrderPriority(this);
|
||||
}
|
||||
ItemComponent target = GetTarget();
|
||||
Item targetItem = target?.Item;
|
||||
@@ -69,10 +69,9 @@ namespace Barotrauma
|
||||
{
|
||||
if (!isOrder)
|
||||
{
|
||||
if (reactor.LastUserWasPlayer && character.TeamID != CharacterTeamType.FriendlyNPC ||
|
||||
HumanAIController.IsTrueForAnyCrewMember(c =>
|
||||
c.ObjectiveManager.CurrentOrder is AIObjectiveOperateItem operateOrder && operateOrder.GetTarget() == target))
|
||||
if (reactor.LastUserWasPlayer && character.TeamID != CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
// The reactor was previously operated by a player -> ignore.
|
||||
Priority = 0;
|
||||
return Priority;
|
||||
}
|
||||
@@ -89,11 +88,15 @@ namespace Barotrauma
|
||||
case "powerup":
|
||||
// Check that we don't already have another order that is targeting the same item.
|
||||
// Without this the autonomous objective will tell the bot to turn the reactor on again.
|
||||
if (objectiveManager.CurrentOrder is AIObjectiveOperateItem operateOrder && operateOrder != this && operateOrder.GetTarget() == target && operateOrder.Option != Option)
|
||||
if (IsAnotherOrderTargetingSameItem(objectiveManager.ForcedOrder) || objectiveManager.CurrentOrders.Any(o => IsAnotherOrderTargetingSameItem(o.Objective)))
|
||||
{
|
||||
Priority = 0;
|
||||
return Priority;
|
||||
}
|
||||
bool IsAnotherOrderTargetingSameItem(AIObjective objective)
|
||||
{
|
||||
return objective is AIObjectiveOperateItem operateObjective && operateObjective != this && operateObjective.GetTarget() == target && operateObjective.Option != Option;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -108,14 +111,23 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
float value = CumulatedDevotion + (AIObjectiveManager.OrderPriority * PriorityModifier);
|
||||
float max = isOrder ? MathHelper.Min(AIObjectiveManager.OrderPriority, 90) : AIObjectiveManager.RunPriority - 1;
|
||||
if (!isOrder && reactor != null && reactor.PowerOn && Option == "powerup")
|
||||
if (isOrder)
|
||||
{
|
||||
// Decrease the priority when targeting a reactor that is already on.
|
||||
value /= 2;
|
||||
float max = objectiveManager.GetOrderPriority(this);
|
||||
float value = CumulatedDevotion + (max * PriorityModifier);
|
||||
Priority = MathHelper.Clamp(value, 0, max);
|
||||
}
|
||||
else
|
||||
{
|
||||
float value = CumulatedDevotion + (AIObjectiveManager.LowestOrderPriority * PriorityModifier);
|
||||
float max = AIObjectiveManager.LowestOrderPriority - 1;
|
||||
if (reactor != null && reactor.PowerOn && reactor.FissionRate > 1 && Option == "powerup")
|
||||
{
|
||||
// Decrease the priority when targeting a reactor that is already on.
|
||||
value /= 2;
|
||||
}
|
||||
Priority = MathHelper.Clamp(value, 0, max);
|
||||
}
|
||||
Priority = MathHelper.Clamp(value, 0, max);
|
||||
}
|
||||
}
|
||||
return Priority;
|
||||
@@ -154,15 +166,18 @@ namespace Barotrauma
|
||||
ItemComponent target = GetTarget();
|
||||
if (useController && controller == null)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("DialogCantFindController", "[item]", component.Item.Name, true), null, 2.0f, "cantfindcontroller", 30.0f);
|
||||
if (character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("DialogCantFindController", "[item]", component.Item.Name, true), null, 2.0f, "cantfindcontroller", 30.0f);
|
||||
}
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (operateTarget != null)
|
||||
{
|
||||
if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.ObjectiveManager.GetActiveObjective() is AIObjectiveOperateItem operateObjective && operateObjective.operateTarget == operateTarget))
|
||||
if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.Character.IsBot && other.ObjectiveManager.GetActiveObjective() is AIObjectiveOperateItem operateObjective && operateObjective.operateTarget == operateTarget))
|
||||
{
|
||||
// Another crew member is already targeting this entity.
|
||||
// Another crew member is already targeting this entity (leak).
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
|
||||
+11
-27
@@ -59,13 +59,13 @@ namespace Barotrauma
|
||||
float dist = Math.Abs(character.WorldPosition.X - Item.WorldPosition.X) + yDist;
|
||||
distanceFactor = MathHelper.Lerp(1, 0.25f, MathUtils.InverseLerp(0, 4000, dist));
|
||||
}
|
||||
float requiredSuccessFactor = objectiveManager.IsCurrentOrder<AIObjectiveRepairItems>() ? 0 : AIObjectiveRepairItems.RequiredSuccessFactor;
|
||||
float requiredSuccessFactor = objectiveManager.HasOrder<AIObjectiveRepairItems>() ? 0 : AIObjectiveRepairItems.RequiredSuccessFactor;
|
||||
float severity = isPriority ? 1 : AIObjectiveRepairItems.GetTargetPriority(Item, character, requiredSuccessFactor) / 100;
|
||||
bool isSelected = IsRepairing();
|
||||
float selectedBonus = isSelected ? 100 - MaxDevotion : 0;
|
||||
float devotion = (CumulatedDevotion + selectedBonus) / 100;
|
||||
float reduction = isPriority ? 1 : isSelected ? 2 : 3;
|
||||
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - reduction, 90);
|
||||
float max = AIObjectiveManager.LowestOrderPriority - reduction;
|
||||
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
|
||||
}
|
||||
return Priority;
|
||||
@@ -74,7 +74,7 @@ namespace Barotrauma
|
||||
protected override bool Check()
|
||||
{
|
||||
IsCompleted = Item.IsFullCondition;
|
||||
if (IsCompleted && IsRepairing())
|
||||
if (character.IsOnPlayerTeam && IsCompleted && IsRepairing())
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("DialogItemRepaired", "[itemname]", Item.Name, true), null, 0.0f, "itemrepaired", 10.0f);
|
||||
}
|
||||
@@ -97,7 +97,10 @@ namespace Barotrauma
|
||||
var getItemObjective = new AIObjectiveGetItem(character, requiredItem.Identifiers, objectiveManager, true);
|
||||
if (objectiveManager.IsCurrentOrder<AIObjectiveRepairItems>())
|
||||
{
|
||||
getItemObjective.Abandoned += () => character.Speak(TextManager.Get("dialogcannotfindrequireditemtorepair"), null, 0.0f, "dialogcannotfindrequireditemtorepair", 10.0f);
|
||||
if (character.IsOnPlayerTeam)
|
||||
{
|
||||
getItemObjective.Abandoned += () => character.Speak(TextManager.Get("dialogcannotfindrequireditemtorepair"), null, 0.0f, "dialogcannotfindrequireditemtorepair", 10.0f);
|
||||
}
|
||||
}
|
||||
subObjectives.Add(getItemObjective);
|
||||
}
|
||||
@@ -119,27 +122,8 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
// Eject empty tanks
|
||||
if (repairTool.Item.OwnInventory.AllItems.Any(it => it.Condition <= 0.0f))
|
||||
{
|
||||
foreach (Item containedItem in repairTool.Item.OwnInventory.AllItemsMod)
|
||||
{
|
||||
if (containedItem == null) { continue; }
|
||||
if (containedItem.Condition <= 0.0f)
|
||||
{
|
||||
if (character.Submarine == null)
|
||||
{
|
||||
// If we are outside of main sub, try to put the tank in the inventory instead dropping it in the sea.
|
||||
if (character.Inventory.TryPutItem(containedItem, character, CharacterInventory.anySlot))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
containedItem.Drop(character);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HumanAIController.UnequipContainedItems(repairTool.Item, it => !it.HasTag("weldingfuel"));
|
||||
HumanAIController.UnequipEmptyItems(repairTool.Item);
|
||||
RelatedItem item = null;
|
||||
Item fuel = null;
|
||||
foreach (RelatedItem requiredItem in repairTool.requiredItems[RelatedItem.RelationType.Contained])
|
||||
@@ -193,7 +177,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (Abandon)
|
||||
{
|
||||
if (IsRepairing())
|
||||
if (character.IsOnPlayerTeam && IsRepairing())
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("DialogCannotRepair", "[itemname]", Item.Name, true), null, 0.0f, "cannotrepair", 10.0f);
|
||||
}
|
||||
@@ -228,7 +212,7 @@ namespace Barotrauma
|
||||
onAbandon: () =>
|
||||
{
|
||||
Abandon = true;
|
||||
if (IsRepairing())
|
||||
if (character.IsOnPlayerTeam && IsRepairing())
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("DialogCannotRepair", "[itemname]", Item.Name, true), null, 0.0f, "cannotrepair", 10.0f);
|
||||
}
|
||||
|
||||
+3
-1
@@ -104,7 +104,7 @@ namespace Barotrauma
|
||||
}
|
||||
bool anyFixers = otherFixers > 0;
|
||||
float ratio = anyFixers ? items / (float)otherFixers : 1;
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
if (objectiveManager.IsOrder(this))
|
||||
{
|
||||
return Targets.Sum(t => 100 - t.ConditionPercentage);
|
||||
}
|
||||
@@ -153,6 +153,8 @@ namespace Barotrauma
|
||||
if (item.IsFullCondition) { return false; }
|
||||
if (item.CurrentHull == null) { return false; }
|
||||
if (item.Submarine == null || character.Submarine == null) { return false; }
|
||||
//player crew ignores items in outposts
|
||||
if (character.IsOnPlayerTeam && item.Submarine.Info.IsOutpost) { return false; }
|
||||
if (!character.Submarine.IsEntityFoundOnThisSub(item, includingConnectedSubs: true)) { return false; }
|
||||
if (item.Repairables.None()) { return false; }
|
||||
return true;
|
||||
|
||||
+14
-8
@@ -78,14 +78,14 @@ namespace Barotrauma
|
||||
// Check if the character needs more oxygen
|
||||
if (!ignoreOxygen && character.SelectedCharacter == targetCharacter || character.CanInteractWith(targetCharacter))
|
||||
{
|
||||
// Replace empty oxygen tank
|
||||
// First remove empty tanks
|
||||
// Replace empty oxygen and welding fuel.
|
||||
if (HumanAIController.HasItem(targetCharacter, AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR, out IEnumerable<Item> suits, requireEquipped: true))
|
||||
{
|
||||
Item suit = suits.FirstOrDefault();
|
||||
if (suit != null)
|
||||
{
|
||||
AIObjectiveFindDivingGear.EjectEmptyTanks(character, suit, out _);
|
||||
AIController.UnequipEmptyItems(character, suit);
|
||||
AIController.UnequipContainedItems(character, suit, it => it.HasTag("weldingfuel"));
|
||||
}
|
||||
}
|
||||
else if (HumanAIController.HasItem(targetCharacter, AIObjectiveFindDivingGear.LIGHT_DIVING_GEAR, out IEnumerable<Item> masks, requireEquipped: true))
|
||||
@@ -93,7 +93,8 @@ namespace Barotrauma
|
||||
Item mask = masks.FirstOrDefault();
|
||||
if (mask != null)
|
||||
{
|
||||
AIObjectiveFindDivingGear.EjectEmptyTanks(character, mask, out _);
|
||||
AIController.UnequipEmptyItems(character, mask);
|
||||
AIController.UnequipContainedItems(character, mask, it => it.HasTag("weldingfuel"));
|
||||
}
|
||||
}
|
||||
bool ShouldRemoveDivingSuit() => targetCharacter.OxygenAvailable < CharacterHealth.InsufficientOxygenThreshold && targetCharacter.CurrentHull?.LethalPressure <= 0;
|
||||
@@ -322,7 +323,7 @@ namespace Barotrauma
|
||||
{
|
||||
itemListStr = string.Join(" or ", string.Join(", ", itemNameList.Take(itemNameList.Count - 1)), itemNameList.Last());
|
||||
}
|
||||
if (targetCharacter != character)
|
||||
if (targetCharacter != character && character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariables("DialogListRequiredTreatments", new string[2] { "[targetname]", "[treatmentlist]" },
|
||||
new string[2] { targetCharacter.Name, itemListStr }, new bool[2] { false, true }),
|
||||
@@ -336,7 +337,10 @@ namespace Barotrauma
|
||||
onAbandon: () =>
|
||||
{
|
||||
Abandon = true;
|
||||
character.Speak(TextManager.GetWithVariable("dialogcannottreatpatient", "[name]", targetCharacter.DisplayName, formatCapitals: false), identifier: "cannottreatpatient", minDurationBetweenSimilar: 20.0f);
|
||||
if (character != targetCharacter && character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("dialogcannottreatpatient", "[name]", targetCharacter.DisplayName, formatCapitals: false), identifier: "cannottreatpatient", minDurationBetweenSimilar: 20.0f);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -383,8 +387,10 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
return false;
|
||||
}
|
||||
bool isCompleted = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter);
|
||||
if (isCompleted && targetCharacter != character)
|
||||
bool isCompleted =
|
||||
AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter) ||
|
||||
targetCharacter.CharacterHealth.GetAllAfflictions().All(a => a.Strength < a.Prefab.TreatmentThreshold);
|
||||
if (isCompleted && targetCharacter != character && character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("DialogTargetHealed", "[targetname]", targetCharacter.Name),
|
||||
null, 1.0f, "targethealed" + targetCharacter.Name, 60.0f);
|
||||
|
||||
+9
-5
@@ -25,8 +25,8 @@ namespace Barotrauma
|
||||
{
|
||||
// When targeting player characters, always treat them when ordered, else use the threshold so that minor/non-severe damage is ignored.
|
||||
// If we ignore any damage when the player orders a bot to do healings, it's observed to cause confusion among the players.
|
||||
// On the other hand, if the bots too eagerly heal characters when it's not nevessary, it's inefficient and can feel frustrating, because it can't be controlled.
|
||||
return character == target || manager.CurrentOrder is AIObjectiveRescueAll ? (target.IsPlayer ? 100 : vitalityThresholdForOrders) : vitalityThreshold;
|
||||
// On the other hand, if the bots too eagerly heal characters when it's not necessary, it's inefficient and can feel frustrating, because it can't be controlled.
|
||||
return character == target || manager.HasOrder<AIObjectiveRescueAll>() ? (target.IsPlayer ? 100 : vitalityThresholdForOrders) : vitalityThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Barotrauma
|
||||
protected override float TargetEvaluation()
|
||||
{
|
||||
if (Targets.None()) { return 100; }
|
||||
if (objectiveManager.CurrentOrder != this)
|
||||
if (!objectiveManager.IsOrder(this))
|
||||
{
|
||||
if (!character.IsMedic && HumanAIController.IsTrueForAnyCrewMember(c => c != HumanAIController && c.Character.IsMedic && !c.Character.IsUnconscious))
|
||||
{
|
||||
@@ -82,8 +82,12 @@ namespace Barotrauma
|
||||
if (!HumanAIController.IsFriendly(character, target, onlySameTeam: true)) { return false; }
|
||||
if (character.AIController is HumanAIController humanAI)
|
||||
{
|
||||
if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target)) { return false; }
|
||||
if (!humanAI.ObjectiveManager.IsCurrentOrder<AIObjectiveRescueAll>())
|
||||
if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target) ||
|
||||
target.CharacterHealth.GetAllAfflictions().All(a => a.Strength < a.Prefab.TreatmentThreshold))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!humanAI.ObjectiveManager.HasOrder<AIObjectiveRescueAll>())
|
||||
{
|
||||
if (!character.IsMedic && target != character)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user