Unstable 1.1.14.0
This commit is contained in:
@@ -40,6 +40,7 @@ namespace Barotrauma
|
||||
public virtual bool AllowOutsideSubmarine => false;
|
||||
public virtual bool AllowInFriendlySubs => false;
|
||||
public virtual bool AllowInAnySub => false;
|
||||
public virtual bool AllowWhileHandcuffed => true;
|
||||
|
||||
protected readonly List<AIObjective> subObjectives = new List<AIObjective>();
|
||||
private float _cumulatedDevotion;
|
||||
@@ -246,32 +247,43 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IgnoreAtOutpost && Level.IsLoadedFriendlyOutpost && character.TeamID != CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
if (Submarine.MainSub != null && Submarine.MainSub.DockedTo.None(s => s.TeamID != CharacterTeamType.FriendlyNPC && s.TeamID != character.TeamID))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!AllowWhileHandcuffed && character.LockHands) { return false; }
|
||||
if (!AllowOutsideSubmarine && character.Submarine == null) { return false; }
|
||||
// Evaluate ignored at outpost first, because it has higher priority than AllowInAnySub or AllowInFriendlySubs.
|
||||
if (IsIgnoredAtOutpost()) { return false; }
|
||||
if (AllowInAnySub) { return true; }
|
||||
if ((AllowInFriendlySubs && character.Submarine.TeamID == CharacterTeamType.FriendlyNPC) || character.IsEscorted) { return true; }
|
||||
return character.Submarine.TeamID == character.TeamID ||
|
||||
character.Submarine.TeamID == character.OriginalTeamID ||
|
||||
character.Submarine.DockedTo.Any(sub => sub.TeamID == character.TeamID || sub.TeamID == character.OriginalTeamID);
|
||||
return character.Submarine.TeamID == character.TeamID || character.Submarine.TeamID == character.OriginalTeamID;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true only when at a friendly outpost and when the order is set to be ignored there.
|
||||
/// Note that even if this returns false, the objective can be disallowed, because AllowInFriendlySubs is false.
|
||||
/// </summary>
|
||||
public bool IsIgnoredAtOutpost()
|
||||
{
|
||||
if (!IgnoreAtOutpost) { return false; }
|
||||
if (!Level.IsLoadedFriendlyOutpost) { return false; }
|
||||
if (!character.IsOnPlayerTeam) { return false; }
|
||||
if (character.Submarine?.Info == null) { return false; }
|
||||
return character.Submarine.Info.IsOutpost && character.Submarine.TeamID == CharacterTeamType.FriendlyNPC;
|
||||
}
|
||||
|
||||
protected void HandleNonAllowed()
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = !IsIgnoredAtOutpost();
|
||||
}
|
||||
|
||||
protected virtual float GetPriority()
|
||||
{
|
||||
bool isOrder = objectiveManager.IsOrder(this);
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
HandleNonAllowed();
|
||||
return Priority;
|
||||
}
|
||||
if (isOrder)
|
||||
if (objectiveManager.IsOrder(this))
|
||||
{
|
||||
Priority = objectiveManager.GetOrderPriority(this);
|
||||
}
|
||||
@@ -446,7 +458,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual bool Check()
|
||||
private bool Check()
|
||||
{
|
||||
if (AbortCondition != null && AbortCondition(this))
|
||||
{
|
||||
|
||||
+1
@@ -19,6 +19,7 @@ namespace Barotrauma
|
||||
protected override bool Filter(PowerContainer battery)
|
||||
{
|
||||
if (battery == null) { return false; }
|
||||
if (battery.OutputDisabled) { return false; }
|
||||
var item = battery.Item;
|
||||
if (item.IgnoreByAI(character)) { return false; }
|
||||
if (!item.IsInteractable(character)) { return false; }
|
||||
|
||||
+10
-7
@@ -12,6 +12,7 @@ namespace Barotrauma
|
||||
public override Identifier Identifier { get; set; } = "cleanup item".ToIdentifier();
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool AllowAutomaticItemUnequipping => false;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
public readonly Item item;
|
||||
public bool IsPriority { get; set; }
|
||||
@@ -30,8 +31,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
HandleNonAllowed();
|
||||
return Priority;
|
||||
}
|
||||
else
|
||||
@@ -119,18 +119,21 @@ namespace Barotrauma
|
||||
if (item.IgnoreByAI(character))
|
||||
{
|
||||
Abandon = true;
|
||||
return false;
|
||||
}
|
||||
if (item.ParentInventory != null)
|
||||
else if (item.ParentInventory != null)
|
||||
{
|
||||
if (item.Container != null && !AIObjectiveCleanupItems.IsValidContainer(item.Container, character, allowUnloading: objectiveManager.HasOrder<AIObjectiveCleanupItems>()))
|
||||
if (!objectiveManager.HasOrder<AIObjectiveCleanupItems>())
|
||||
{
|
||||
// Don't allow taking items from containers in the idle state.
|
||||
Abandon = true;
|
||||
}
|
||||
else if (item.Container != null && !AIObjectiveCleanupItems.IsValidContainer(item.Container, character))
|
||||
{
|
||||
// Target was picked up or moved by someone.
|
||||
Abandon = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return IsCompleted;
|
||||
return !Abandon && IsCompleted;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
|
||||
+7
-7
@@ -14,8 +14,6 @@ namespace Barotrauma
|
||||
|
||||
public readonly List<Item> prioritizedItems = new List<Item>();
|
||||
|
||||
public static readonly Identifier AllowCleanupTag = "allowcleanup".ToIdentifier();
|
||||
|
||||
protected override int MaxTargets => 100;
|
||||
|
||||
public AIObjectiveCleanupItems(Character character, AIObjectiveManager objectiveManager, Item prioritizedItem = null, float priorityModifier = 1)
|
||||
@@ -83,9 +81,8 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool IsValidContainer(Item container, Character character, bool allowUnloading = true) =>
|
||||
allowUnloading &&
|
||||
container.HasTag(AllowCleanupTag) &&
|
||||
public static bool IsValidContainer(Item container, Character character) =>
|
||||
container.HasTag(Tags.AllowCleanup) &&
|
||||
container.HasAccess(character) &&
|
||||
container.ParentInventory == null && container.OwnInventory != null && container.OwnInventory.AllItems.Any() &&
|
||||
container.GetComponent<ItemContainer>() != null &&
|
||||
@@ -103,15 +100,18 @@ namespace Barotrauma
|
||||
// In a character inventory
|
||||
return false;
|
||||
}
|
||||
if (!IsValidContainer(item.Container, character, allowUnloading)) { return false; }
|
||||
if (!allowUnloading) { return false; }
|
||||
if (!IsValidContainer(item.Container, character)) { return false; }
|
||||
}
|
||||
if (!item.HasAccess(character)) { return false; }
|
||||
if (character != null && !IsItemInsideValidSubmarine(item, character)) { return false; }
|
||||
if (item.HasBallastFloraInHull) { return false; }
|
||||
//something (e.g. a pet) was eating the item within the last second - don't clean up
|
||||
if (item.LastEatenTime > Timing.TotalTimeUnpaused - 1.0) { return false; }
|
||||
var wire = item.GetComponent<Wire>();
|
||||
if (wire != null)
|
||||
{
|
||||
if (wire.Connections.Any()) { return false; }
|
||||
if (wire.Connections.Any(c => c != null)) { return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+172
-182
@@ -46,7 +46,6 @@ namespace Barotrauma
|
||||
_weapon = value;
|
||||
_weaponComponent = null;
|
||||
hasAimed = false;
|
||||
RemoveSubObjective(ref seekAmmunitionObjective);
|
||||
}
|
||||
}
|
||||
private ItemComponent _weaponComponent;
|
||||
@@ -55,14 +54,7 @@ namespace Barotrauma
|
||||
get
|
||||
{
|
||||
if (Weapon == null) { return null; }
|
||||
if (_weaponComponent == null)
|
||||
{
|
||||
_weaponComponent =
|
||||
Weapon.GetComponent<RangedWeapon>() ??
|
||||
Weapon.GetComponent<MeleeWeapon>() ??
|
||||
Weapon.GetComponent<RepairTool>() as ItemComponent;
|
||||
}
|
||||
return _weaponComponent;
|
||||
return _weaponComponent ?? GetWeaponComponent(Weapon);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,13 +272,13 @@ namespace Barotrauma
|
||||
}
|
||||
break;
|
||||
case CombatMode.Arrest:
|
||||
if (HumanAIController.HasItem(Enemy, "handlocker".ToIdentifier(), out _, requireEquipped: true))
|
||||
if (HumanAIController.HasItem(Enemy, Tags.HandLockerItem, out _, requireEquipped: true))
|
||||
{
|
||||
IsCompleted = true;
|
||||
}
|
||||
else if (Enemy.IsKnockedDown &&
|
||||
!objectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>() &&
|
||||
!HumanAIController.HasItem(character, "handlocker".ToIdentifier(), out _, requireEquipped: false))
|
||||
!HumanAIController.HasItem(character, Tags.HandLockerItem, out _, requireEquipped: false))
|
||||
{
|
||||
IsCompleted = true;
|
||||
}
|
||||
@@ -348,8 +340,10 @@ namespace Barotrauma
|
||||
if (character.LockHands || Enemy == null)
|
||||
{
|
||||
Weapon = null;
|
||||
RemoveSubObjective(ref seekAmmunitionObjective);
|
||||
return false;
|
||||
}
|
||||
bool isAllowedToSeekWeapons = character.CurrentHull != null && !IsEnemyCloserThan(300) && character.IsOnPlayerTeam && IsOffensiveOrArrest;
|
||||
if (checkWeaponsTimer < 0)
|
||||
{
|
||||
checkWeaponsTimer = checkWeaponsInterval;
|
||||
@@ -375,7 +369,7 @@ namespace Barotrauma
|
||||
// All good, the weapon is loaded
|
||||
break;
|
||||
}
|
||||
if (Reload(seekAmmo: false))
|
||||
if (Reload(seekAmmo: isAllowedToSeekWeapons))
|
||||
{
|
||||
// All good, we can use the weapon.
|
||||
break;
|
||||
@@ -407,7 +401,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
bool isAllowedToSeekWeapons = character.CurrentHull != null && !IsEnemyCloserThan(300) && character.IsOnPlayerTeam && IsOffensiveOrArrest;
|
||||
if (!isAllowedToSeekWeapons)
|
||||
{
|
||||
if (WeaponComponent == null)
|
||||
@@ -416,7 +409,7 @@ namespace Barotrauma
|
||||
Mode = CombatMode.Retreat;
|
||||
}
|
||||
}
|
||||
else if (seekAmmunitionObjective == null && (WeaponComponent == null || WeaponComponent.CombatPriority < goodWeaponPriority))
|
||||
else if (seekAmmunitionObjective == null && (WeaponComponent == null || (WeaponComponent.CombatPriority < goodWeaponPriority)))
|
||||
{
|
||||
// Poor weapon equipped -> try to find better.
|
||||
RemoveSubObjective(ref seekAmmunitionObjective);
|
||||
@@ -431,27 +424,10 @@ namespace Barotrauma
|
||||
{
|
||||
if (Weapon != null && (i == Weapon || i.Prefab.Identifier == Weapon.Prefab.Identifier)) { return 0; }
|
||||
if (i.IsOwnedBy(character)) { return 0; }
|
||||
var mw = i.GetComponent<MeleeWeapon>();
|
||||
var rw = i.GetComponent<RangedWeapon>();
|
||||
float priority = 0;
|
||||
if (mw != null)
|
||||
if (GetWeaponComponent(i) is ItemComponent ic)
|
||||
{
|
||||
priority = mw.CombatPriority / 100;
|
||||
}
|
||||
else if (rw != null)
|
||||
{
|
||||
priority = rw.CombatPriority / 100;
|
||||
}
|
||||
if (i.HasTag("stunner"))
|
||||
{
|
||||
if (Mode == CombatMode.Arrest)
|
||||
{
|
||||
priority *= 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
priority /= 2;
|
||||
}
|
||||
priority = GetWeaponPriority(ic, prioritizeMelee: false, isCloseToEnemy: false, out _) / 100;
|
||||
}
|
||||
return priority;
|
||||
}
|
||||
@@ -477,6 +453,7 @@ namespace Barotrauma
|
||||
if (!CheckWeapon(seekAmmo: false))
|
||||
{
|
||||
Weapon = null;
|
||||
RemoveSubObjective(ref seekAmmunitionObjective);
|
||||
}
|
||||
}
|
||||
return Weapon != null;
|
||||
@@ -521,111 +498,164 @@ namespace Barotrauma
|
||||
|
||||
private Item FindWeapon(out ItemComponent weaponComponent) => GetWeapon(FindWeaponsFromInventory(), out weaponComponent);
|
||||
|
||||
private static ItemComponent GetWeaponComponent(Item item) =>
|
||||
item.GetComponent<MeleeWeapon>() ??
|
||||
item.GetComponent<RangedWeapon>() ??
|
||||
item.GetComponent<RepairTool>() ??
|
||||
item.GetComponent<Holdable>() as ItemComponent;
|
||||
|
||||
private float GetWeaponPriority(ItemComponent weapon, bool prioritizeMelee, bool isCloseToEnemy, out float lethalDmg)
|
||||
{
|
||||
lethalDmg = -1;
|
||||
float priority = weapon.CombatPriority;
|
||||
if (weapon is RepairTool repairTool)
|
||||
{
|
||||
switch (repairTool.UsableIn)
|
||||
{
|
||||
case RepairTool.UseEnvironment.Air:
|
||||
if (character.InWater) { return 0; }
|
||||
break;
|
||||
case RepairTool.UseEnvironment.Water:
|
||||
if (!character.InWater) { return 0; }
|
||||
break;
|
||||
case RepairTool.UseEnvironment.None:
|
||||
return 0;
|
||||
case RepairTool.UseEnvironment.Both:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (prioritizeMelee && weapon is MeleeWeapon)
|
||||
{
|
||||
priority *= 5;
|
||||
}
|
||||
if (weapon.IsEmpty(character))
|
||||
{
|
||||
if (weapon is RangedWeapon && isCloseToEnemy)
|
||||
{
|
||||
// Ignore weapons that don't have any ammunition (-> Don't seek ammo).
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reduce the priority for weapons that don't have proper ammunition loaded.
|
||||
if (character.HasEquippedItem(Weapon, predicate: CharacterInventory.IsHandSlotType))
|
||||
{
|
||||
// Yet prefer the equipped weapon.
|
||||
priority *= 0.75f;
|
||||
}
|
||||
else
|
||||
{
|
||||
priority *= 0.5f;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Enemy.Params.Health.StunImmunity)
|
||||
{
|
||||
if (weapon.Item.HasTag(Tags.StunnerItem))
|
||||
{
|
||||
priority /= 2;
|
||||
}
|
||||
}
|
||||
else if (Enemy.IsKnockedDown)
|
||||
{
|
||||
// Enemy is stunned, reduce the priority of stunner weapons.
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
if (attack != null)
|
||||
{
|
||||
lethalDmg = attack.GetTotalDamage();
|
||||
float max = lethalDmg + 1;
|
||||
if (weapon.Item.HasTag(Tags.StunnerItem))
|
||||
{
|
||||
priority = max;
|
||||
}
|
||||
else
|
||||
{
|
||||
float stunDmg = ApproximateStunDamage(weapon, attack);
|
||||
float diff = stunDmg - lethalDmg;
|
||||
priority = Math.Clamp(priority - Math.Max(diff * 2, 0), min: 1, max);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Mode == CombatMode.Arrest)
|
||||
{
|
||||
// Enemy is not stunned, increase the priority of stunner weapons and decrease the priority of lethal weapons.
|
||||
if (weapon.Item.HasTag(Tags.StunnerItem))
|
||||
{
|
||||
priority *= 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
if (attack != null)
|
||||
{
|
||||
lethalDmg = attack.GetTotalDamage();
|
||||
float stunDmg = ApproximateStunDamage(weapon, attack);
|
||||
float diff = stunDmg - lethalDmg;
|
||||
if (diff < 0)
|
||||
{
|
||||
priority /= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (weapon is MeleeWeapon && weapon.Item.HasTag(Tags.StunnerItem) && (Enemy.Params.Health.StunImmunity || !CanMeleeStunnerStun(weapon)))
|
||||
{
|
||||
// Cannot do stun damage -> use the melee damage to determine the priority.
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
priority = attack?.GetTotalDamage() ?? priority / 2;
|
||||
}
|
||||
return priority;
|
||||
}
|
||||
|
||||
private float ApproximateStunDamage(ItemComponent weapon, Attack attack)
|
||||
{
|
||||
// Try to reduce the priority using the actual damage values and status effects.
|
||||
// This is an approximation, because we can't check the status effect conditions here.
|
||||
// The result might be incorrect if there is a high stun effect that's only applied in certain conditions.
|
||||
var statusEffects = attack.StatusEffects.Where(se => !se.HasConditions && se.type == ActionType.OnUse && se.HasRequiredItems(character));
|
||||
if (weapon.statusEffectLists != null && weapon.statusEffectLists.TryGetValue(ActionType.OnUse, out List<StatusEffect> hitEffects))
|
||||
{
|
||||
statusEffects = statusEffects.Concat(hitEffects);
|
||||
}
|
||||
float afflictionsStun = attack.Afflictions.Keys.Sum(a => a.Identifier == AfflictionPrefab.StunType ? a.Strength : 0);
|
||||
float effectsStun = statusEffects.None() ? 0 : statusEffects.Max(se =>
|
||||
{
|
||||
float stunAmount = 0;
|
||||
var stunAffliction = se.Afflictions.Find(a => a.Identifier == AfflictionPrefab.StunType);
|
||||
if (stunAffliction != null)
|
||||
{
|
||||
stunAmount = stunAffliction.Strength;
|
||||
}
|
||||
return stunAmount;
|
||||
});
|
||||
return attack.Stun + afflictionsStun + effectsStun;
|
||||
}
|
||||
|
||||
private bool CanMeleeStunnerStun(ItemComponent weapon)
|
||||
{
|
||||
// If there's an item container that takes a battery,
|
||||
// assume that it's required for the stun effect
|
||||
// as we can't check the status effect conditions here.
|
||||
var mobileBatteryTag = Tags.MobileBattery;
|
||||
var containers = weapon.Item.Components.Where(ic =>
|
||||
ic is ItemContainer container &&
|
||||
container.ContainableItemIdentifiers.Contains(mobileBatteryTag));
|
||||
// If there's no such container, assume that the melee weapon can stun without a battery.
|
||||
return containers.None() || containers.Any(container =>
|
||||
(container as ItemContainer)?.Inventory.AllItems.Any(i => i != null && i.HasTag(mobileBatteryTag) && i.Condition > 0.0f) ?? false);
|
||||
}
|
||||
|
||||
private Item GetWeapon(IEnumerable<ItemComponent> weaponList, out ItemComponent weaponComponent)
|
||||
{
|
||||
weaponComponent = null;
|
||||
float bestPriority = 0;
|
||||
float lethalDmg = -1;
|
||||
bool isAllowedToSeekWeapons = !IsEnemyCloserThan(300);
|
||||
bool isCloseToEnemy = IsEnemyCloserThan(300);
|
||||
bool prioritizeMelee = IsEnemyCloserThan(50) || EnemyAIController.IsLatchedTo(Enemy, character);
|
||||
foreach (var weapon in weaponList)
|
||||
{
|
||||
float priority = weapon.CombatPriority;
|
||||
if (weapon is RepairTool repairTool)
|
||||
{
|
||||
switch (repairTool.UsableIn)
|
||||
{
|
||||
case RepairTool.UseEnvironment.Air:
|
||||
if (character.InWater) { continue; }
|
||||
break;
|
||||
case RepairTool.UseEnvironment.Water:
|
||||
if (!character.InWater) { continue; }
|
||||
break;
|
||||
case RepairTool.UseEnvironment.None:
|
||||
continue;
|
||||
case RepairTool.UseEnvironment.Both:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (prioritizeMelee)
|
||||
{
|
||||
if (weapon is MeleeWeapon)
|
||||
{
|
||||
priority *= 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
priority /= 2;
|
||||
}
|
||||
}
|
||||
if (weapon.IsEmpty(character))
|
||||
{
|
||||
if (weapon is RangedWeapon && !isAllowedToSeekWeapons)
|
||||
{
|
||||
// Close to the enemy. Ignore weapons that don't have any ammunition (-> Don't seek ammo).
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Halve the priority for weapons that don't have proper ammunition loaded.
|
||||
priority /= 2;
|
||||
}
|
||||
}
|
||||
if (Enemy.Params.Health.StunImmunity)
|
||||
{
|
||||
if (weapon.Item.HasTag("stunner"))
|
||||
{
|
||||
priority /= 2;
|
||||
}
|
||||
}
|
||||
else if (Enemy.IsKnockedDown)
|
||||
{
|
||||
// Enemy is stunned, reduce the priority of stunner weapons.
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
if (attack != null)
|
||||
{
|
||||
lethalDmg = attack.GetTotalDamage();
|
||||
float max = lethalDmg + 1;
|
||||
if (weapon.Item.HasTag("stunner"))
|
||||
{
|
||||
priority = max;
|
||||
}
|
||||
else
|
||||
{
|
||||
float stunDmg = ApproximateStunDamage(weapon, attack);
|
||||
float diff = stunDmg - lethalDmg;
|
||||
priority = Math.Clamp(priority - Math.Max(diff * 2, 0), min: 1, max);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Mode == CombatMode.Arrest)
|
||||
{
|
||||
// Enemy is not stunned, increase the priority of stunner weapons and decrease the priority of lethal weapons.
|
||||
if (weapon.Item.HasTag("stunner"))
|
||||
{
|
||||
priority *= 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
if (attack != null)
|
||||
{
|
||||
lethalDmg = attack.GetTotalDamage();
|
||||
float stunDmg = ApproximateStunDamage(weapon, attack);
|
||||
float diff = stunDmg - lethalDmg;
|
||||
if (diff < 0)
|
||||
{
|
||||
priority /= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (weapon is MeleeWeapon && weapon.Item.HasTag("stunner") && !CanMeleeStunnerStun(weapon))
|
||||
{
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
priority = attack?.GetTotalDamage() ?? priority / 2;
|
||||
}
|
||||
float priority = GetWeaponPriority(weapon, prioritizeMelee, isCloseToEnemy, out lethalDmg);
|
||||
if (priority > bestPriority)
|
||||
{
|
||||
weaponComponent = weapon;
|
||||
@@ -636,7 +666,7 @@ namespace Barotrauma
|
||||
if (bestPriority < 1) { return null; }
|
||||
if (Mode == CombatMode.Arrest)
|
||||
{
|
||||
if (weaponComponent.Item.HasTag("stunner"))
|
||||
if (weaponComponent.Item.HasTag(Tags.StunnerItem))
|
||||
{
|
||||
isLethalWeapon = false;
|
||||
}
|
||||
@@ -654,44 +684,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
return weaponComponent.Item;
|
||||
|
||||
float ApproximateStunDamage(ItemComponent weapon, Attack attack)
|
||||
{
|
||||
// Try to reduce the priority using the actual damage values and status effects.
|
||||
// This is an approximation, because we can't check the status effect conditions here.
|
||||
// The result might be incorrect if there is a high stun effect that's only applied in certain conditions.
|
||||
var statusEffects = attack.StatusEffects.Where(se => !se.HasConditions && se.type == ActionType.OnUse && se.HasRequiredItems(character));
|
||||
if (weapon.statusEffectLists != null && weapon.statusEffectLists.TryGetValue(ActionType.OnUse, out List<StatusEffect> hitEffects))
|
||||
{
|
||||
statusEffects = statusEffects.Concat(hitEffects);
|
||||
}
|
||||
float afflictionsStun = attack.Afflictions.Keys.Sum(a => a.Identifier == AfflictionPrefab.StunType ? a.Strength : 0);
|
||||
float effectsStun = statusEffects.None() ? 0 : statusEffects.Max(se =>
|
||||
{
|
||||
float stunAmount = 0;
|
||||
var stunAffliction = se.Afflictions.Find(a => a.Identifier == AfflictionPrefab.StunType);
|
||||
if (stunAffliction != null)
|
||||
{
|
||||
stunAmount = stunAffliction.Strength;
|
||||
}
|
||||
return stunAmount;
|
||||
});
|
||||
return attack.Stun + afflictionsStun + effectsStun;
|
||||
}
|
||||
|
||||
bool CanMeleeStunnerStun(ItemComponent weapon)
|
||||
{
|
||||
// If there's an item container that takes a battery,
|
||||
// assume that it's required for the stun effect
|
||||
// as we can't check the status effect conditions here.
|
||||
var mobileBatteryTag = "mobilebattery".ToIdentifier();
|
||||
var containers = weapon.Item.Components.Where(ic =>
|
||||
ic is ItemContainer container &&
|
||||
container.ContainableItemIdentifiers.Contains(mobileBatteryTag));
|
||||
// If there's no such container, assume that the melee weapon can stun without a battery.
|
||||
return containers.None() || containers.Any(container =>
|
||||
(container as ItemContainer)?.Inventory.AllItems.Any(i => i != null && i.HasTag(mobileBatteryTag) && i.Condition > 0.0f) ?? false);
|
||||
}
|
||||
}
|
||||
|
||||
public static float GetLethalDamage(ItemComponent weapon)
|
||||
@@ -771,13 +763,13 @@ namespace Barotrauma
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!character.HasEquippedItem(Weapon, predicate: IsHandSlotType))
|
||||
if (!character.HasEquippedItem(Weapon, predicate: CharacterInventory.IsHandSlotType))
|
||||
{
|
||||
//clear aim and shoot inputs so the bot doesn't immediately fire the weapon if it was previously e.g. using a scooter
|
||||
character.ClearInput(InputType.Aim);
|
||||
character.ClearInput(InputType.Shoot);
|
||||
Weapon.TryInteract(character, forceSelectKey: true);
|
||||
var slots = Weapon.AllowedSlots.Where(s => IsHandSlotType(s));
|
||||
var slots = Weapon.AllowedSlots.Where(s => CharacterInventory.IsHandSlotType(s));
|
||||
if (character.Inventory.TryPutItem(Weapon, character, slots))
|
||||
{
|
||||
SetAimTimer(Rand.Range(0.2f, 0.4f) / AimSpeed);
|
||||
@@ -791,8 +783,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
static bool IsHandSlotType(InvSlotType s) => s == InvSlotType.LeftHand || s == InvSlotType.RightHand || s == (InvSlotType.LeftHand | InvSlotType.RightHand);
|
||||
}
|
||||
|
||||
private float findHullTimer;
|
||||
@@ -926,7 +916,7 @@ namespace Barotrauma
|
||||
if (followTargetObjective == null) { return; }
|
||||
if (Mode == CombatMode.Arrest && Enemy.IsKnockedDown)
|
||||
{
|
||||
if (HumanAIController.HasItem(character, "handlocker".ToIdentifier(), out _))
|
||||
if (HumanAIController.HasItem(character, Tags.HandLockerItem, out _))
|
||||
{
|
||||
if (!arrestingRegistered)
|
||||
{
|
||||
@@ -986,7 +976,7 @@ namespace Barotrauma
|
||||
foreach (var item in Enemy.Inventory.AllItemsMod)
|
||||
{
|
||||
if (character.TeamID == CharacterTeamType.FriendlyNPC && item.StolenDuringRound ||
|
||||
item.HasTag("weapon") ||
|
||||
item.HasTag(Tags.Weapon) ||
|
||||
item.GetComponent<MeleeWeapon>() != null ||
|
||||
item.GetComponent<RangedWeapon>() != null)
|
||||
{
|
||||
@@ -997,9 +987,9 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
//prefer using handcuffs already on the enemy's inventory
|
||||
if (!HumanAIController.HasItem(Enemy, "handlocker".ToIdentifier(), out IEnumerable<Item> matchingItems))
|
||||
if (!HumanAIController.HasItem(Enemy, Tags.HandLockerItem, out IEnumerable<Item> matchingItems))
|
||||
{
|
||||
HumanAIController.HasItem(character, "handlocker".ToIdentifier(), out matchingItems);
|
||||
HumanAIController.HasItem(character, Tags.HandLockerItem, out matchingItems);
|
||||
}
|
||||
|
||||
if (matchingItems.Any() &&
|
||||
@@ -1079,7 +1069,7 @@ namespace Barotrauma
|
||||
if (ammunitionIdentifiers != null)
|
||||
{
|
||||
// Try reload ammunition from inventory
|
||||
static bool IsInsideHeadset(Item i) => i.ParentInventory?.Owner is Item ownerItem && ownerItem.HasTag("mobileradio");
|
||||
static bool IsInsideHeadset(Item i) => i.ParentInventory?.Owner is Item ownerItem && ownerItem.HasTag(Tags.MobileRadio);
|
||||
Item ammunition = character.Inventory.FindItem(i => i.HasIdentifierOrTags(ammunitionIdentifiers) && i.Condition > 0 && !IsInsideHeadset(i), recursive: true);
|
||||
if (ammunition != null)
|
||||
{
|
||||
@@ -1205,7 +1195,7 @@ namespace Barotrauma
|
||||
myBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody);
|
||||
}
|
||||
// Check that we don't hit friendlies. No need to check the walls, because there's a separate check for that at 1096 (which intentionally has a small delay)
|
||||
var pickedBodies = Submarine.PickBodies(Weapon.SimPosition, Character.GetRelativeSimPosition(from: Weapon, to: Enemy), myBodies, Physics.CollisionCharacter);
|
||||
var pickedBodies = Submarine.PickBodies(Weapon.SimPosition, Submarine.GetRelativeSimPosition(from: Weapon, to: Enemy), myBodies, Physics.CollisionCharacter);
|
||||
foreach (var body in pickedBodies)
|
||||
{
|
||||
Character target = null;
|
||||
@@ -1248,7 +1238,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
character.SetInput(InputType.Shoot, false, true);
|
||||
Weapon.Use(deltaTime, character);
|
||||
Weapon.Use(deltaTime, user: character);
|
||||
reloadTimer = Math.Max(reloadTime, reloadTime * Rand.Range(1f, 1.25f) / AimSpeed);
|
||||
}
|
||||
|
||||
@@ -1265,7 +1255,7 @@ namespace Barotrauma
|
||||
{
|
||||
Unequip();
|
||||
}
|
||||
SteeringManager.Reset();
|
||||
SteeringManager?.Reset();
|
||||
}
|
||||
|
||||
protected override void OnAbandon()
|
||||
@@ -1275,7 +1265,7 @@ namespace Barotrauma
|
||||
{
|
||||
Unequip();
|
||||
}
|
||||
SteeringManager.Reset();
|
||||
SteeringManager?.Reset();
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
|
||||
+6
-2
@@ -11,6 +11,7 @@ namespace Barotrauma
|
||||
class AIObjectiveContainItem: AIObjective
|
||||
{
|
||||
public override Identifier Identifier { get; set; } = "contain item".ToIdentifier();
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
public Func<Item, float> GetItemPriority;
|
||||
|
||||
@@ -109,7 +110,7 @@ namespace Barotrauma
|
||||
|
||||
private bool CheckItem(Item item)
|
||||
{
|
||||
return item.HasIdentifierOrTags(itemIdentifiers) && item.ConditionPercentage >= ConditionLevel && item.HasAccess(character);
|
||||
return item.HasIdentifierOrTags(itemIdentifiers) && item.ConditionPercentage >= ConditionLevel && item.HasAccess(character) && container.ShouldBeContained(item, out _);
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
@@ -226,7 +227,10 @@ namespace Barotrauma
|
||||
AllowToFindDivingGear = AllowToFindDivingGear,
|
||||
AllowDangerousPressure = AllowDangerousPressure,
|
||||
TargetCondition = ConditionLevel,
|
||||
ItemFilter = (Item potentialItem) => RemoveEmpty ? container.CanBeContained(potentialItem) : container.Inventory.CanBePut(potentialItem),
|
||||
ItemFilter = (Item potentialItem) =>
|
||||
{
|
||||
return (RemoveEmpty ? container.CanBeContained(potentialItem) : container.Inventory.CanBePut(potentialItem)) && container.ShouldBeContained(potentialItem, out _);
|
||||
},
|
||||
ItemCount = ItemCount,
|
||||
TakeWholeStack = MoveWholeStack
|
||||
}, onAbandon: () =>
|
||||
|
||||
+6
-5
@@ -9,11 +9,12 @@ namespace Barotrauma
|
||||
class AIObjectiveDecontainItem : AIObjective
|
||||
{
|
||||
public override Identifier Identifier { get; set; } = "decontain item".ToIdentifier();
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
public Func<Item, float> GetItemPriority;
|
||||
|
||||
//can either be a tag or an identifier
|
||||
private readonly string[] itemIdentifiers;
|
||||
private readonly Identifier[] itemIdentifiers;
|
||||
private readonly ItemContainer sourceContainer;
|
||||
private readonly ItemContainer targetContainer;
|
||||
private readonly Item targetItem;
|
||||
@@ -52,16 +53,16 @@ namespace Barotrauma
|
||||
this.targetContainer = targetContainer;
|
||||
}
|
||||
|
||||
public AIObjectiveDecontainItem(Character character, string itemIdentifier, AIObjectiveManager objectiveManager, ItemContainer sourceContainer, ItemContainer targetContainer = null, float priorityModifier = 1)
|
||||
: this(character, new string[] { itemIdentifier }, objectiveManager, sourceContainer, targetContainer, priorityModifier) { }
|
||||
public AIObjectiveDecontainItem(Character character, Identifier itemIdentifier, AIObjectiveManager objectiveManager, ItemContainer sourceContainer, ItemContainer targetContainer = null, float priorityModifier = 1)
|
||||
: this(character, new Identifier[] { itemIdentifier }, objectiveManager, sourceContainer, targetContainer, priorityModifier) { }
|
||||
|
||||
public AIObjectiveDecontainItem(Character character, string[] itemIdentifiers, AIObjectiveManager objectiveManager, ItemContainer sourceContainer, ItemContainer targetContainer = null, float priorityModifier = 1)
|
||||
public AIObjectiveDecontainItem(Character character, Identifier[] itemIdentifiers, AIObjectiveManager objectiveManager, ItemContainer sourceContainer, ItemContainer targetContainer = null, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
this.itemIdentifiers = itemIdentifiers;
|
||||
for (int i = 0; i < itemIdentifiers.Length; i++)
|
||||
{
|
||||
itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant();
|
||||
itemIdentifiers[i] = itemIdentifiers[i];
|
||||
}
|
||||
this.sourceContainer = sourceContainer;
|
||||
this.targetContainer = targetContainer;
|
||||
|
||||
+1
-1
@@ -88,7 +88,7 @@ namespace Barotrauma
|
||||
escapeProgress += Rand.Range(2, 5);
|
||||
if (escapeProgress > 15)
|
||||
{
|
||||
Item handcuffs = character.Inventory.FindItemByTag("handlocker".ToIdentifier());
|
||||
Item handcuffs = character.Inventory.FindItemByTag(Tags.HandLockerItem);
|
||||
if (handcuffs != null)
|
||||
{
|
||||
handcuffs.Drop(character);
|
||||
|
||||
+6
-5
@@ -15,6 +15,8 @@ namespace Barotrauma
|
||||
|
||||
public override bool AllowInAnySub => true;
|
||||
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
private readonly Hull targetHull;
|
||||
|
||||
private AIObjectiveGetItem getExtinguisherObjective;
|
||||
@@ -30,8 +32,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
HandleNonAllowed();
|
||||
return Priority;
|
||||
}
|
||||
bool isOrder = objectiveManager.HasOrder<AIObjectiveExtinguishFires>();
|
||||
@@ -176,19 +177,19 @@ namespace Barotrauma
|
||||
getExtinguisherObjective = null;
|
||||
gotoObjective = null;
|
||||
sinTime = 0;
|
||||
SteeringManager.Reset();
|
||||
SteeringManager?.Reset();
|
||||
}
|
||||
|
||||
protected override void OnCompleted()
|
||||
{
|
||||
base.OnCompleted();
|
||||
SteeringManager.Reset();
|
||||
SteeringManager?.Reset();
|
||||
}
|
||||
|
||||
protected override void OnAbandon()
|
||||
{
|
||||
base.OnAbandon();
|
||||
SteeringManager.Reset();
|
||||
SteeringManager?.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
-28
@@ -12,6 +12,7 @@ namespace Barotrauma
|
||||
public override bool ForceRun => true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool AbandonWhenCannotCompleteSubjectives => false;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
private readonly Identifier gearTag;
|
||||
|
||||
@@ -22,34 +23,20 @@ namespace Barotrauma
|
||||
|
||||
public const float MIN_OXYGEN = 10;
|
||||
|
||||
public static readonly Identifier HEAVY_DIVING_GEAR = "deepdiving".ToIdentifier();
|
||||
public static readonly Identifier LIGHT_DIVING_GEAR = "lightdiving".ToIdentifier();
|
||||
/// <summary>
|
||||
/// Diving gear that's suitable for wearing indoors (-> the bots don't try to unequip it when they don't need diving gear)
|
||||
/// </summary>
|
||||
public static readonly Identifier DIVING_GEAR_WEARABLE_INDOORS = "divinggear_wearableindoors".ToIdentifier();
|
||||
public static readonly Identifier OXYGEN_SOURCE = "oxygensource".ToIdentifier();
|
||||
|
||||
protected override bool CheckObjectiveSpecific() =>
|
||||
targetItem != null && character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.InnerClothes | InvSlotType.Head);
|
||||
|
||||
public AIObjectiveFindDivingGear(Character character, bool needsDivingSuit, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
gearTag = needsDivingSuit ? HEAVY_DIVING_GEAR : LIGHT_DIVING_GEAR;
|
||||
gearTag = needsDivingSuit ? Tags.HeavyDivingGear : Tags.LightDivingGear;
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (character.LockHands)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
|
||||
TrySetTargetItem(character.Inventory.FindItemByTag(gearTag, true));
|
||||
if (targetItem == null && gearTag == LIGHT_DIVING_GEAR)
|
||||
if (targetItem == null && gearTag == Tags.LightDivingGear)
|
||||
{
|
||||
TrySetTargetItem(character.Inventory.FindItemByTag(HEAVY_DIVING_GEAR, true));
|
||||
TrySetTargetItem(character.Inventory.FindItemByTag(Tags.HeavyDivingGear, true));
|
||||
}
|
||||
if (targetItem == null ||
|
||||
!character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.InnerClothes | InvSlotType.Head) &&
|
||||
@@ -74,7 +61,7 @@ namespace Barotrauma
|
||||
onCompleted: () =>
|
||||
{
|
||||
RemoveSubObjective(ref getDivingGear);
|
||||
if (gearTag == HEAVY_DIVING_GEAR && HumanAIController.HasItem(character, LIGHT_DIVING_GEAR, out IEnumerable<Item> masks, requireEquipped: true))
|
||||
if (gearTag == Tags.HeavyDivingGear && HumanAIController.HasItem(character, Tags.LightDivingGear, out IEnumerable<Item> masks, requireEquipped: true))
|
||||
{
|
||||
foreach (Item mask in masks)
|
||||
{
|
||||
@@ -95,10 +82,10 @@ namespace Barotrauma
|
||||
{
|
||||
if (character.IsOnPlayerTeam)
|
||||
{
|
||||
if (HumanAIController.HasItem(character, OXYGEN_SOURCE, out _, conditionPercentage: min))
|
||||
if (HumanAIController.HasItem(character, Tags.OxygenSource, out _, conditionPercentage: min))
|
||||
{
|
||||
character.Speak(TextManager.Get("dialogswappingoxygentank").Value, null, 0, "swappingoxygentank".ToIdentifier(), 30.0f);
|
||||
if (character.Inventory.FindAllItems(i => i.HasTag(OXYGEN_SOURCE) && i.Condition > min, recursive: true).Count == 1)
|
||||
if (character.Inventory.FindAllItems(i => i.HasTag(Tags.OxygenSource) && i.Condition > min, recursive: true).Count == 1)
|
||||
{
|
||||
character.Speak(TextManager.Get("dialoglastoxygentank").Value, null, 0.0f, "dialoglastoxygentank".ToIdentifier(), 30.0f);
|
||||
}
|
||||
@@ -109,7 +96,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
var container = targetItem.GetComponent<ItemContainer>();
|
||||
var objective = new AIObjectiveContainItem(character, OXYGEN_SOURCE, container, objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
var objective = new AIObjectiveContainItem(character, Tags.OxygenSource, container, objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
AllowToFindDivingGear = false,
|
||||
AllowDangerousPressure = true,
|
||||
@@ -119,7 +106,7 @@ namespace Barotrauma
|
||||
};
|
||||
if (container.HasSubContainers)
|
||||
{
|
||||
objective.TargetSlot = container.FindSuitableSubContainerIndex(OXYGEN_SOURCE);
|
||||
objective.TargetSlot = container.FindSuitableSubContainerIndex(Tags.OxygenSource);
|
||||
}
|
||||
// Only remove the oxygen source being replaced
|
||||
objective.RemoveExistingPredicate = i => objective.IsInTargetSlot(i);
|
||||
@@ -132,7 +119,7 @@ namespace Barotrauma
|
||||
// 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)
|
||||
return new AIObjectiveContainItem(character, Tags.OxygenSource, targetItem.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
AllowToFindDivingGear = false,
|
||||
AllowDangerousPressure = true,
|
||||
@@ -142,7 +129,7 @@ namespace Barotrauma
|
||||
onAbandon: () =>
|
||||
{
|
||||
Abandon = true;
|
||||
if (remainingTanks > 0 && !HumanAIController.HasItem(character, OXYGEN_SOURCE, out _, conditionPercentage: 0.01f))
|
||||
if (remainingTanks > 0 && !HumanAIController.HasItem(character, Tags.OxygenSource, out _, conditionPercentage: 0.01f))
|
||||
{
|
||||
character.Speak(TextManager.Get("dialogcantfindtoxygen").Value, null, 0, "cantfindoxygen".ToIdentifier(), 30.0f);
|
||||
}
|
||||
@@ -158,7 +145,7 @@ namespace Barotrauma
|
||||
int ReportOxygenTankCount()
|
||||
{
|
||||
if (character.Submarine != Submarine.MainSub) { return 1; }
|
||||
int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag(OXYGEN_SOURCE) && i.Condition > 1);
|
||||
int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag(Tags.OxygenSource) && i.Condition > 1);
|
||||
if (remainingOxygenTanks == 0)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogOutOfOxygenTanks").Value, null, 0.0f, "outofoxygentanks".ToIdentifier(), 30.0f);
|
||||
@@ -177,7 +164,7 @@ namespace Barotrauma
|
||||
{
|
||||
return
|
||||
item != null &&
|
||||
item.HasTag(OXYGEN_SOURCE) &&
|
||||
item.HasTag(Tags.OxygenSource) &&
|
||||
item.Condition > 0 &&
|
||||
(oxygenSourceSlotIndex == null || item.ParentInventory.IsInSlot(item, oxygenSourceSlotIndex.Value));
|
||||
}
|
||||
@@ -188,7 +175,7 @@ namespace Barotrauma
|
||||
targetItem = item;
|
||||
if (targetItem != null)
|
||||
{
|
||||
oxygenSourceSlotIndex = targetItem.GetComponent<ItemContainer>()?.FindSuitableSubContainerIndex(OXYGEN_SOURCE);
|
||||
oxygenSourceSlotIndex = targetItem.GetComponent<ItemContainer>()?.FindSuitableSubContainerIndex(Tags.OxygenSource);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -212,7 +199,7 @@ namespace Barotrauma
|
||||
// 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 = 0.01f;
|
||||
float minOxygen = character.IsInFriendlySub ? MIN_OXYGEN : min;
|
||||
if (minOxygen > min && character.Inventory.AllItems.Any(i => i.HasTag("oxygensource") && i.ConditionPercentage >= minOxygen))
|
||||
if (minOxygen > min && character.Inventory.AllItems.Any(i => i.HasTag(Tags.OxygenSource) && i.ConditionPercentage >= minOxygen))
|
||||
{
|
||||
// There's a valid oxygen tank in the inventory -> no need to swap the tank too early.
|
||||
minOxygen = min;
|
||||
|
||||
+15
-12
@@ -40,24 +40,21 @@ namespace Barotrauma
|
||||
|
||||
protected override float GetPriority()
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
return Priority;
|
||||
}
|
||||
if (character.CurrentHull == null)
|
||||
{
|
||||
Priority = (
|
||||
objectiveManager.HasOrder<AIObjectiveGoTo>(o => o.Priority > 0) ||
|
||||
objectiveManager.HasOrder<AIObjectiveReturn>(o => o.Priority > 0) ||
|
||||
objectiveManager.HasActiveObjective<AIObjectiveRescue>() ||
|
||||
objectiveManager.Objectives.Any(o => o is AIObjectiveCombat && o.Priority > 0))
|
||||
objectiveManager.Objectives.Any(o => (o is AIObjectiveCombat || o is AIObjectiveReturn) && o.Priority > 0))
|
||||
&& ((!character.IsLowInOxygen && character.IsImmuneToPressure)|| HumanAIController.HasDivingSuit(character)) ? 0 : AIObjectiveManager.EmergencyObjectivePriority - 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((character.IsLowInOxygen && !character.AnimController.HeadInWater && HumanAIController.HasDivingSuit(character, requireOxygenTank: false)) ||
|
||||
NeedMoreDivingGear(character.CurrentHull, AIObjectiveFindDivingGear.GetMinOxygen(character)))
|
||||
bool isSuffocatingInDivingSuit = character.IsLowInOxygen && !character.AnimController.HeadInWater && HumanAIController.HasDivingSuit(character, requireOxygenTank: false);
|
||||
static bool IsSuffocatingWithoutDivingGear(Character c) => c.IsLowInOxygen && c.AnimController.HeadInWater && !HumanAIController.HasDivingGear(c, requireOxygenTank: true);
|
||||
if (isSuffocatingInDivingSuit ||
|
||||
NeedMoreDivingGear(character.CurrentHull, AIObjectiveFindDivingGear.GetMinOxygen(character)) ||
|
||||
(!objectiveManager.HasActiveObjective<AIObjectiveFindDivingGear>() && IsSuffocatingWithoutDivingGear(character)))
|
||||
{
|
||||
Priority = AIObjectiveManager.MaxObjectivePriority;
|
||||
}
|
||||
@@ -215,7 +212,7 @@ namespace Barotrauma
|
||||
AllowGoingOutside =
|
||||
character.IsProtectedFromPressure ||
|
||||
character.CurrentHull == null ||
|
||||
character.CurrentHull.IsTaggedAirlock() ||
|
||||
character.CurrentHull.IsAirlock ||
|
||||
character.CurrentHull.LeadsOutside(character)
|
||||
},
|
||||
onCompleted: () =>
|
||||
@@ -258,6 +255,13 @@ namespace Barotrauma
|
||||
}
|
||||
if (subObjectives.Any(so => so.CanBeCompleted)) { return; }
|
||||
UpdateSimpleEscape(deltaTime);
|
||||
if (cannotFindSafeHull && !character.IsInFriendlySub && objectiveManager.Objectives.None(o => o is AIObjectiveReturn))
|
||||
{
|
||||
if (OrderPrefab.Prefabs.TryGet("return".ToIdentifier(), out OrderPrefab orderPrefab))
|
||||
{
|
||||
objectiveManager.AddObjective(new AIObjectiveReturn(character, character, objectiveManager));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,8 +437,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: could also target gaps that get us inside?
|
||||
if (potentialHull.IsTaggedAirlock())
|
||||
if (potentialHull.IsAirlock)
|
||||
{
|
||||
hullSafety = 100;
|
||||
hullIsAirlock = true;
|
||||
|
||||
+19
-17
@@ -12,7 +12,9 @@ namespace Barotrauma
|
||||
public override Identifier Identifier { get; set; } = "fix leak".ToIdentifier();
|
||||
public override bool ForceRun => true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool AllowInFriendlySubs => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
public Gap Leak { get; private set; }
|
||||
|
||||
@@ -35,8 +37,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
HandleNonAllowed();
|
||||
return Priority;
|
||||
}
|
||||
float coopMultiplier = 1;
|
||||
@@ -94,6 +95,7 @@ namespace Barotrauma
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
var weldingTool = character.Inventory.FindItemByTag("weldingequipment".ToIdentifier(), true);
|
||||
var repairTool = weldingTool?.GetComponent<RepairTool>();
|
||||
if (weldingTool == null)
|
||||
{
|
||||
TryAddSubObjective(ref getWeldingTool, () => new AIObjectiveGetItem(character, "weldingequipment".ToIdentifier(), objectiveManager, equip: true, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC),
|
||||
@@ -110,17 +112,25 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (weldingTool.OwnInventory == null)
|
||||
if (repairTool == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError($"{character.Name}: AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no proper inventory");
|
||||
DebugConsole.ThrowError($"{character.Name}: AIObjectiveFixLeak failed - the item \"{weldingTool}\" has no RepairTool component but is tagged as a welding tool");
|
||||
#endif
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (weldingTool.OwnInventory != null && weldingTool.OwnInventory.AllItems.None(i => i.HasTag("weldingfuel") && i.Condition > 0.0f))
|
||||
if (weldingTool.OwnInventory == null && repairTool.requiredItems.Any(r => r.Key == RelatedItem.RelationType.Contained))
|
||||
{
|
||||
TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, "weldingfuel".ToIdentifier(), weldingTool.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError($"{character.Name}: AIObjectiveFixLeak failed - the item \"{weldingTool}\" has no proper inventory");
|
||||
#endif
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (weldingTool.OwnInventory != null && weldingTool.OwnInventory.AllItems.None(i => i.HasTag(Tags.WeldingFuel) && i.Condition > 0.0f))
|
||||
{
|
||||
TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, Tags.WeldingFuel, weldingTool.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
RemoveExisting = true
|
||||
},
|
||||
@@ -138,7 +148,7 @@ namespace Barotrauma
|
||||
void ReportWeldingFuelTankCount()
|
||||
{
|
||||
if (character.Submarine != Submarine.MainSub) { return; }
|
||||
int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag("weldingfuel") && i.Condition > 1);
|
||||
int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag(Tags.WeldingFuel) && i.Condition > 1);
|
||||
if (remainingOxygenTanks == 0)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogOutOfWeldingFuel").Value, null, 0.0f, "outofweldingfuel".ToIdentifier(), 30.0f);
|
||||
@@ -152,15 +162,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
if (subObjectives.Any()) { return; }
|
||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
||||
if (repairTool == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError($"{character.Name}: AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
||||
#endif
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
Vector2 toLeak = Leak.WorldPosition - character.AnimController.AimSourceWorldPos;
|
||||
// TODO: use the collider size/reach?
|
||||
if (!character.AnimController.InWater && Math.Abs(toLeak.X) < 100 && toLeak.Y < 0.0f && toLeak.Y > -150)
|
||||
@@ -200,7 +201,8 @@ namespace Barotrauma
|
||||
Leak.linkedTo.Any(e => e is Hull h && (character.CurrentHull == h || h.linkedTo.Contains(character.CurrentHull))),
|
||||
endNodeFilter = IsSuitableEndNode,
|
||||
// The Go To objective can be abandoned if the leak is fixed (in which case we don't want to use the dialogue)
|
||||
SpeakCannotReachCondition = () => !CheckObjectiveSpecific()
|
||||
// Only report about contextual targets.
|
||||
SpeakCannotReachCondition = () => isPriority && !CheckObjectiveSpecific()
|
||||
},
|
||||
onAbandon: () =>
|
||||
{
|
||||
|
||||
+2
-1
@@ -9,7 +9,8 @@ namespace Barotrauma
|
||||
public override Identifier Identifier { get; set; } = "fix leaks".ToIdentifier();
|
||||
public override bool ForceRun => true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
public override bool AllowInFriendlySubs => true;
|
||||
|
||||
private Hull PrioritizedHull { get; set; }
|
||||
|
||||
public AIObjectiveFixLeaks(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1, Hull prioritizedHull = null) : base(character, objectiveManager, priorityModifier)
|
||||
|
||||
+40
-10
@@ -15,6 +15,7 @@ namespace Barotrauma
|
||||
|
||||
public override bool AbandonWhenCannotCompleteSubjectives => false;
|
||||
public override bool AllowMultipleInstances => true;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
public HashSet<Item> ignoredItems = new HashSet<Item>();
|
||||
|
||||
@@ -158,11 +159,6 @@ namespace Barotrauma
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (character.LockHands)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (IdentifiersOrTags != null && !isDoneSeeking)
|
||||
{
|
||||
if (checkInventory)
|
||||
@@ -271,15 +267,49 @@ namespace Barotrauma
|
||||
|
||||
Inventory itemInventory = targetItem.ParentInventory;
|
||||
var slots = itemInventory?.FindIndices(targetItem);
|
||||
var droppedStack = TargetItem.DroppedStack.ToList();
|
||||
if (HumanAIController.TakeItem(targetItem, character.Inventory, Equip, Wear, storeUnequipped: true, targetTags: IdentifiersOrTags))
|
||||
{
|
||||
if (TakeWholeStack && slots != null)
|
||||
if (TakeWholeStack)
|
||||
{
|
||||
foreach (int slot in slots)
|
||||
//taking the whole stack in this context means "as many items that can fit in one of the bot's slots",
|
||||
//and the stack means either a stack of items in an inventory slot or a "dropped stack"
|
||||
//so we need a bit of extra logic here
|
||||
int maxStackSize = 0;
|
||||
int takenItemCount = 1;
|
||||
for (int i = 0; i < character.Inventory.Capacity; i++)
|
||||
{
|
||||
foreach (Item item in itemInventory.GetItemsAt(slot).ToList())
|
||||
maxStackSize = Math.Max(maxStackSize, character.Inventory.HowManyCanBePut(targetItem.Prefab, i, condition: null));
|
||||
}
|
||||
if (slots != null)
|
||||
{
|
||||
foreach (int slot in slots)
|
||||
{
|
||||
HumanAIController.TakeItem(item, character.Inventory, equip: false, storeUnequipped: true);
|
||||
foreach (Item item in itemInventory.GetItemsAt(slot).ToList())
|
||||
{
|
||||
if (HumanAIController.TakeItem(item, character.Inventory, equip: false, storeUnequipped: true))
|
||||
{
|
||||
takenItemCount++;
|
||||
if (takenItemCount >= maxStackSize) { break; }
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var item in droppedStack)
|
||||
{
|
||||
if (item == TargetItem) { continue; }
|
||||
if (HumanAIController.TakeItem(item, character.Inventory, equip: false, storeUnequipped: true))
|
||||
{
|
||||
takenItemCount++;
|
||||
if (takenItemCount >= maxStackSize) { break; }
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -411,7 +441,7 @@ namespace Barotrauma
|
||||
if (!CheckItem(item)) { continue; }
|
||||
if (item.Container != null)
|
||||
{
|
||||
if (item.Container.HasTag("donttakeitems")) { continue; }
|
||||
if (item.Container.HasTag(Tags.DontTakeItems)) { continue; }
|
||||
if (ignoredItems.Contains(item.Container)) { continue; }
|
||||
if (ignoredContainerIdentifiers != null)
|
||||
{
|
||||
|
||||
+41
-47
@@ -12,6 +12,7 @@ namespace Barotrauma
|
||||
public override string DebugTag => $"{Identifier}";
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool AllowMultipleInstances => true;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
public bool AllowStealing { get; set; }
|
||||
public bool TakeWholeStack { get; set; }
|
||||
@@ -40,55 +41,48 @@ namespace Barotrauma
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (character.LockHands)
|
||||
if (subObjectivesCreated) { return; }
|
||||
foreach (Identifier tag in gearTags)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (!subObjectivesCreated)
|
||||
{
|
||||
foreach (Identifier tag in gearTags)
|
||||
{
|
||||
if (subObjectives.Any(so => so is AIObjectiveGetItem getItem && getItem.IdentifiersOrTags.Contains(tag))) { continue; }
|
||||
int count = gearTags.Count(t => t == tag);
|
||||
AIObjectiveGetItem? getItem = null;
|
||||
TryAddSubObjective(ref getItem, () =>
|
||||
new AIObjectiveGetItem(character, tag, objectiveManager, Equip, CheckInventory && count <= 1)
|
||||
{
|
||||
AllowVariants = AllowVariants,
|
||||
Wear = Wear,
|
||||
TakeWholeStack = TakeWholeStack,
|
||||
AllowStealing = AllowStealing,
|
||||
ignoredIdentifiersOrTags = ignoredTags,
|
||||
CheckPathForEachItem = CheckPathForEachItem,
|
||||
RequireNonEmpty = RequireNonEmpty,
|
||||
ItemCount = count,
|
||||
SpeakIfFails = RequireAllItems
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
var item = getItem?.TargetItem;
|
||||
if (item?.IsOwnedBy(character) != null)
|
||||
{
|
||||
achievedItems.Add(item);
|
||||
}
|
||||
},
|
||||
onAbandon: () =>
|
||||
{
|
||||
var item = getItem?.TargetItem;
|
||||
if (item != null)
|
||||
{
|
||||
achievedItems.Remove(item);
|
||||
}
|
||||
RemoveSubObjective(ref getItem);
|
||||
if (RequireAllItems)
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
subObjectivesCreated = true;
|
||||
if (subObjectives.Any(so => so is AIObjectiveGetItem getItem && getItem.IdentifiersOrTags.Contains(tag))) { continue; }
|
||||
int count = gearTags.Count(t => t == tag);
|
||||
AIObjectiveGetItem? getItem = null;
|
||||
TryAddSubObjective(ref getItem, () =>
|
||||
new AIObjectiveGetItem(character, tag, objectiveManager, Equip, CheckInventory && count <= 1)
|
||||
{
|
||||
AllowVariants = AllowVariants,
|
||||
Wear = Wear,
|
||||
TakeWholeStack = TakeWholeStack,
|
||||
AllowStealing = AllowStealing,
|
||||
ignoredIdentifiersOrTags = ignoredTags,
|
||||
CheckPathForEachItem = CheckPathForEachItem,
|
||||
RequireNonEmpty = RequireNonEmpty,
|
||||
ItemCount = count,
|
||||
SpeakIfFails = RequireAllItems
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
var item = getItem?.TargetItem;
|
||||
if (item?.IsOwnedBy(character) != null)
|
||||
{
|
||||
achievedItems.Add(item);
|
||||
}
|
||||
},
|
||||
onAbandon: () =>
|
||||
{
|
||||
var item = getItem?.TargetItem;
|
||||
if (item != null)
|
||||
{
|
||||
achievedItems.Remove(item);
|
||||
}
|
||||
RemoveSubObjective(ref getItem);
|
||||
if (RequireAllItems)
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
subObjectivesCreated = true;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
|
||||
+19
-12
@@ -223,23 +223,30 @@ namespace Barotrauma
|
||||
Hull targetHull = GetTargetHull();
|
||||
if (!IsFollowOrder)
|
||||
{
|
||||
// Abandon if going through unsafe paths or targeting unsafe hulls.
|
||||
bool isUnreachable = HumanAIController.UnreachableHulls.Contains(targetHull);
|
||||
if (!objectiveManager.CurrentObjective.IgnoreUnsafeHulls)
|
||||
{
|
||||
if (HumanAIController.UnsafeHulls.Contains(targetHull))
|
||||
// Wait orders check this so that the bot temporarily leaves the unsafe hull.
|
||||
// Non-orders (that are not set to ignore the unsafe hulls) abandon. In practice this means e.g. repair and clean up item subobjectives (of the looping parent objective).
|
||||
// Other orders are only abandoned if the hull is unreachable, because the path is invalid or not found at all.
|
||||
if (IsWaitOrder || !objectiveManager.HasOrders())
|
||||
{
|
||||
isUnreachable = true;
|
||||
HumanAIController.AskToRecalculateHullSafety(targetHull);
|
||||
}
|
||||
else if (PathSteering?.CurrentPath != null)
|
||||
{
|
||||
foreach (WayPoint wp in PathSteering.CurrentPath.Nodes)
|
||||
if (HumanAIController.UnsafeHulls.Contains(targetHull))
|
||||
{
|
||||
if (wp.CurrentHull == null) { continue; }
|
||||
if (HumanAIController.UnsafeHulls.Contains(wp.CurrentHull))
|
||||
isUnreachable = true;
|
||||
HumanAIController.AskToRecalculateHullSafety(targetHull);
|
||||
}
|
||||
else if (PathSteering?.CurrentPath != null)
|
||||
{
|
||||
foreach (WayPoint wp in PathSteering.CurrentPath.Nodes)
|
||||
{
|
||||
isUnreachable = true;
|
||||
HumanAIController.AskToRecalculateHullSafety(wp.CurrentHull);
|
||||
if (wp.CurrentHull == null) { continue; }
|
||||
if (HumanAIController.UnsafeHulls.Contains(wp.CurrentHull))
|
||||
{
|
||||
isUnreachable = true;
|
||||
HumanAIController.AskToRecalculateHullSafety(wp.CurrentHull);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -803,7 +810,7 @@ namespace Barotrauma
|
||||
|
||||
private void StopMovement()
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
SteeringManager?.Reset();
|
||||
if (Target != null)
|
||||
{
|
||||
character.AnimController.TargetDir = Target.WorldPosition.X > character.WorldPosition.X ? Direction.Right : Direction.Left;
|
||||
|
||||
+3
-1
@@ -382,7 +382,9 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.CurrentHull != currentHull || !item.HasTag("chair")) { continue; }
|
||||
if (item.CurrentHull != currentHull || !item.HasTag(Tags.ChairItem)) { continue; }
|
||||
//not possible in vanilla game, but a mod might have holdable/attachable chairs
|
||||
if (item.ParentInventory != null || item.body is { Enabled: true }) { continue; }
|
||||
var controller = item.GetComponent<Controller>();
|
||||
if (controller == null || controller.User != null) { continue; }
|
||||
item.TryInteract(character, forceSelectKey: true);
|
||||
|
||||
+4
-3
@@ -17,6 +17,8 @@ namespace Barotrauma
|
||||
set => throw new Exception("Trying to set the value for AIObjectiveLoadItem.IsLoop from: " + Environment.StackTrace.CleanupStackTrace());
|
||||
}
|
||||
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
private AIObjectiveLoadItems.ItemCondition TargetItemCondition { get; }
|
||||
private Item Container { get; }
|
||||
private ItemContainer ItemContainer { get; }
|
||||
@@ -161,8 +163,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
HandleNonAllowed();
|
||||
return Priority;
|
||||
}
|
||||
else if (!AIObjectiveLoadItems.IsValidTarget(Container, character, targetCondition: TargetItemCondition))
|
||||
@@ -299,7 +300,7 @@ namespace Barotrauma
|
||||
if (rootInventoryOwner is Character owner && owner != character) { return false; }
|
||||
if (rootInventoryOwner is Item parentItem)
|
||||
{
|
||||
if (parentItem.HasTag("donttakeitems")) { return false; }
|
||||
if (parentItem.HasTag(Tags.DontTakeItems)) { return false; }
|
||||
}
|
||||
if (!item.HasAccess(character)) { return false; }
|
||||
if (!character.HasItem(item) && !CanEquip(item, allowWearing: false)) { return false; }
|
||||
|
||||
+27
-32
@@ -44,6 +44,8 @@ namespace Barotrauma
|
||||
public override bool CanBeCompleted => true;
|
||||
public override bool AbandonWhenCannotCompleteSubjectives => false;
|
||||
public override bool AllowSubObjectiveSorting => true;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
public virtual bool InverseTargetEvaluation => false;
|
||||
protected virtual bool ResetWhenClearingIgnoreList => true;
|
||||
protected virtual bool ForceOrderPriority => true;
|
||||
@@ -117,51 +119,44 @@ namespace Barotrauma
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
HandleNonAllowed();
|
||||
return Priority;
|
||||
}
|
||||
if (character.LockHands)
|
||||
// Allow the target value to be more than 100.
|
||||
float targetValue = TargetEvaluation();
|
||||
if (InverseTargetEvaluation)
|
||||
{
|
||||
targetValue = 100 - targetValue;
|
||||
}
|
||||
var currentSubObjective = CurrentSubObjective;
|
||||
if (currentSubObjective != null && currentSubObjective.Priority > targetValue)
|
||||
{
|
||||
// If the priority is higher than the target value, let's just use it.
|
||||
// The priority calculation is more precise, but it takes into account things like distances,
|
||||
// so it's better not to use it if it's lower than the rougher targetValue.
|
||||
targetValue = currentSubObjective.Priority;
|
||||
}
|
||||
// If the target value is less than 1% of the max value, let's just treat it as zero.
|
||||
if (targetValue < 1)
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Allow the target value to be more than 100.
|
||||
float targetValue = TargetEvaluation();
|
||||
if (InverseTargetEvaluation)
|
||||
if (objectiveManager.IsOrder(this))
|
||||
{
|
||||
targetValue = 100 - targetValue;
|
||||
}
|
||||
var currentSubObjective = CurrentSubObjective;
|
||||
if (currentSubObjective != null && currentSubObjective.Priority > targetValue)
|
||||
{
|
||||
// If the priority is higher than the target value, let's just use it.
|
||||
// The priority calculation is more precise, but it takes into account things like distances,
|
||||
// so it's better not to use it if it's lower than the rougher targetValue.
|
||||
targetValue = currentSubObjective.Priority;
|
||||
}
|
||||
// If the target value is less than 1% of the max value, let's just treat it as zero.
|
||||
if (targetValue < 1)
|
||||
{
|
||||
Priority = 0;
|
||||
Priority = ForceOrderPriority ? objectiveManager.GetOrderPriority(this) : targetValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (objectiveManager.IsOrder(this))
|
||||
float max = AIObjectiveManager.LowestOrderPriority - 1;
|
||||
if (this is AIObjectiveRescueAll rescueObjective && rescueObjective.Targets.Contains(character))
|
||||
{
|
||||
Priority = ForceOrderPriority ? objectiveManager.GetOrderPriority(this) : targetValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
float max = AIObjectiveManager.LowestOrderPriority - 1;
|
||||
if (this is AIObjectiveRescueAll rescueObjective && rescueObjective.Targets.Contains(character))
|
||||
{
|
||||
// Allow higher prio
|
||||
max = AIObjectiveManager.EmergencyObjectivePriority;
|
||||
}
|
||||
float value = MathHelper.Clamp((CumulatedDevotion + (targetValue * PriorityModifier)) / 100, 0, 1);
|
||||
Priority = MathHelper.Lerp(0, max, value);
|
||||
// Allow higher prio
|
||||
max = AIObjectiveManager.EmergencyObjectivePriority;
|
||||
}
|
||||
float value = MathHelper.Clamp((CumulatedDevotion + (targetValue * PriorityModifier)) / 100, 0, 1);
|
||||
Priority = MathHelper.Lerp(0, max, value);
|
||||
}
|
||||
}
|
||||
return Priority;
|
||||
|
||||
+1
-1
@@ -530,7 +530,7 @@ namespace Barotrauma
|
||||
case "cleanupitems":
|
||||
if (order.TargetEntity is Item targetItem)
|
||||
{
|
||||
if (targetItem.HasTag("allowcleanup") && targetItem.ParentInventory == null && targetItem.OwnInventory != null)
|
||||
if (targetItem.HasTag(Tags.AllowCleanup) && targetItem.ParentInventory == null && targetItem.OwnInventory != null)
|
||||
{
|
||||
// Target all items inside the container
|
||||
newObjective = new AIObjectiveCleanupItems(character, this, targetItem.OwnInventory.AllItems, priorityModifier);
|
||||
|
||||
+3
-3
@@ -14,6 +14,7 @@ namespace Barotrauma
|
||||
public override bool AllowAutomaticItemUnequipping => true;
|
||||
public override bool AllowMultipleInstances => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
public override bool PrioritizeIfSubObjectivesActive => component != null && (component is Reactor || component is Turret);
|
||||
|
||||
private readonly ItemComponent component, controller;
|
||||
@@ -47,10 +48,9 @@ namespace Barotrauma
|
||||
protected override float GetPriority()
|
||||
{
|
||||
bool isOrder = objectiveManager.IsOrder(this);
|
||||
if (!IsAllowed || character.LockHands)
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = !isOrder;
|
||||
HandleNonAllowed();
|
||||
return Priority;
|
||||
}
|
||||
if (!isOrder && component.Item.ConditionPercentage <= 0)
|
||||
|
||||
+2
-7
@@ -13,6 +13,7 @@ namespace Barotrauma
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool KeepDivingGearOnAlsoWhenInactive => true;
|
||||
public override bool PrioritizeIfSubObjectivesActive => true;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
private AIObjectiveGetItem getSingleItemObjective;
|
||||
private AIObjectiveGetItems getAllItemsObjective;
|
||||
@@ -60,8 +61,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
HandleNonAllowed();
|
||||
return Priority;
|
||||
}
|
||||
Priority = objectiveManager.GetOrderPriority(this);
|
||||
@@ -75,11 +75,6 @@ namespace Barotrauma
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (character.LockHands)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (!subObjectivesCreated)
|
||||
{
|
||||
if (FindAllItems && targetItem == null)
|
||||
|
||||
+2
-1
@@ -12,6 +12,7 @@ namespace Barotrauma
|
||||
public override Identifier Identifier { get; set; } = "pump water".ToIdentifier();
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool AllowAutomaticItemUnequipping => true;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
private List<Pump> pumpList;
|
||||
|
||||
@@ -54,7 +55,7 @@ namespace Barotrauma
|
||||
var pump = item.GetComponent<Pump>();
|
||||
if (pump == null || pump.Item.Submarine == null || pump.Item.CurrentHull == null) { continue; }
|
||||
if (pump.Item.Submarine.TeamID != character.TeamID) { continue; }
|
||||
if (pump.Item.HasTag("ballast")) { continue; }
|
||||
if (pump.Item.HasTag(Tags.Ballast)) { continue; }
|
||||
pumpList.Add(pump);
|
||||
}
|
||||
}
|
||||
|
||||
+50
-34
@@ -10,8 +10,9 @@ namespace Barotrauma
|
||||
{
|
||||
public override Identifier Identifier { get; set; } = "repair item".ToIdentifier();
|
||||
|
||||
public override bool AllowInAnySub => true;
|
||||
public override bool AllowInFriendlySubs => true;
|
||||
public override bool KeepDivingGearOn => Item?.CurrentHull == null;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
public Item Item { get; private set; }
|
||||
|
||||
@@ -36,10 +37,13 @@ namespace Barotrauma
|
||||
|
||||
protected override float GetPriority()
|
||||
{
|
||||
if (!IsAllowed || Item.IgnoreByAI(character))
|
||||
if (!IsAllowed) { HandleNonAllowed(); }
|
||||
if (Item.IgnoreByAI(character))
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
}
|
||||
if (Abandon)
|
||||
{
|
||||
if (IsRepairing())
|
||||
{
|
||||
Item.Repairables.ForEach(r => r.StopRepairing(character));
|
||||
@@ -136,32 +140,35 @@ namespace Barotrauma
|
||||
}
|
||||
if (repairTool != null)
|
||||
{
|
||||
if (repairTool.Item.OwnInventory == null)
|
||||
if (repairTool.requiredItems.TryGetValue(RelatedItem.RelationType.Contained, out var requiredItems))
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError($"{character.Name}: AIObjectiveRepairItem failed - the item \"" + repairTool + "\" has no proper inventory");
|
||||
#endif
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
RelatedItem item = null;
|
||||
Item fuel = null;
|
||||
foreach (RelatedItem requiredItem in repairTool.requiredItems[RelatedItem.RelationType.Contained])
|
||||
{
|
||||
item = requiredItem;
|
||||
fuel = repairTool.Item.OwnInventory.AllItems.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, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
if (repairTool.Item.OwnInventory == null)
|
||||
{
|
||||
RemoveExisting = true
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref refuelObjective),
|
||||
onAbandon: () => Abandon = true);
|
||||
return;
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError($"{character.Name}: AIObjectiveRepairItem failed - the item \"{repairTool}\" has no proper inventory.");
|
||||
#endif
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
RelatedItem item = null;
|
||||
Item fuel = null;
|
||||
foreach (RelatedItem requiredItem in requiredItems)
|
||||
{
|
||||
item = requiredItem;
|
||||
fuel = repairTool.Item.OwnInventory.AllItems.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, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
RemoveExisting = true
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref refuelObjective),
|
||||
onAbandon: () => Abandon = true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (character.CanInteractWith(Item, out _, checkLinked: false))
|
||||
@@ -170,10 +177,8 @@ namespace Barotrauma
|
||||
if (waitTimer < WaitTimeBeforeRepair) { return; }
|
||||
|
||||
HumanAIController.FaceTarget(Item);
|
||||
if (repairTool != null)
|
||||
{
|
||||
OperateRepairTool(deltaTime);
|
||||
}
|
||||
|
||||
bool repairThroughRepairInterface = false;
|
||||
foreach (Repairable repairable in Item.Repairables)
|
||||
{
|
||||
if (repairable.CurrentFixer != null && repairable.CurrentFixer != character)
|
||||
@@ -185,10 +190,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (character.SelectedItem != Item)
|
||||
{
|
||||
if (Item.TryInteract(character, ignoreRequiredItems: true, forceUseKey: true) ||
|
||||
Item.TryInteract(character, ignoreRequiredItems: true, forceSelectKey: true))
|
||||
if (Item.TryInteract(character, forceUseKey: true) ||
|
||||
Item.TryInteract(character, forceSelectKey: true))
|
||||
{
|
||||
character.SelectedItem = Item;
|
||||
repairThroughRepairInterface = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -209,8 +215,17 @@ namespace Barotrauma
|
||||
{
|
||||
repairable.StartRepairing(character, Repairable.FixActions.Repair);
|
||||
}
|
||||
else
|
||||
{
|
||||
repairThroughRepairInterface = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!repairThroughRepairInterface && repairTool != null && !Abandon)
|
||||
{
|
||||
OperateRepairTool(deltaTime);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -222,7 +237,8 @@ namespace Barotrauma
|
||||
{
|
||||
var objective = new AIObjectiveGoTo(Item, character, objectiveManager)
|
||||
{
|
||||
TargetName = Item.Name
|
||||
TargetName = Item.Name,
|
||||
SpeakCannotReachCondition = () => isPriority
|
||||
};
|
||||
if (repairTool != null)
|
||||
{
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ namespace Barotrauma
|
||||
public Item PrioritizedItem { get; private set; }
|
||||
|
||||
public override bool AllowMultipleInstances => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
public override bool AllowInFriendlySubs => true;
|
||||
|
||||
public readonly static float RequiredSuccessFactor = 0.4f;
|
||||
|
||||
|
||||
+67
-66
@@ -16,12 +16,13 @@ namespace Barotrauma
|
||||
|
||||
public override bool AllowOutsideSubmarine => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
const float TreatmentDelay = 0.5f;
|
||||
|
||||
const float CloseEnoughToTreat = 100.0f;
|
||||
|
||||
private readonly Character targetCharacter;
|
||||
public readonly Character Target;
|
||||
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private AIObjectiveContainItem replaceOxygenObjective;
|
||||
@@ -44,7 +45,7 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
this.targetCharacter = targetCharacter;
|
||||
Target = targetCharacter;
|
||||
}
|
||||
|
||||
protected override void OnAbandon()
|
||||
@@ -61,55 +62,55 @@ namespace Barotrauma
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (character.LockHands || targetCharacter == null || targetCharacter.Removed || targetCharacter.IsDead)
|
||||
if (Target == null || Target.Removed || Target.IsDead)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
var otherRescuer = targetCharacter.SelectedBy;
|
||||
var otherRescuer = Target.SelectedBy;
|
||||
if (otherRescuer != null && otherRescuer != character)
|
||||
{
|
||||
// Someone else is rescuing/holding the target.
|
||||
Abandon = otherRescuer.IsPlayer || character.GetSkillLevel("medical") < otherRescuer.GetSkillLevel("medical");
|
||||
return;
|
||||
}
|
||||
if (targetCharacter != character)
|
||||
if (Target != character)
|
||||
{
|
||||
if (targetCharacter.IsIncapacitated)
|
||||
if (Target.IsIncapacitated)
|
||||
{
|
||||
// Check if the character needs more oxygen
|
||||
if (!ignoreOxygen && character.SelectedCharacter == targetCharacter || character.CanInteractWith(targetCharacter))
|
||||
if (!ignoreOxygen && character.SelectedCharacter == Target || character.CanInteractWith(Target))
|
||||
{
|
||||
// Replace empty oxygen and welding fuel.
|
||||
if (HumanAIController.HasItem(targetCharacter, AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR, out IEnumerable<Item> suits, requireEquipped: true))
|
||||
if (HumanAIController.HasItem(Target, Tags.HeavyDivingGear, out IEnumerable<Item> suits, requireEquipped: true))
|
||||
{
|
||||
Item suit = suits.FirstOrDefault();
|
||||
if (suit != null)
|
||||
{
|
||||
AIController.UnequipEmptyItems(character, suit);
|
||||
AIController.UnequipContainedItems(character, suit, it => it.HasTag("weldingfuel"));
|
||||
AIController.UnequipContainedItems(character, suit, it => it.HasTag(Tags.WeldingFuel));
|
||||
}
|
||||
}
|
||||
else if (HumanAIController.HasItem(targetCharacter, AIObjectiveFindDivingGear.LIGHT_DIVING_GEAR, out IEnumerable<Item> masks, requireEquipped: true))
|
||||
else if (HumanAIController.HasItem(Target, Tags.LightDivingGear, out IEnumerable<Item> masks, requireEquipped: true))
|
||||
{
|
||||
Item mask = masks.FirstOrDefault();
|
||||
if (mask != null)
|
||||
{
|
||||
AIController.UnequipEmptyItems(character, mask);
|
||||
AIController.UnequipContainedItems(character, mask, it => it.HasTag("weldingfuel"));
|
||||
AIController.UnequipContainedItems(character, mask, it => it.HasTag(Tags.WeldingFuel));
|
||||
}
|
||||
}
|
||||
bool ShouldRemoveDivingSuit() => targetCharacter.OxygenAvailable < CharacterHealth.InsufficientOxygenThreshold && targetCharacter.CurrentHull?.LethalPressure <= 0;
|
||||
bool ShouldRemoveDivingSuit() => Target.OxygenAvailable < CharacterHealth.InsufficientOxygenThreshold && Target.CurrentHull?.LethalPressure <= 0;
|
||||
if (ShouldRemoveDivingSuit())
|
||||
{
|
||||
suits.ForEach(suit => suit.Drop(character));
|
||||
}
|
||||
else if (suits.Any() && suits.None(s => s.OwnInventory?.AllItems != null && s.OwnInventory.AllItems.Any(it => it.HasTag(AIObjectiveFindDivingGear.OXYGEN_SOURCE) && it.ConditionPercentage > 0)))
|
||||
else if (suits.Any() && suits.None(s => s.OwnInventory?.AllItems != null && s.OwnInventory.AllItems.Any(it => it.HasTag(Tags.OxygenSource) && it.ConditionPercentage > 0)))
|
||||
{
|
||||
// The target has a suit equipped with an empty oxygen tank.
|
||||
// Can't remove the suit, because the target needs it.
|
||||
// If we happen to have an extra oxygen tank in the inventory, let's swap it.
|
||||
Item spareOxygenTank = FindOxygenTank(targetCharacter) ?? FindOxygenTank(character);
|
||||
Item spareOxygenTank = FindOxygenTank(Target) ?? FindOxygenTank(character);
|
||||
if (spareOxygenTank != null)
|
||||
{
|
||||
Item suit = suits.FirstOrDefault();
|
||||
@@ -133,36 +134,36 @@ namespace Barotrauma
|
||||
|
||||
Item FindOxygenTank(Character c) =>
|
||||
c.Inventory.FindItem(i =>
|
||||
i.HasTag(AIObjectiveFindDivingGear.OXYGEN_SOURCE) &&
|
||||
i.HasTag(Tags.OxygenSource) &&
|
||||
i.ConditionPercentage > 1 &&
|
||||
i.FindParentInventory(inv => inv.Owner is Item otherItem && otherItem.HasTag("diving")) == null,
|
||||
i.FindParentInventory(inv => inv.Owner is Item otherItem && otherItem.HasTag(Tags.DivingGear)) == null,
|
||||
recursive: true);
|
||||
}
|
||||
}
|
||||
if (character.Submarine != null && targetCharacter.CurrentHull != null)
|
||||
if (character.Submarine != null && Target.CurrentHull != null)
|
||||
{
|
||||
if (HumanAIController.GetHullSafety(targetCharacter.CurrentHull, targetCharacter) < HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
if (HumanAIController.GetHullSafety(Target.CurrentHull, Target) < HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
// Incapacitated target is not in a safe place -> Move to a safe place first
|
||||
if (character.SelectedCharacter != targetCharacter)
|
||||
if (character.SelectedCharacter != Target)
|
||||
{
|
||||
if (HumanAIController.VisibleHulls.Contains(targetCharacter.CurrentHull) && targetCharacter.CurrentHull.DisplayName != null)
|
||||
if (HumanAIController.VisibleHulls.Contains(Target.CurrentHull) && Target.CurrentHull.DisplayName != null)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariables("DialogFoundUnconsciousTarget",
|
||||
("[targetname]", targetCharacter.Name, FormatCapitals.No),
|
||||
("[roomname]", targetCharacter.CurrentHull.DisplayName, FormatCapitals.Yes)).Value,
|
||||
null, 1.0f, $"foundunconscioustarget{targetCharacter.Name}".ToIdentifier(), 60.0f);
|
||||
("[targetname]", Target.Name, FormatCapitals.No),
|
||||
("[roomname]", Target.CurrentHull.DisplayName, FormatCapitals.Yes)).Value,
|
||||
null, 1.0f, $"foundunconscioustarget{Target.Name}".ToIdentifier(), 60.0f);
|
||||
}
|
||||
// Go to the target and select it
|
||||
if (!character.CanInteractWith(targetCharacter))
|
||||
if (!character.CanInteractWith(Target))
|
||||
{
|
||||
RemoveSubObjective(ref replaceOxygenObjective);
|
||||
RemoveSubObjective(ref goToObjective);
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager)
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(Target, character, objectiveManager)
|
||||
{
|
||||
CloseEnough = CloseEnoughToTreat,
|
||||
DialogueIdentifier = "dialogcannotreachpatient".ToIdentifier(),
|
||||
TargetName = targetCharacter.DisplayName
|
||||
TargetName = Target.DisplayName
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref goToObjective),
|
||||
onAbandon: () =>
|
||||
@@ -173,7 +174,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
character.SelectCharacter(targetCharacter);
|
||||
character.SelectCharacter(Target);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -213,16 +214,16 @@ namespace Barotrauma
|
||||
|
||||
if (subObjectives.Any()) { return; }
|
||||
|
||||
if (targetCharacter != character && !character.CanInteractWith(targetCharacter))
|
||||
if (Target != character && !character.CanInteractWith(Target))
|
||||
{
|
||||
RemoveSubObjective(ref replaceOxygenObjective);
|
||||
RemoveSubObjective(ref goToObjective);
|
||||
// Go to the target and select it
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager)
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(Target, character, objectiveManager)
|
||||
{
|
||||
CloseEnough = CloseEnoughToTreat,
|
||||
DialogueIdentifier = "dialogcannotreachpatient".ToIdentifier(),
|
||||
TargetName = targetCharacter.DisplayName
|
||||
TargetName = Target.DisplayName
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref goToObjective),
|
||||
onAbandon: () =>
|
||||
@@ -234,14 +235,14 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
// We can start applying treatment
|
||||
if (character != targetCharacter && character.SelectedCharacter != targetCharacter)
|
||||
if (character != Target && character.SelectedCharacter != Target)
|
||||
{
|
||||
if (targetCharacter.CurrentHull?.DisplayName != null)
|
||||
if (Target.CurrentHull?.DisplayName != null)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariables("DialogFoundWoundedTarget",
|
||||
("[targetname]", targetCharacter.Name, FormatCapitals.No),
|
||||
("[roomname]", targetCharacter.CurrentHull.DisplayName, FormatCapitals.Yes)).Value,
|
||||
null, 1.0f, $"foundwoundedtarget{targetCharacter.Name}".ToIdentifier(), 60.0f);
|
||||
("[targetname]", Target.Name, FormatCapitals.No),
|
||||
("[roomname]", Target.CurrentHull.DisplayName, FormatCapitals.Yes)).Value,
|
||||
null, 1.0f, $"foundwoundedtarget{Target.Name}".ToIdentifier(), 60.0f);
|
||||
}
|
||||
}
|
||||
GiveTreatment(deltaTime);
|
||||
@@ -253,7 +254,7 @@ namespace Barotrauma
|
||||
private readonly Dictionary<Identifier, float> currentTreatmentSuitabilities = new Dictionary<Identifier, float>();
|
||||
private void GiveTreatment(float deltaTime)
|
||||
{
|
||||
if (targetCharacter == null)
|
||||
if (Target == null)
|
||||
{
|
||||
string errorMsg = $"{character.Name}: Attempted to update a Rescue objective with no target!";
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
@@ -263,10 +264,10 @@ namespace Barotrauma
|
||||
|
||||
SteeringManager.Reset();
|
||||
|
||||
if (!targetCharacter.IsPlayer)
|
||||
if (!Target.IsPlayer)
|
||||
{
|
||||
// If the target is a bot, don't let it move
|
||||
targetCharacter.AIController?.SteeringManager?.Reset();
|
||||
Target.AIController?.SteeringManager?.Reset();
|
||||
}
|
||||
if (treatmentTimer > 0.0f)
|
||||
{
|
||||
@@ -275,28 +276,28 @@ namespace Barotrauma
|
||||
}
|
||||
treatmentTimer = TreatmentDelay;
|
||||
|
||||
float cprSuitability = targetCharacter.Oxygen < 0.0f ? -targetCharacter.Oxygen * 100.0f : 0.0f;
|
||||
float cprSuitability = Target.Oxygen < 0.0f ? -Target.Oxygen * 100.0f : 0.0f;
|
||||
|
||||
//find which treatments are the most suitable to treat the character's current condition
|
||||
targetCharacter.CharacterHealth.GetSuitableTreatments(currentTreatmentSuitabilities, user: character, normalize: false, predictFutureDuration: 10.0f);
|
||||
Target.CharacterHealth.GetSuitableTreatments(currentTreatmentSuitabilities, user: character, normalize: false, predictFutureDuration: 10.0f);
|
||||
|
||||
//check if we already have a suitable treatment for any of the afflictions
|
||||
foreach (Affliction affliction in GetSortedAfflictions(targetCharacter))
|
||||
foreach (Affliction affliction in GetSortedAfflictions(Target))
|
||||
{
|
||||
if (affliction == null) { throw new Exception("Affliction was null"); }
|
||||
if (affliction.Prefab == null) { throw new Exception("Affliction prefab was null"); }
|
||||
float bestSuitability = 0.0f;
|
||||
Item bestItem = null;
|
||||
foreach (KeyValuePair<Identifier, float> treatmentSuitability in affliction.Prefab.TreatmentSuitability)
|
||||
foreach (KeyValuePair<Identifier, float> treatmentSuitability in affliction.Prefab.TreatmentSuitabilities)
|
||||
{
|
||||
if (currentTreatmentSuitabilities.ContainsKey(treatmentSuitability.Key) &&
|
||||
currentTreatmentSuitabilities[treatmentSuitability.Key] > bestSuitability)
|
||||
{
|
||||
Item matchingItem = character.Inventory.FindItemByIdentifier(treatmentSuitability.Key, true);
|
||||
//allow taking items from the target's inventory too if the target is unconscious
|
||||
if (matchingItem == null && targetCharacter.IsIncapacitated)
|
||||
if (matchingItem == null && Target.IsIncapacitated)
|
||||
{
|
||||
matchingItem ??= targetCharacter.Inventory?.FindItemByIdentifier(treatmentSuitability.Key, true);
|
||||
matchingItem ??= Target.Inventory?.FindItemByIdentifier(treatmentSuitability.Key, true);
|
||||
}
|
||||
if (matchingItem != null)
|
||||
{
|
||||
@@ -307,7 +308,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (bestItem != null)
|
||||
{
|
||||
if (targetCharacter != character) { character.SelectCharacter(targetCharacter); }
|
||||
if (Target != character) { character.SelectCharacter(Target); }
|
||||
ApplyTreatment(affliction, bestItem);
|
||||
//wait a bit longer after applying a treatment to wait for potential side-effects to manifest
|
||||
treatmentTimer = TreatmentDelay * 4;
|
||||
@@ -370,12 +371,12 @@ namespace Barotrauma
|
||||
("[treatment1]", itemListStr),
|
||||
("[treatment2]", itemNameList.Last()));
|
||||
}
|
||||
if (targetCharacter != character && character.IsOnPlayerTeam)
|
||||
if (Target != character && character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariables("DialogListRequiredTreatments",
|
||||
("[targetname]", targetCharacter.Name, FormatCapitals.No),
|
||||
("[targetname]", Target.Name, FormatCapitals.No),
|
||||
("[treatmentlist]", itemListStr, FormatCapitals.Yes)).Value,
|
||||
null, 2.0f, $"listrequiredtreatments{targetCharacter.Name}".ToIdentifier(), 60.0f);
|
||||
null, 2.0f, $"listrequiredtreatments{Target.Name}".ToIdentifier(), 60.0f);
|
||||
}
|
||||
RemoveSubObjective(ref getItemObjective);
|
||||
TryAddSubObjective(ref getItemObjective,
|
||||
@@ -397,18 +398,18 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!targetCharacter.IsUnconscious)
|
||||
else if (!Target.IsUnconscious)
|
||||
{
|
||||
Abandon = true;
|
||||
//no suitable treatments found, not inside our own sub (= can't search for more treatments), the target isn't unconscious (= can't give CPR)
|
||||
SpeakCannotTreat();
|
||||
return;
|
||||
}
|
||||
if (character != targetCharacter)
|
||||
if (character != Target)
|
||||
{
|
||||
if (cprSuitability > 0.0f)
|
||||
{
|
||||
character.SelectCharacter(targetCharacter);
|
||||
character.SelectCharacter(Target);
|
||||
character.AnimController.Anim = AnimController.Animation.CPR;
|
||||
performedCpr = true;
|
||||
}
|
||||
@@ -421,40 +422,40 @@ namespace Barotrauma
|
||||
|
||||
private void SpeakCannotTreat()
|
||||
{
|
||||
LocalizedString msg = character == targetCharacter ?
|
||||
LocalizedString msg = character == Target ?
|
||||
TextManager.Get("dialogcannottreatself") :
|
||||
TextManager.GetWithVariable("dialogcannottreatpatient", "[name]", targetCharacter.DisplayName, FormatCapitals.No);
|
||||
TextManager.GetWithVariable("dialogcannottreatpatient", "[name]", Target.DisplayName, FormatCapitals.No);
|
||||
character.Speak(msg.Value, identifier: "cannottreatpatient".ToIdentifier(), minDurationBetweenSimilar: 20.0f);
|
||||
}
|
||||
|
||||
private void ApplyTreatment(Affliction affliction, Item item)
|
||||
{
|
||||
item.ApplyTreatment(character, targetCharacter, targetCharacter.CharacterHealth.GetAfflictionLimb(affliction));
|
||||
item.ApplyTreatment(character, Target, Target.CharacterHealth.GetAfflictionLimb(affliction));
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific()
|
||||
{
|
||||
bool isCompleted = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter);
|
||||
if (isCompleted && targetCharacter != character && character.IsOnPlayerTeam)
|
||||
bool isCompleted = AIObjectiveRescueAll.GetVitalityFactor(Target) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, Target);
|
||||
if (isCompleted && Target != character && character.IsOnPlayerTeam)
|
||||
{
|
||||
string textTag = performedCpr ? "DialogTargetResuscitated" : "DialogTargetHealed";
|
||||
string message = TextManager.GetWithVariable(textTag, "[targetname]", targetCharacter.Name)?.Value;
|
||||
character.Speak(message, delay: 1.0f, identifier: $"targethealed{targetCharacter.Name}".ToIdentifier(), minDurationBetweenSimilar: 60.0f);
|
||||
string message = TextManager.GetWithVariable(textTag, "[targetname]", Target.Name)?.Value;
|
||||
character.Speak(message, delay: 1.0f, identifier: $"targethealed{Target.Name}".ToIdentifier(), minDurationBetweenSimilar: 60.0f);
|
||||
}
|
||||
return isCompleted;
|
||||
}
|
||||
|
||||
protected override float GetPriority()
|
||||
{
|
||||
if (!IsAllowed || targetCharacter == null)
|
||||
if (Target == null) { Abandon = true; }
|
||||
if (!IsAllowed) { HandleNonAllowed(); }
|
||||
if (Abandon)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
return Priority;
|
||||
}
|
||||
if (character.CurrentHull != null)
|
||||
{
|
||||
if (Character.CharacterList.Any(c => c.CurrentHull == targetCharacter.CurrentHull && !HumanAIController.IsFriendly(character, c) && HumanAIController.IsActive(c)))
|
||||
if (Character.CharacterList.Any(c => c.CurrentHull == Target.CurrentHull && !HumanAIController.IsFriendly(character, c) && HumanAIController.IsActive(c)))
|
||||
{
|
||||
// Don't go into rooms that have enemies
|
||||
Priority = 0;
|
||||
@@ -462,18 +463,18 @@ namespace Barotrauma
|
||||
return Priority;
|
||||
}
|
||||
}
|
||||
float horizontalDistance = Math.Abs(character.WorldPosition.X - targetCharacter.WorldPosition.X);
|
||||
float verticalDistance = Math.Abs(character.WorldPosition.Y - targetCharacter.WorldPosition.Y);
|
||||
float horizontalDistance = Math.Abs(character.WorldPosition.X - Target.WorldPosition.X);
|
||||
float verticalDistance = Math.Abs(character.WorldPosition.Y - Target.WorldPosition.Y);
|
||||
if (character.Submarine?.Info is { IsRuin: false })
|
||||
{
|
||||
verticalDistance *= 2;
|
||||
}
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, horizontalDistance + verticalDistance));
|
||||
if (character.CurrentHull != null && targetCharacter.CurrentHull == character.CurrentHull)
|
||||
if (character.CurrentHull != null && Target.CurrentHull == character.CurrentHull)
|
||||
{
|
||||
distanceFactor = 1;
|
||||
}
|
||||
float vitalityFactor = 1 - AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) / 100;
|
||||
float vitalityFactor = 1 - AIObjectiveRescueAll.GetVitalityFactor(Target) / 100;
|
||||
float devotion = CumulatedDevotion / 100;
|
||||
Priority = MathHelper.Lerp(0, AIObjectiveManager.EmergencyObjectivePriority, MathHelper.Clamp(devotion + (vitalityFactor * distanceFactor * PriorityModifier), 0, 1));
|
||||
return Priority;
|
||||
|
||||
+55
-45
@@ -34,24 +34,29 @@ namespace Barotrauma
|
||||
|
||||
protected override bool Filter(Character target)
|
||||
{
|
||||
if (!IsValidTarget(target, character, requireTreatableAfflictions: false)) { return false; }
|
||||
if (GetTreatableAfflictions(target).Any())
|
||||
if (!IsValidTarget(target, character, out bool ignoredasMinorWounds))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//the target might be at a low enough health to be considered a valid target,
|
||||
//but if all afflictions are below treatment thresholds, the bot won't (and shouldn't) treat them
|
||||
// -> make the bot speak to make it clear the bot intentionally ignores very minor injuries
|
||||
if (!charactersWithMinorInjuries.Contains(character))
|
||||
if (ignoredasMinorWounds)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("dialogignoreminorinjuries", "[targetname]", target.Name).Value,
|
||||
null, 1.0f, $"notreatableafflictions{target.Name}".ToIdentifier(), 10.0f);
|
||||
charactersWithMinorInjuries.Add(character);
|
||||
//the target might be at a low enough health to be considered a valid target,
|
||||
//but if all afflictions are below treatment thresholds, the bot won't (and shouldn't) treat them
|
||||
// -> make the bot speak to make it clear the bot intentionally ignores very minor injuries
|
||||
if (character.IsOnPlayerTeam && target != character && !charactersWithMinorInjuries.Contains(target))
|
||||
{
|
||||
// But only speak about targets when we are not already actively treating, in which case we should be speaking about the current target.
|
||||
if (objectiveManager.GetFirstActiveObjective<AIObjectiveRescue>() == null)
|
||||
{
|
||||
charactersWithMinorInjuries.Add(target);
|
||||
character.Speak(TextManager.GetWithVariable("dialogignoreminorinjuries", "[targetname]", target.Name).Value,
|
||||
delay: 1.0f,
|
||||
identifier: $"notreatableafflictions{target.Name}".ToIdentifier(),
|
||||
minDurationBetweenSimilar: 10.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override IEnumerable<Character> GetList() => Character.CharacterList;
|
||||
@@ -103,12 +108,13 @@ namespace Barotrauma
|
||||
return Math.Clamp(vitality, 0, 100);
|
||||
}
|
||||
|
||||
public static IEnumerable<Affliction> GetTreatableAfflictions(Character character, bool ignoreTreatmentThreshold = false)
|
||||
public static IEnumerable<Affliction> GetTreatableAfflictions(Character character, bool ignoreTreatmentThreshold)
|
||||
{
|
||||
var allAfflictions = character.CharacterHealth.GetAllAfflictions();
|
||||
foreach (Affliction affliction in allAfflictions)
|
||||
{
|
||||
if (affliction.Prefab.IsBuff) { continue; }
|
||||
if (!affliction.Prefab.HasTreatments) { continue; }
|
||||
if (!ignoreTreatmentThreshold)
|
||||
{
|
||||
//other afflictions of the same type increase the "treatability"
|
||||
@@ -116,7 +122,6 @@ namespace Barotrauma
|
||||
float totalAfflictionStrength = character.CharacterHealth.GetTotalAdjustedAfflictionStrength(affliction);
|
||||
if (totalAfflictionStrength < affliction.Prefab.TreatmentThreshold) { continue; }
|
||||
}
|
||||
if (affliction.Prefab.TreatmentSuitability.None(kvp => kvp.Value > 0)) { continue; }
|
||||
if (allAfflictions.Any(otherAffliction => affliction.Prefab.IgnoreTreatmentIfAfflictedBy.Contains(otherAffliction.Identifier))) { continue; }
|
||||
yield return affliction;
|
||||
}
|
||||
@@ -128,39 +133,50 @@ namespace Barotrauma
|
||||
protected override void OnObjectiveCompleted(AIObjective objective, Character target)
|
||||
=> HumanAIController.RemoveTargets<AIObjectiveRescueAll, Character>(character, target);
|
||||
|
||||
public static bool IsValidTarget(Character target, Character character, bool requireTreatableAfflictions = true)
|
||||
public static bool IsValidTarget(Character target, Character character, out bool ignoredAsMinorWounds)
|
||||
{
|
||||
ignoredAsMinorWounds = false;
|
||||
if (target == null || target.IsDead || target.Removed) { return false; }
|
||||
if (target.IsInstigator) { return false; }
|
||||
if (target.IsPet) { return false; }
|
||||
if (!HumanAIController.IsFriendly(character, target, onlySameTeam: true)) { return false; }
|
||||
bool isBelowTreatmentThreshold;
|
||||
float vitalityFactor;
|
||||
if (character.AIController is HumanAIController humanAI)
|
||||
{
|
||||
if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!humanAI.ObjectiveManager.HasOrder<AIObjectiveRescueAll>())
|
||||
{
|
||||
if (!character.IsMedic && target != character)
|
||||
{
|
||||
// Don't allow to treat others autonomously, unless we are a medic
|
||||
return false;
|
||||
}
|
||||
// Ignore unsafe hulls, unless ordered
|
||||
if (humanAI.UnsafeHulls.Contains(target.CurrentHull))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (requireTreatableAfflictions && GetTreatableAfflictions(target).None())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!IsValidTargetForAI(target, humanAI)) { return false; }
|
||||
vitalityFactor = GetVitalityFactor(target);
|
||||
isBelowTreatmentThreshold = vitalityFactor < GetVitalityThreshold(humanAI.ObjectiveManager, character, target);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GetVitalityFactor(target) >= vitalityThreshold) { return false; }
|
||||
vitalityFactor = GetVitalityFactor(target);
|
||||
isBelowTreatmentThreshold = vitalityFactor < vitalityThreshold;
|
||||
}
|
||||
bool hasTreatableAfflictions = GetTreatableAfflictions(target, ignoreTreatmentThreshold: false).Any();
|
||||
bool isValidTarget = isBelowTreatmentThreshold && hasTreatableAfflictions;
|
||||
if (!isValidTarget)
|
||||
{
|
||||
ignoredAsMinorWounds = hasTreatableAfflictions || vitalityFactor < 100;
|
||||
}
|
||||
return isValidTarget;
|
||||
}
|
||||
|
||||
private static bool IsValidTargetForAI(Character target, HumanAIController humanAI)
|
||||
{
|
||||
Character character = humanAI.Character;
|
||||
if (!humanAI.ObjectiveManager.HasOrder<AIObjectiveRescueAll>())
|
||||
{
|
||||
if (!character.IsMedic && target != character)
|
||||
{
|
||||
// Don't allow to treat others autonomously, unless we are a medic
|
||||
return false;
|
||||
}
|
||||
// Ignore unsafe hulls, unless ordered
|
||||
if (humanAI.UnsafeHulls.Contains(target.CurrentHull))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
@@ -189,11 +205,5 @@ namespace Barotrauma
|
||||
}
|
||||
return character.GetDamageDoneByAttacker(target) <= 0;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
charactersWithMinorInjuries.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-6
@@ -20,7 +20,7 @@ namespace Barotrauma
|
||||
ReturnTarget = GetReturnTarget(Submarine.MainSubs) ?? GetReturnTarget(Submarine.Loaded);
|
||||
if (ReturnTarget == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error with a Return objective: no suitable return target found");
|
||||
DebugConsole.AddSafeError("Error with a Return objective: no suitable return target found");
|
||||
Abandon = true;
|
||||
}
|
||||
|
||||
@@ -47,8 +47,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Consider if this needs to be addressed
|
||||
Priority = 0;
|
||||
Priority = AIObjectiveManager.LowestOrderPriority - 1;
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
@@ -91,7 +90,7 @@ namespace Barotrauma
|
||||
targetHull = d.Item.CurrentHull;
|
||||
break;
|
||||
}
|
||||
if (targetHull != null && !targetHull.IsTaggedAirlock())
|
||||
if (targetHull != null && !targetHull.IsAirlock)
|
||||
{
|
||||
// Target the closest airlock
|
||||
float closestDist = 0;
|
||||
@@ -99,7 +98,7 @@ namespace Barotrauma
|
||||
foreach (Hull hull in Hull.HullList)
|
||||
{
|
||||
if (hull.Submarine != targetHull.Submarine) { continue; }
|
||||
if (!hull.IsTaggedAirlock()) { continue; }
|
||||
if (!hull.IsAirlock) { continue; }
|
||||
float dist = Vector2.DistanceSquared(targetHull.Position, hull.Position);
|
||||
if (airlock == null || closestDist <= 0 || dist < closestDist)
|
||||
{
|
||||
@@ -146,7 +145,7 @@ namespace Barotrauma
|
||||
bool targetIsAirlock = false;
|
||||
foreach (var hull in ReturnTarget.GetHulls(false))
|
||||
{
|
||||
bool hullIsAirlock = hull.IsTaggedAirlock();
|
||||
bool hullIsAirlock = hull.IsAirlock;
|
||||
if(hullIsAirlock || (!targetIsAirlock && hull.LeadsOutside(character)))
|
||||
{
|
||||
float distanceSquared = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition);
|
||||
|
||||
Reference in New Issue
Block a user