(7d7010579) Fix ai dialog about not being able to find targets. Use a higher thershold for rescue objectives when it's an order.

This commit is contained in:
Joonas Rikkonen
2019-05-16 06:14:25 +03:00
parent af07862995
commit 9c964b7843
51 changed files with 688 additions and 417 deletions
@@ -409,9 +409,40 @@ namespace Barotrauma
CurrentOrderOption = option;
CurrentOrder = order;
objectiveManager.SetOrder(order, option, orderGiver);
if (speak && Character.SpeechImpediment < 100.0f)
if (ObjectiveManager.CurrentOrder != null && speak && Character.SpeechImpediment < 100.0f)
{
Character.Speak(TextManager.Get("DialogAffirmative"), null, 1.0f);
if (ObjectiveManager.CurrentOrder is AIObjectiveRepairItems repairItems && repairItems.Targets.None())
{
Character.Speak(TextManager.Get("DialogNoRepairTargets"), null, 3.0f, "norepairtargets");
}
else if (ObjectiveManager.CurrentOrder is AIObjectiveChargeBatteries chargeBatteries && chargeBatteries.Targets.None())
{
Character.Speak(TextManager.Get("DialogNoBatteries"), null, 3.0f, "nobatteries");
}
else if (ObjectiveManager.CurrentOrder is AIObjectiveExtinguishFires extinguishFires && extinguishFires.Targets.None())
{
Character.Speak(TextManager.Get("DialogNoFire"), null, 3.0f, "nofire");
}
else if (ObjectiveManager.CurrentOrder is AIObjectiveFixLeaks fixLeaks && fixLeaks.Targets.None())
{
Character.Speak(TextManager.Get("DialogNoLeaks"), null, 3.0f, "noleaks");
}
else if (ObjectiveManager.CurrentOrder is AIObjectiveFightIntruders fightIntruders && fightIntruders.Targets.None())
{
Character.Speak(TextManager.Get("DialogNoEnemies"), null, 3.0f, "noenemies");
}
else if (ObjectiveManager.CurrentOrder is AIObjectiveRescueAll rescueAll && rescueAll.Targets.None())
{
Character.Speak(TextManager.Get("DialogNoRescueTargets"), null, 3.0f, "norescuetargets");
}
else if (ObjectiveManager.CurrentOrder is AIObjectivePumpWater pumpWater && pumpWater.Targets.None())
{
Character.Speak(TextManager.Get("DialogNoPumps"), null, 3.0f, "nopumps");
}
else
{
Character.Speak(TextManager.Get("DialogAffirmative"), null, 1.0f);
}
}
SetOrderProjSpecific(order);
}
@@ -106,6 +106,18 @@ namespace Barotrauma
subObjectives.Add(objective);
}
public void RemoveSubObjective<T>(ref T objective) where T : AIObjective
{
if (objective != null)
{
if (subObjectives.Contains(objective))
{
subObjectives.Remove(objective);
}
objective = null;
}
}
public void SortSubObjectives()
{
if (subObjectives.None()) { return; }
@@ -20,15 +20,6 @@ namespace Barotrauma
return otherObjective is AIObjectiveChargeBatteries other && other.Option == Option;
}
protected override void FindTargets()
{
base.FindTargets();
if (targets.None() && objectiveManager.CurrentOrder == this)
{
character.Speak(TextManager.Get("DialogNoBatteries"), null, 4.0f, "nobatteries", 10.0f);
}
}
protected override bool Filter(PowerContainer battery)
{
if (battery == null) { return false; }
@@ -54,12 +45,12 @@ namespace Barotrauma
{
if (Option == "charge")
{
return targets.Max(t => MathHelper.Lerp(100, 0, Math.Abs(PowerContainer.aiRechargeTargetRatio - t.RechargeRatio)));
return Targets.Max(t => MathHelper.Lerp(100, 0, Math.Abs(PowerContainer.aiRechargeTargetRatio - t.RechargeRatio)));
}
else
{
return targets.Max(t => MathHelper.Lerp(0, 100, t.RechargeRatio));
return Targets.Max(t => MathHelper.Lerp(0, 100, t.RechargeRatio));
}
}
@@ -11,9 +11,10 @@ namespace Barotrauma
class AIObjectiveCombat : AIObjective
{
public override string DebugTag => "combat";
public bool useCoolDown = true;
const float CoolDown = 10.0f;
private readonly CombatMode initialMode;
const float coolDown = 10.0f;
public Character Enemy { get; private set; }
@@ -25,14 +26,7 @@ namespace Barotrauma
{
_weapon = value;
_weaponComponent = null;
if (reloadWeaponObjective != null)
{
if (subObjectives.Contains(reloadWeaponObjective))
{
subObjectives.Remove(reloadWeaponObjective);
}
reloadWeaponObjective = null;
}
RemoveSubObjective(ref seekAmmunition);
}
}
private ItemComponent _weaponComponent;
@@ -40,6 +34,7 @@ namespace Barotrauma
{
get
{
if (Weapon == null) { return null; }
if (_weaponComponent == null)
{
_weaponComponent =
@@ -58,12 +53,14 @@ namespace Barotrauma
private readonly HashSet<MeleeWeapon> meleeWeapons = new HashSet<MeleeWeapon>();
private readonly HashSet<Item> adHocWeapons = new HashSet<Item>();
private AIObjectiveContainItem reloadWeaponObjective;
private AIObjectiveContainItem seekAmmunition;
private AIObjectiveGoTo retreatObjective;
private AIObjectiveGoTo followTargetObjective;
private Hull retreatTarget;
private float coolDownTimer;
private IEnumerable<FarseerPhysics.Dynamics.Body> myBodies;
private float aimTimer;
public enum CombatMode
{
@@ -78,7 +75,7 @@ namespace Barotrauma
: base(character, objectiveManager, priorityModifier)
{
Enemy = enemy;
coolDownTimer = CoolDown;
coolDownTimer = coolDown;
findSafety = objectiveManager.GetObjective<AIObjectiveFindSafety>();
if (findSafety != null)
{
@@ -86,6 +83,7 @@ namespace Barotrauma
findSafety.unreachable.Clear();
}
Mode = mode;
initialMode = Mode;
if (Enemy == null)
{
Mode = CombatMode.Retreat;
@@ -104,7 +102,7 @@ namespace Barotrauma
public override bool IsCompleted()
{
bool completed = (Enemy != null && (Enemy.Removed || Enemy.IsDead)) || (useCoolDown && coolDownTimer <= 0);
bool completed = (Enemy != null && (Enemy.Removed || Enemy.IsDead)) || (initialMode != CombatMode.Offensive && coolDownTimer <= 0);
if (completed)
{
if (objectiveManager.CurrentOrder == this && Enemy != null && Enemy.IsDead)
@@ -121,65 +119,93 @@ namespace Barotrauma
protected override void Act(float deltaTime)
{
if (useCoolDown)
if (initialMode != CombatMode.Offensive)
{
coolDownTimer -= deltaTime;
}
if (abandon) { return; }
Arm(deltaTime);
Move(deltaTime);
TryArm();
if (seekAmmunition == null || !subObjectives.Contains(seekAmmunition))
{
Move();
if (Weapon != null)
{
OperateWeapon(deltaTime);
}
}
}
private void Arm(float deltaTime)
private void Move()
{
switch (Mode)
{
case CombatMode.Offensive:
Engage();
break;
case CombatMode.Defensive:
if (Weapon != null && !character.Inventory.Items.Contains(_weapon) || _weaponComponent != null && !_weaponComponent.HasRequiredContainedItems(false))
case CombatMode.Retreat:
Retreat();
break;
default:
throw new NotImplementedException();
}
}
private bool TryArm()
{
if (Weapon != null)
{
if (!character.Inventory.Items.Contains(Weapon) || WeaponComponent == null)
{
Weapon = null;
}
else if (!WeaponComponent.HasRequiredContainedItems(false))
{
// Seek ammunition only if cannot find a new weapon
if (!Reload(true, () => GetWeapon() == null))
{
Weapon = null;
}
if (Weapon == null)
{
Weapon = GetWeapon();
}
if (Weapon == null)
{
Mode = CombatMode.Retreat;
}
if (Equip())
{
if (Reload(deltaTime))
if (seekAmmunition != null && subObjectives.Contains(seekAmmunition))
{
Attack(deltaTime);
return false;
}
else
{
Weapon = null;
}
}
break;
case CombatMode.Retreat:
break;
default:
throw new NotImplementedException();
}
}
if (Weapon == null)
{
Weapon = GetWeapon();
}
if (Weapon == null)
{
Weapon = GetWeapon(ignoreRequiredItems: true);
}
Mode = Weapon == null ? CombatMode.Retreat : initialMode;
return Weapon != null;
}
private void Move(float deltaTime)
private void OperateWeapon(float deltaTime)
{
switch (Mode)
{
case CombatMode.Offensive:
Engage(deltaTime);
break;
case CombatMode.Defensive:
if (Equip())
{
Attack(deltaTime);
}
break;
case CombatMode.Retreat:
Retreat(deltaTime);
break;
default:
throw new NotImplementedException();
}
}
private Item GetWeapon()
private Item GetWeapon(bool ignoreRequiredItems = false)
{
rangedWeapons.Clear();
meleeWeapons.Clear();
@@ -193,14 +219,14 @@ namespace Barotrauma
{
if (component is RangedWeapon rw)
{
if (rw.HasRequiredContainedItems(false))
if (ignoreRequiredItems || rw.HasRequiredContainedItems(false))
{
rangedWeapons.Add(rw);
}
}
else if (component is MeleeWeapon mw)
{
if (mw.HasRequiredContainedItems(false))
if (ignoreRequiredItems || mw.HasRequiredContainedItems(false))
{
meleeWeapons.Add(mw);
}
@@ -216,7 +242,7 @@ namespace Barotrauma
{
if (statusEffect.Afflictions.Any())
{
if (component.HasRequiredContainedItems(false))
if (ignoreRequiredItems || component.HasRequiredContainedItems(false))
{
adHocWeapons.Add(item);
}
@@ -253,19 +279,29 @@ namespace Barotrauma
Weapon.Drop(character);
}
}
return true;
}
private bool Equip()
{
if (!character.SelectedItems.Contains(Weapon))
if (!WeaponComponent.HasRequiredContainedItems(false))
{
Mode = CombatMode.Retreat;
return false;
}
//if (!character.SelectedItems.Contains(Weapon))
if (!character.HasEquippedItem(Weapon))
{
Weapon.TryInteract(character, forceSelectKey: true);
var slots = Weapon.AllowedSlots.FindAll(s => s == InvSlotType.LeftHand || s == InvSlotType.RightHand || s == (InvSlotType.LeftHand | InvSlotType.RightHand));
if (character.Inventory.TryPutItem(Weapon, character, slots))
{
Weapon.Equip(character);
aimTimer = Rand.Range(0.5f, 1f);
}
else
{
Weapon = null;
Mode = CombatMode.Retreat;
return false;
}
@@ -273,16 +309,10 @@ namespace Barotrauma
return true;
}
private void Retreat(float deltaTime)
private void Retreat()
{
if (followTargetObjective != null)
{
if (subObjectives.Contains(followTargetObjective))
{
subObjectives.Remove(followTargetObjective);
}
followTargetObjective = null;
}
RemoveSubObjective(ref followTargetObjective);
RemoveSubObjective(ref seekAmmunition);
if (retreatObjective != null && retreatObjective.Target != retreatTarget)
{
retreatObjective = null;
@@ -294,59 +324,115 @@ namespace Barotrauma
TryAddSubObjective(ref retreatObjective, () => new AIObjectiveGoTo(retreatTarget, character, objectiveManager, false, true));
}
private void Engage(float deltaTime)
private void Engage()
{
retreatTarget = null;
if (retreatObjective != null)
RemoveSubObjective(ref retreatObjective);
RemoveSubObjective(ref seekAmmunition);
if (followTargetObjective != null && followTargetObjective.Target != Enemy)
{
if (subObjectives.Contains(retreatObjective))
{
subObjectives.Remove(retreatObjective);
}
retreatObjective = null;
followTargetObjective = null;
}
TryAddSubObjective(ref followTargetObjective,
constructor: () => new AIObjectiveGoTo(Enemy, character, objectiveManager, repeat: true, getDivingGearIfNeeded: true)
{
AllowGoingOutside = true,
IgnoreIfTargetDead = true,
CheckVisibility = true,
CloseEnough =
WeaponComponent is RangedWeapon ? 3 :
WeaponComponent is MeleeWeapon mw ? ConvertUnits.ToSimUnits(mw.Range) :
WeaponComponent is RepairTool rt ? ConvertUnits.ToSimUnits(rt.Range) : 0.5f
CheckVisibility = true
},
onAbandon: () =>
{
SteeringManager.Reset();
Mode = CombatMode.Retreat;
SteeringManager.Reset();
});
if (followTargetObjective != null && subObjectives.Contains(followTargetObjective))
{
followTargetObjective.CloseEnough =
WeaponComponent is RangedWeapon ? 3 :
WeaponComponent is MeleeWeapon mw ? ConvertUnits.ToSimUnits(mw.Range) :
WeaponComponent is RepairTool rt ? ConvertUnits.ToSimUnits(rt.Range) : 0.5f;
}
}
private bool Reload(float deltaTime)
/// <summary>
/// Seeks for more ammunition. Creates a new subobjective.
/// </summary>
private void SeekAmmunition(string[] ammunitionIdentifiers)
{
if (WeaponComponent != null && WeaponComponent.requiredItems.ContainsKey(RelatedItem.RelationType.Contained))
{
var containedItems = Weapon.ContainedItems;
foreach (RelatedItem requiredItem in WeaponComponent.requiredItems[RelatedItem.RelationType.Contained])
retreatTarget = null;
RemoveSubObjective(ref retreatObjective);
RemoveSubObjective(ref followTargetObjective);
TryAddSubObjective(ref seekAmmunition,
constructor: () => new AIObjectiveContainItem(character, ammunitionIdentifiers, Weapon.GetComponent<ItemContainer>(), objectiveManager)
{
Item containedItem = containedItems.FirstOrDefault(it => it.Condition > 0.0f && requiredItem.MatchesItem(it));
if (containedItem == null)
targetItemCount = Weapon.GetComponent<ItemContainer>().Capacity,
checkInventory = false
},
onAbandon: () =>
{
Weapon = null;
Mode = CombatMode.Retreat;
SteeringManager.Reset();
});
}
/// <summary>
/// Reloads the ammunition found in the inventory.
/// If seekAmmo is true and the condition is met or not provided, tries to get find the ammo elsewhere.
/// </summary>
private bool Reload(bool seekAmmo, Func<bool> condition = null)
{
if (WeaponComponent == null) { return false; }
if (!WeaponComponent.requiredItems.ContainsKey(RelatedItem.RelationType.Contained)) { return false; }
var containedItems = Weapon.ContainedItems;
RelatedItem item = null;
Item ammunition = null;
string[] ammunitionIdentifiers = null;
foreach (RelatedItem requiredItem in WeaponComponent.requiredItems[RelatedItem.RelationType.Contained])
{
ammunition = containedItems.FirstOrDefault(it => it.Condition > 0.0f && requiredItem.MatchesItem(it));
if (ammunition != null)
{
// Ammunition still remaining
return true;
}
item = requiredItem;
ammunitionIdentifiers = requiredItem.Identifiers;
}
// No ammo
if (ammunition == null)
{
var container = Weapon.GetComponent<ItemContainer>();
// Try reload ammunition in inventory
foreach (string identifier in ammunitionIdentifiers)
{
foreach (var i in character.Inventory.Items)
{
TryAddSubObjective(ref reloadWeaponObjective,
constructor: () => new AIObjectiveContainItem(character, requiredItem.Identifiers, Weapon.GetComponent<ItemContainer>(), objectiveManager),
onAbandon: () =>
if (i == null) { continue; }
if (i.Prefab.Identifier == identifier || i.HasTag(identifier))
{
if (i.Condition > 0)
{
SteeringManager.Reset();
Mode = CombatMode.Retreat;
});
container.Inventory.TryPutItem(ammunition, null);
}
}
}
}
}
return reloadWeaponObjective == null || reloadWeaponObjective.IsCompleted();
if (WeaponComponent.HasRequiredContainedItems(false))
{
return true;
}
else if (ammunition == null)
{
if (seekAmmo && ammunitionIdentifiers != null && (condition == null || condition()))
{
SeekAmmunition(ammunitionIdentifiers);
}
}
return false;
}
private IEnumerable<FarseerPhysics.Dynamics.Body> myBodies;
private void Attack(float deltaTime)
{
float squaredDistance = Vector2.DistanceSquared(character.Position, Enemy.Position);
@@ -371,6 +457,16 @@ namespace Barotrauma
character.SetInput(InputType.Aim, false, true);
}
}
bool isFacing = character.AnimController.Dir > 0 && Enemy.WorldPosition.X > character.WorldPosition.X || character.AnimController.Dir < 0 && Enemy.WorldPosition.X < character.WorldPosition.X;
if (!isFacing)
{
aimTimer = Rand.Range(1f, 1.5f);
}
if (aimTimer > 0)
{
aimTimer -= deltaTime;
return;
}
if (WeaponComponent is MeleeWeapon meleeWeapon)
{
if (squaredDistance <= meleeWeapon.Range * meleeWeapon.Range)
@@ -385,14 +481,14 @@ namespace Barotrauma
{
if (squaredDistance > repairTool.Range * repairTool.Range) { return; }
}
if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - character.Position) < MathHelper.PiOver4)
if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - Weapon.Position) < MathHelper.PiOver4)
{
if (myBodies == null)
{
myBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody);
}
var collisionCategories = Physics.CollisionCharacter | Physics.CollisionWall;
var pickedBody = Submarine.PickBody(character.SimPosition, Enemy.SimPosition, myBodies, collisionCategories);
var pickedBody = Submarine.PickBody(Weapon.SimPosition, Enemy.SimPosition, myBodies, collisionCategories);
if (pickedBody != null)
{
Character target = null;
@@ -404,10 +500,11 @@ namespace Barotrauma
{
target = limb.character;
}
if (target != null && target == Enemy)
if (target != null && (target == Enemy || !HumanAIController.IsFriendly(target)))
{
character.SetInput(InputType.Shoot, false, true);
Weapon.Use(deltaTime, character);
aimTimer = Rand.Range(0.25f, 0.5f);
}
}
}
@@ -11,12 +11,13 @@ namespace Barotrauma
public Func<Item, float> GetItemPriority;
public int MinContainedAmount = 1;
public int targetItemCount = 1;
public string[] ignoredContainerIdentifiers;
public bool checkInventory = true;
//can either be a tag or an identifier
private readonly string[] itemIdentifiers;
private readonly ItemContainer container;
public readonly string[] itemIdentifiers;
public readonly ItemContainer container;
private bool isCompleted;
private AIObjectiveGetItem getItemObjective;
@@ -39,7 +40,6 @@ namespace Barotrauma
public override bool IsCompleted()
{
if (isCompleted) { return true; }
int containedItemCount = 0;
foreach (Item item in container.Inventory.Items)
{
@@ -48,7 +48,7 @@ namespace Barotrauma
containedItemCount++;
}
}
return containedItemCount >= MinContainedAmount;
return containedItemCount >= targetItemCount;
}
public override float GetPriority()
@@ -62,7 +62,6 @@ namespace Barotrauma
protected override void Act(float deltaTime)
{
if (isCompleted) { return; }
//get the item that should be contained
Item itemToContain = null;
foreach (string identifier in itemIdentifiers)
@@ -72,8 +71,13 @@ namespace Barotrauma
}
if (itemToContain == null)
{
if (getItemObjective != null && getItemObjective.IsCompleted())
{
getItemObjective = null;
targetItemCount--;
}
TryAddSubObjective(ref getItemObjective, () =>
new AIObjectiveGetItem(character, itemIdentifiers, objectiveManager)
new AIObjectiveGetItem(character, itemIdentifiers, objectiveManager, checkInventory: checkInventory)
{
GetItemPriority = GetItemPriority,
ignoredContainerIdentifiers = ignoredContainerIdentifiers
@@ -81,14 +85,7 @@ namespace Barotrauma
return;
}
if (container.Item.ParentInventory == character.Inventory)
{
var containedItems = container.Inventory.Items;
//if there's already something in the mask (empty oxygen tank?), drop it
var existingItem = containedItems.FirstOrDefault(i => i != null);
if (existingItem != null)
{
existingItem.Drop(character);
}
{
character.Inventory.RemoveItem(itemToContain);
container.Inventory.TryPutItem(itemToContain, null);
}
@@ -102,7 +99,6 @@ namespace Barotrauma
}
container.Combine(itemToContain);
}
isCompleted = true;
}
public override bool IsDuplicate(AIObjective otherObjective)
@@ -11,18 +11,9 @@ namespace Barotrauma
public AIObjectiveExtinguishFires(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
protected override void FindTargets()
{
base.FindTargets();
if (targets.None() && objectiveManager.CurrentOrder == this)
{
character.Speak(TextManager.Get("DialogNoFire"), null, 3.0f, "nofire", 30.0f);
}
}
protected override bool Filter(Hull hull) => IsValidTarget(hull, character);
protected override float TargetEvaluation() => objectiveManager.CurrentObjective == this ? 100 : targets.Sum(t => GetFireSeverity(t));
protected override float TargetEvaluation() => objectiveManager.CurrentObjective == this ? 100 : Targets.Sum(t => GetFireSeverity(t));
public static float GetFireSeverity(Hull hull) => hull.FireSources.Sum(fs => fs.Size.X);
@@ -10,26 +10,18 @@ namespace Barotrauma
class AIObjectiveFightIntruders : AIObjectiveLoop<Character>
{
public override string DebugTag => "fight intruders";
protected override float IgnoreListClearInterval => 30;
public AIObjectiveFightIntruders(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier) { }
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFightIntruders;
protected override void FindTargets()
{
base.FindTargets();
if (targets.None() && objectiveManager.CurrentOrder == this)
{
character.Speak(TextManager.Get("DialogNoEnemies"), null, 3.0f, "noenemies", 30.0f);
}
}
protected override bool Filter(Character target) => IsValidTarget(target, character);
protected override IEnumerable<Character> GetList() => Character.CharacterList;
protected override AIObjective ObjectiveConstructor(Character target) => new AIObjectiveCombat(character, target, AIObjectiveCombat.CombatMode.Offensive, objectiveManager, PriorityModifier) { useCoolDown = false };
protected override AIObjective ObjectiveConstructor(Character target) => new AIObjectiveCombat(character, target, AIObjectiveCombat.CombatMode.Offensive, objectiveManager, PriorityModifier);
protected override float TargetEvaluation()
{
@@ -325,6 +325,31 @@ namespace Barotrauma
{
#if DEBUG
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
#endif
abandon = true;
return;
}
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
// TODO: use the collider size/reach?
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
{
HumanAIController.AnimController.Crouching = true;
}
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
if (canOperate)
{
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
}
else
{
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
}
var repairTool = weldingTool.GetComponent<RepairTool>();
if (repairTool == null)
{
#if DEBUG
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
#endif
abandon = true;
return;
@@ -13,15 +13,6 @@ namespace Barotrauma
public AIObjectiveFixLeaks(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
protected override void FindTargets()
{
base.FindTargets();
if (targets.None() && objectiveManager.CurrentOrder == this)
{
character.Speak(TextManager.Get("DialogNoLeaks"), null, 3.0f, "noleaks", 30.0f);
}
}
protected override bool Filter(Gap gap) => IsValidTarget(gap, character);
public static float GetLeakSeverity(Gap leak)
@@ -34,7 +25,7 @@ namespace Barotrauma
}
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFixLeaks;
protected override float TargetEvaluation() => targets.Max(t => GetLeakSeverity(t));
protected override float TargetEvaluation() => Targets.Max(t => GetLeakSeverity(t));
protected override IEnumerable<Gap> GetList() => Gap.GapList;
protected override AIObjective ObjectiveConstructor(Gap gap) => new AIObjectiveFixLeak(gap, character, objectiveManager, PriorityModifier);
@@ -41,10 +41,10 @@ namespace Barotrauma
this.targetItem = targetItem;
}
public AIObjectiveGetItem(Character character, string itemIdentifier, AIObjectiveManager objectiveManager, bool equip = false, float priorityModifier = 1)
: this(character, new string[] { itemIdentifier }, objectiveManager, equip, priorityModifier) { }
public AIObjectiveGetItem(Character character, string itemIdentifier, AIObjectiveManager objectiveManager, bool equip = false, bool checkInventory = true, float priorityModifier = 1)
: this(character, new string[] { itemIdentifier }, objectiveManager, equip, checkInventory, priorityModifier) { }
public AIObjectiveGetItem(Character character, string[] itemIdentifiers, AIObjectiveManager objectiveManager, bool equip = false, float priorityModifier = 1)
public AIObjectiveGetItem(Character character, string[] itemIdentifiers, AIObjectiveManager objectiveManager, bool equip = false, bool checkInventory = true, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier)
{
currSearchIndex = -1;
@@ -54,7 +54,10 @@ namespace Barotrauma
{
itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant();
}
CheckInventory();
if (checkInventory)
{
CheckInventory();
}
}
private void CheckInventory()
@@ -225,8 +228,7 @@ namespace Barotrauma
public override bool IsDuplicate(AIObjective otherObjective)
{
AIObjectiveGetItem getItem = otherObjective as AIObjectiveGetItem;
if (getItem == null) { return false; }
if (!(otherObjective is AIObjectiveGetItem getItem)) { return false; }
if (getItem.equip != equip) { return false; }
if (getItem.itemIdentifiers != null && itemIdentifiers != null)
{
@@ -246,7 +248,11 @@ namespace Barotrauma
public override bool IsCompleted()
{
if (itemIdentifiers != null)
if (targetItem != null)
{
return HasItem(targetItem);
}
else if (itemIdentifiers != null)
{
foreach (string itemName in itemIdentifiers)
{
@@ -258,10 +264,6 @@ namespace Barotrauma
}
return false;
}
else if (targetItem != null)
{
return character.Inventory.Items.Contains(targetItem) && (!equip || character.HasEquippedItem(targetItem));
}
return false;
}
@@ -73,6 +73,21 @@ namespace Barotrauma
}
}
public override void Update(float deltaTime)
{
if (objectiveManager.CurrentObjective == this)
{
if (randomTimer > 0)
{
randomTimer -= deltaTime;
}
else
{
SetRandom();
}
}
}
public override bool IsCompleted() => false;
public override bool CanBeCompleted => true;
@@ -7,8 +7,8 @@ namespace Barotrauma
{
abstract class AIObjectiveLoop<T> : AIObjective
{
protected HashSet<T> targets = new HashSet<T>();
protected Dictionary<T, AIObjective> objectives = new Dictionary<T, AIObjective>();
public HashSet<T> Targets { get; private set; } = new HashSet<T>();
public Dictionary<T, AIObjective> Objectives { get; private set; } = new Dictionary<T, AIObjective>();
protected HashSet<T> ignoreList = new HashSet<T>();
private float ignoreListTimer;
private float targetUpdateTimer;
@@ -67,7 +67,7 @@ namespace Barotrauma
targetUpdateTimer -= deltaTime;
}
// Sync objectives, subobjectives and targets
foreach (var objective in objectives)
foreach (var objective in Objectives)
{
var target = objective.Key;
if (!objective.Value.CanBeCompleted)
@@ -75,13 +75,13 @@ namespace Barotrauma
ignoreList.Add(target);
targetUpdateTimer = 0;
}
if (!targets.Contains(target))
if (!Targets.Contains(target))
{
subObjectives.Remove(objective.Value);
}
}
SyncRemovedObjectives(objectives, GetList());
if (objectives.None() && targets.Any())
SyncRemovedObjectives(Objectives, GetList());
if (Objectives.None() && Targets.Any())
{
CreateObjectives();
}
@@ -109,7 +109,7 @@ namespace Barotrauma
public override float GetPriority()
{
if (character.Submarine == null) { return 0; }
if (targets.None()) { return 0; }
if (Targets.None()) { return 0; }
// Allow the target value to be more than 100.
float targetValue = TargetEvaluation();
// If the target value is less than 1% of the max value, let's just treat it as zero.
@@ -127,7 +127,7 @@ namespace Barotrauma
protected void UpdateTargets()
{
SetTargetUpdateTimer();
targets.Clear();
Targets.Clear();
FindTargets();
CreateObjectives();
}
@@ -146,20 +146,20 @@ namespace Barotrauma
if (!Filter(target)) { continue; }
if (!ignoreList.Contains(target))
{
targets.Add(target);
Targets.Add(target);
}
}
}
protected virtual void CreateObjectives()
{
foreach (T target in targets)
foreach (T target in Targets)
{
if (!objectives.TryGetValue(target, out AIObjective objective))
if (!Objectives.TryGetValue(target, out AIObjective objective))
{
objective = ObjectiveConstructor(target);
objective.Completed += () => ReportedTargets.Remove(target);
objectives.Add(target, objective);
Objectives.Add(target, objective);
AddSubObjective(objective);
}
}
@@ -290,6 +290,10 @@ namespace Barotrauma
{
isCompleted = true;
}
if (component.AIOperate(deltaTime, character, this))
{
isCompleted = true;
}
}
else
{
@@ -12,7 +12,7 @@ namespace Barotrauma
public override string DebugTag => "pump water";
private IEnumerable<Pump> pumpList;
public AIObjectivePumpWater(Character character, AIObjectiveManager objectiveManager, string option, float priorityModifier = 1)
public AIObjectivePumpWater(Character character, AIObjectiveManager objectiveManager, string option, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier, option) { }
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectivePumpWater && otherObjective.Option == Option;
@@ -21,10 +21,6 @@ namespace Barotrauma
{
if (Option == null) { return; }
base.FindTargets();
if (targets.None() && objectiveManager.CurrentOrder == this)
{
character.Speak(TextManager.Get("DialogNoPumps"), null, 3.0f, "nopumps", 30.0f);
}
}
protected override bool Filter(Pump pump)
@@ -63,11 +59,11 @@ namespace Barotrauma
{
if (Option == "stoppumping")
{
return targets.Max(t => MathHelper.Lerp(0, 100, Math.Abs(t.FlowPercentage / 100)));
return Targets.Max(t => MathHelper.Lerp(0, 100, Math.Abs(t.FlowPercentage / 100)));
}
else
{
return targets.Max(t => MathHelper.Lerp(100, 0, Math.Abs(-t.FlowPercentage / 100)));
return Targets.Max(t => MathHelper.Lerp(100, 0, Math.Abs(-t.FlowPercentage / 100)));
}
}
}
@@ -4,6 +4,7 @@ using System;
using System.Linq;
using Barotrauma.Extensions;
using FarseerPhysics;
using Barotrauma.Items.Components;
namespace Barotrauma
{
@@ -14,6 +15,7 @@ namespace Barotrauma
public Item Item { get; private set; }
private AIObjectiveGoTo goToObjective;
private AIObjectiveContainItem refuelObjective;
private float previousCondition = -1;
private RepairTool repairTool;
@@ -71,11 +73,46 @@ namespace Barotrauma
}
}
// Only continue when the get item sub objectives have been completed.
if (subObjectives.Any(so => so is AIObjectiveGetItem)) { return; }
if (subObjectives.Any()) { return; }
if (repairTool == null)
{
FindRepairTool();
}
if (repairTool != null)
{
var containedItems = repairTool.Item.ContainedItems;
if (containedItems == null)
{
#if DEBUG
DebugConsole.ThrowError("AIObjectiveRepairItem failed - the item \"" + repairTool + "\" has no proper inventory");
#endif
abandon = true;
return;
}
// Drop empty tanks
foreach (Item containedItem in containedItems)
{
if (containedItem == null) { continue; }
if (containedItem.Condition <= 0.0f)
{
containedItem.Drop(character);
}
}
RelatedItem item = null;
Item fuel = null;
foreach (RelatedItem requiredItem in repairTool.requiredItems[RelatedItem.RelationType.Contained])
{
item = requiredItem;
fuel = containedItems.FirstOrDefault(it => it.Condition > 0.0f && requiredItem.MatchesItem(it));
if (fuel != null) { break; }
}
if (fuel == null)
{
RemoveSubObjective(ref goToObjective);
TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, item.Identifiers, repairTool.Item.GetComponent<ItemContainer>(), objectiveManager));
return;
}
}
if (character.CurrentHull == Item.CurrentHull && character.CanInteractWith(Item))
{
if (repairTool != null)
@@ -112,6 +149,7 @@ namespace Barotrauma
}
else
{
RemoveSubObjective(ref refuelObjective);
// If cannot reach the item, approach it.
TryAddSubObjective(ref goToObjective,
constructor: () =>
@@ -18,25 +18,16 @@ namespace Barotrauma
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveRepairItems repairItems && repairItems.RequireAdequateSkills == RequireAdequateSkills;
protected override void FindTargets()
{
base.FindTargets();
if (targets.None() && objectiveManager.CurrentOrder == this)
{
character.Speak(TextManager.Get("DialogNoRepairTargets"), null, 3.0f, "norepairtargets", 30.0f);
}
}
protected override void CreateObjectives()
{
foreach (var item in targets)
foreach (var item in Targets)
{
foreach (Repairable repairable in item.Repairables)
{
if (!objectives.TryGetValue(item, out AIObjective objective))
if (!Objectives.TryGetValue(item, out AIObjective objective))
{
objective = ObjectiveConstructor(item);
objectives.Add(item, objective);
Objectives.Add(item, objective);
AddSubObjective(objective);
}
break;
@@ -50,7 +41,7 @@ namespace Barotrauma
if (item.CurrentHull.FireSources.Count > 0) { return false; }
// Don't repair items in rooms that have enemies inside.
if (Character.CharacterList.Any(c => c.CurrentHull == item.CurrentHull && !HumanAIController.IsFriendly(c))) { return false; }
if (!objectives.ContainsKey(item))
if (!Objectives.ContainsKey(item))
{
if (item.Repairables.All(r => item.Condition > r.ShowRepairUIThreshold)) { return false; }
}
@@ -61,7 +52,7 @@ namespace Barotrauma
return true;
}
protected override float TargetEvaluation() => targets.Max(t => 100 - t.ConditionPercentage);
protected override float TargetEvaluation() => Targets.Max(t => 100 - t.ConditionPercentage);
protected override IEnumerable<Item> GetList() => Item.ItemList;
protected override AIObjective ObjectiveConstructor(Item item) => new AIObjectiveRepairItem(character, item, objectiveManager, PriorityModifier);
@@ -201,7 +201,7 @@ namespace Barotrauma
public override bool IsCompleted()
{
bool isCompleted = targetCharacter.Vitality / targetCharacter.MaxVitality > AIObjectiveRescueAll.VitalityThreshold;
bool isCompleted = targetCharacter.Vitality / targetCharacter.MaxVitality > AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager);
if (isCompleted)
{
character.Speak(TextManager.Get("DialogTargetHealed").Replace("[targetname]", targetCharacter.Name),
@@ -9,30 +9,22 @@ namespace Barotrauma
public override string DebugTag => "rescue all";
public override bool ForceRun => true;
//only treat characters whose vitality is below this (0.9 = 90% of max vitality)
public const float VitalityThreshold = 0.9f;
private const float vitalityThreshold = 0.85f;
private const float vitalityThresholdForOrders = 0.95f;
public static float GetVitalityThreshold(AIObjectiveManager manager) => manager.CurrentOrder is AIObjectiveRescueAll ? vitalityThresholdForOrders : vitalityThreshold;
public AIObjectiveRescueAll(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier) { }
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveRescueAll;
protected override void FindTargets()
{
base.FindTargets();
if (targets.None() && objectiveManager.CurrentOrder == this)
{
character.Speak(TextManager.Get("DialogNoRescueTargets"), null, 3.0f, "norescuetargets", 30.0f);
}
}
protected override bool Filter(Character target) => IsValidTarget(target, character);
protected override IEnumerable<Character> GetList() => Character.CharacterList;
protected override AIObjective ObjectiveConstructor(Character target) => new AIObjectiveRescue(character, target, objectiveManager, PriorityModifier);
protected override float TargetEvaluation() => targets.Max(t => GetVitalityFactor(t)) * 100;
protected override float TargetEvaluation() => Targets.Max(t => GetVitalityFactor(t)) * 100;
public static float GetVitalityFactor(Character character) => (character.MaxVitality - character.Vitality) / character.MaxVitality;
@@ -41,7 +33,8 @@ namespace Barotrauma
if (target == null || target.IsDead || target.Removed) { return false; }
if (target == character) { return false; } // TODO: enable healing self
if (!HumanAIController.IsFriendly(character, target)) { return false; }
if (target.Vitality / target.MaxVitality > VitalityThreshold) { return false; }
if (!(character.AIController is HumanAIController humanAI)) { return false; }
if (target.Vitality / target.MaxVitality > GetVitalityThreshold(humanAI.ObjectiveManager)) { return false; }
if (target.Submarine == null) { return false; }
if (target.Submarine.TeamID != character.Submarine.TeamID) { return false; }
if (target.CurrentHull == null) { return false; }
@@ -203,7 +203,9 @@ namespace Barotrauma
if (startNode == null)
{
#if DEBUG
DebugConsole.NewMessage("Pathfinding error, couldn't find a start node. "+ errorMsgStr, Color.DarkRed);
#endif
return new SteeringPath(true);
}
@@ -253,7 +255,9 @@ namespace Barotrauma
if (endNode == null)
{
#if DEBUG
DebugConsole.NewMessage("Pathfinding error, couldn't find an end node. " + errorMsgStr, Color.DarkRed);
#endif
return new SteeringPath(true);
}
@@ -281,7 +285,9 @@ namespace Barotrauma
if (startNode == null || endNode == null)
{
#if DEBUG
DebugConsole.NewMessage("Pathfinding error, couldn't find matching pathnodes to waypoints.", Color.DarkRed);
#endif
return new SteeringPath(true);
}