Build 0.21.6.0 (1.0 pre-patch)

This commit is contained in:
Regalis11
2023-01-31 18:08:26 +02:00
parent e1c04bc31d
commit cf9ecd35b3
231 changed files with 4479 additions and 2276 deletions
@@ -206,13 +206,19 @@ namespace Barotrauma
private set;
} = new HashSet<Submarine>();
public bool IsTargetingPlayerTeam => IsTargetInPlayerTeam(SelectedAiTarget);
public static bool IsTargetBeingChasedBy(Character target, Character character)
=> character?.AIController is EnemyAIController enemyAI && enemyAI.SelectedAiTarget?.Entity == target && (enemyAI.State == AIState.Attack || enemyAI.State == AIState.Aggressive);
public bool IsBeingChasedBy(Character c) => IsTargetBeingChasedBy(Character, c);
private bool IsBeingChased => IsBeingChasedBy(SelectedAiTarget?.Entity as Character);
private bool IsTargetInPlayerTeam(AITarget target) => target?.Entity?.Submarine != null && target.Entity.Submarine.Info.IsPlayer || target?.Entity is Character targetCharacter && targetCharacter.IsOnPlayerTeam;
private static bool IsTargetInPlayerTeam(AITarget target) => target?.Entity?.Submarine != null && target.Entity.Submarine.Info.IsPlayer || target?.Entity is Character targetCharacter && targetCharacter.IsOnPlayerTeam;
private bool IsAttackingOwner(Character other) =>
PetBehavior != null && PetBehavior.Owner != null &&
!other.IsUnconscious && !other.IsArrested &&
other.AIController is HumanAIController humanAI &&
humanAI.ObjectiveManager.CurrentObjective is AIObjectiveCombat combat &&
combat.Enemy != null && combat.Enemy == PetBehavior.Owner;
private bool reverse;
public bool Reverse
@@ -355,6 +361,10 @@ namespace Barotrauma
{
targetingTag = "owner";
}
else if (PetBehavior != null && (!Character.IsOnFriendlyTeam(targetCharacter) || IsAttackingOwner(targetCharacter)))
{
targetingTag = "hostile";
}
else if (AIParams.TryGetTarget(targetCharacter, out CharacterParams.TargetParams tP))
{
targetingTag = tP.Tag;
@@ -365,7 +375,7 @@ namespace Barotrauma
{
targetingTag = "husk";
}
else if (!Character.IsFriendly(targetCharacter))
else if (!Character.IsSameSpeciesOrGroup(targetCharacter))
{
if (enemy.CombatStrength > CombatStrength)
{
@@ -677,22 +687,22 @@ namespace Barotrauma
{
if (SelectedAiTarget.Entity is Character targetCharacter)
{
bool IsValid(Character.Attacker a)
bool ShouldRetaliate(Character.Attacker a)
{
Character c = a.Character;
if (c.IsDead || c.Removed) { return false; }
if (!Character.IsFriendly(c)) { return true; }
if (!c.IsPlayer) { return false; }
// Only apply the threshold to players
return a.Damage >= selectedTargetingParams.Threshold;
if (c == null || c.IsUnconscious || c.Removed) { return false; }
// Can't target characters of same species/group because that would make us hostile to all friendly characters in the same species/group.
if (Character.IsSameSpeciesOrGroup(c)) { return false; }
if (targetCharacter.IsSameSpeciesOrGroup(c)) { return false; }
if (c.IsPlayer || Character.IsOnFriendlyTeam(c))
{
return a.Damage >= selectedTargetingParams.Threshold;
}
return true;
}
Character attacker = targetCharacter.LastAttackers.LastOrDefault(IsValid)?.Character;
//if the attacker has the same targeting tag as the character we're protecting, we can't change the TargetState
//otherwise e.g. a pet that's set to follow humans would start attacking all humans (and other pets, since they're considered part of the same group) when a hostile human attacks it
//TODO: a way for pets to differentiate hostile and friendly humans?
if (attacker?.AiTarget != null && targetCharacter.SpeciesName != GetTargetingTag(attacker.AiTarget) && !attacker.IsFriendly(targetCharacter))
Character attacker = targetCharacter.LastAttackers.LastOrDefault(ShouldRetaliate)?.Character;
if (attacker?.AiTarget != null)
{
// Attack the character that attacked the target we are protecting
ChangeTargetState(attacker, AIState.Attack, selectedTargetingParams.Priority * 2);
SelectTarget(attacker.AiTarget);
State = AIState.Attack;
@@ -1501,7 +1511,7 @@ namespace Barotrauma
{
hitTarget = limb.character;
}
if (hitTarget != null && !hitTarget.IsDead && Character.IsFriendly(hitTarget))
if (hitTarget != null && !hitTarget.IsDead && Character.IsFriendly(hitTarget) && !IsAttackingOwner(hitTarget))
{
return true;
}
@@ -2315,7 +2325,7 @@ namespace Barotrauma
{
t = limb.character;
}
if (t != null && (t == target || !Character.IsFriendly(t)))
if (t != null && (t == target || (!Character.IsFriendly(t) || IsAttackingOwner(t))))
{
return true;
}
@@ -310,7 +310,7 @@ namespace Barotrauma
UseIndoorSteeringOutside = false;
}
if (Character.Submarine == null || Character.IsOnPlayerTeam && !Character.IsEscorted && !IsOnFriendlyTeam(Character.TeamID, Character.Submarine.TeamID))
if (Character.Submarine == null || Character.IsOnPlayerTeam && !Character.IsEscorted && !Character.IsOnFriendlyTeam(Character.Submarine.TeamID))
{
// Spot enemies while staying outside or inside an enemy ship.
// does not apply for escorted characters, such as prisoners or terrorists who have their own behavior
@@ -541,7 +541,7 @@ namespace Barotrauma
if (Character.LockHands) { return; }
if (ObjectiveManager.CurrentObjective == null) { return; }
if (Character.CurrentHull == null) { return; }
bool oxygenLow = !Character.AnimController.HeadInWater && Character.OxygenAvailable < CharacterHealth.LowOxygenThreshold && Character.NeedsOxygen;
bool shouldActOnSuffocation = Character.IsLowInOxygen && !Character.AnimController.HeadInWater && HasDivingSuit(Character, requireOxygenTank: false) && !HasItem(Character, AIObjectiveFindDivingGear.OXYGEN_SOURCE, out _, conditionPercentage: 1);
bool isCarrying = ObjectiveManager.HasActiveObjective<AIObjectiveContainItem>() || ObjectiveManager.HasActiveObjective<AIObjectiveDecontainItem>();
bool NeedsDivingGearOnPath(AIObjectiveGoTo gotoObjective)
@@ -566,17 +566,17 @@ namespace Barotrauma
gotoObjective.Abandon = true;
}
}
if (!oxygenLow)
if (!shouldActOnSuffocation)
{
return;
}
}
// Diving gear
if (oxygenLow || findItemState != FindItemState.OtherItem)
if (shouldActOnSuffocation || findItemState != FindItemState.OtherItem)
{
bool needsGear = NeedsDivingGear(Character.CurrentHull, out _);
if (!needsGear || oxygenLow)
if (!needsGear || shouldActOnSuffocation)
{
bool isCurrentObjectiveFindSafety = ObjectiveManager.IsCurrentObjective<AIObjectiveFindSafety>();
bool shouldKeepTheGearOn =
@@ -591,14 +591,14 @@ namespace Barotrauma
Character.CurrentHull.IsWetRoom;
bool IsOrderedToWait() => Character.IsOnPlayerTeam && ObjectiveManager.CurrentOrder is AIObjectiveGoTo goTo && goTo.Target == Character;
bool removeDivingSuit = !shouldKeepTheGearOn && !IsOrderedToWait();
if (oxygenLow && Character.CurrentHull.Oxygen > 0 && (!isCurrentObjectiveFindSafety || Character.OxygenAvailable < 1))
if (shouldActOnSuffocation && Character.CurrentHull.Oxygen > 0 && (!isCurrentObjectiveFindSafety || Character.OxygenAvailable < 1))
{
shouldKeepTheGearOn = false;
// Remove the suit before we pass out
removeDivingSuit = true;
}
bool takeMaskOff = !shouldKeepTheGearOn;
if (!shouldKeepTheGearOn && !oxygenLow)
if (!shouldKeepTheGearOn && !shouldActOnSuffocation)
{
if (ObjectiveManager.IsCurrentObjective<AIObjectiveIdle>())
{
@@ -647,7 +647,7 @@ namespace Barotrauma
var divingSuit = Character.Inventory.FindItemByTag(AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR);
if (divingSuit != null && !divingSuit.HasTag(AIObjectiveFindDivingGear.DIVING_GEAR_WEARABLE_INDOORS))
{
if (oxygenLow || Character.Submarine?.TeamID != Character.TeamID || ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority)
if (shouldActOnSuffocation || Character.Submarine?.TeamID != Character.TeamID || ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority)
{
divingSuit.Drop(Character);
HandleRelocation(divingSuit);
@@ -982,7 +982,7 @@ namespace Barotrauma
if (target.CurrentHull != hull) { continue; }
if (AIObjectiveRescueAll.IsValidTarget(target, Character))
{
if (AddTargets<AIObjectiveRescueAll, Character>(Character, target) && newOrder == null && !ObjectiveManager.HasActiveObjective<AIObjectiveRescue>())
if (AddTargets<AIObjectiveRescueAll, Character>(Character, target) && newOrder == null && (!Character.IsMedic || Character == target) && !ObjectiveManager.HasActiveObjective<AIObjectiveRescue>())
{
var orderPrefab = OrderPrefab.Prefabs["requestfirstaid"];
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
@@ -1161,7 +1161,7 @@ namespace Barotrauma
freezeAI = true;
}
}
if (attacker == null || attacker.IsDead || attacker.Removed)
if (attacker == null || attacker.IsUnconscious || attacker.Removed)
{
// Don't react to the damage if there's no attacker.
// We might consider launching the retreat combat objective in some cases, so that the bot does not just stand somewhere getting damaged and dying.
@@ -1199,7 +1199,7 @@ namespace Barotrauma
return;
}
float cumulativeDamage = realDamage + Character.GetDamageDoneByAttacker(attacker);
bool isAccidental = attacker.IsBot && !IsMentallyUnstable && !attacker.AIController.IsMentallyUnstable && Character.CombatAction == null;
bool isAccidental = attacker.IsBot && !IsMentallyUnstable && !attacker.AIController.IsMentallyUnstable && attacker.CombatAction == null;
if (isAccidental)
{
if (!Character.IsSecurity && cumulativeDamage > minorDamageThreshold)
@@ -1279,7 +1279,7 @@ namespace Barotrauma
if (otherCharacter.Submarine != attacker.Submarine) { continue; }
if (otherCharacter.Info?.Job == null || otherCharacter.IsInstigator) { continue; }
if (otherCharacter.IsPlayer) { continue; }
if (!(otherCharacter.AIController is HumanAIController otherHumanAI)) { continue; }
if (otherCharacter.AIController is not HumanAIController otherHumanAI) { continue; }
if (!otherHumanAI.IsFriendly(Character)) { continue; }
bool isWitnessing = otherHumanAI.VisibleHulls.Contains(Character.CurrentHull) || otherHumanAI.VisibleHulls.Contains(attacker.CurrentHull);
if (!isWitnessing)
@@ -1299,7 +1299,7 @@ namespace Barotrauma
AIObjectiveCombat.CombatMode DetermineCombatMode(Character c, float cumulativeDamage = 0, bool isWitnessing = false)
{
if (!(c.AIController is HumanAIController humanAI)) { return AIObjectiveCombat.CombatMode.None; }
if (c.AIController is not HumanAIController humanAI) { return AIObjectiveCombat.CombatMode.None; }
if (!IsFriendly(attacker))
{
if (c.Submarine == null)
@@ -1327,7 +1327,7 @@ namespace Barotrauma
}
if (attacker.IsPlayer && c.TeamID == attacker.TeamID)
{
if (GameMain.IsSingleplayer || Character.TeamID != attacker.TeamID)
if (GameMain.IsSingleplayer || c.TeamID != attacker.TeamID)
{
// Bots in the player team never act aggressively in single player when attacked by the player
// In multiplayer, they react only to players attacking them or other crew members
@@ -1345,11 +1345,11 @@ namespace Barotrauma
isAttackerFightingEnemy = true;
return AIObjectiveCombat.CombatMode.None;
}
if (isWitnessing && Character.CombatAction != null && !c.IsSecurity)
if (isWitnessing && c.CombatAction != null && !c.IsSecurity)
{
return Character.CombatAction.WitnessReaction;
return c.CombatAction.WitnessReaction;
}
if (attacker.IsPlayer && FindInstigator() is Character instigator)
if (!attacker.IsInstigator && c.IsOnFriendlyTeam(attacker) && FindInstigator() is Character instigator)
{
// The guards don't react to player's aggressions when there's an instigator around
isAttackerFightingEnemy = true;
@@ -1359,11 +1359,11 @@ namespace Barotrauma
{
if (c.IsSecurity)
{
return Character.CombatAction != null ? Character.CombatAction.GuardReaction : AIObjectiveCombat.CombatMode.None;
return attacker.CombatAction != null ? attacker.CombatAction.GuardReaction : AIObjectiveCombat.CombatMode.Offensive;
}
else
{
return Character.CombatAction != null ? Character.CombatAction.WitnessReaction : AIObjectiveCombat.CombatMode.None;
return attacker.CombatAction != null ? attacker.CombatAction.WitnessReaction : AIObjectiveCombat.CombatMode.Retreat;
}
}
else
@@ -1514,9 +1514,18 @@ namespace Barotrauma
startPos.X += MathHelper.Clamp(Character.AnimController.TargetMovement.X, -1.0f, 1.0f);
//do a raycast upwards to find any walls
float minCeilingDist = Character.AnimController.Collider.height / 2 + Character.AnimController.Collider.radius + 0.1f;
if (!Character.AnimController.TryGetCollider(0, out PhysicsBody mainCollider))
{
mainCollider = Character.AnimController.Collider;
}
float margin = 0.1f;
if (shouldCrouch)
{
margin *= 2;
}
float minCeilingDist = mainCollider.height / 2 + mainCollider.radius + margin;
shouldCrouch = Submarine.PickBody(startPos, startPos + Vector2.UnitY * minCeilingDist, null, Physics.CollisionWall, customPredicate: (fixture) => { return !(fixture.Body.UserData is Submarine); }) != null;
shouldCrouch = Submarine.PickBody(startPos, startPos + Vector2.UnitY * minCeilingDist, null, Physics.CollisionWall, customPredicate: (fixture) => { return fixture.Body.UserData is not Submarine; }) != null;
}
public bool AllowCampaignInteraction()
@@ -1558,20 +1567,20 @@ namespace Barotrauma
return false;
}
public static bool HasDivingGear(Character character, float conditionPercentage = 0) => HasDivingSuit(character, conditionPercentage) || HasDivingMask(character, conditionPercentage);
public static bool HasDivingGear(Character character, float conditionPercentage = 0, bool requireOxygenTank = true) => HasDivingSuit(character, conditionPercentage, requireOxygenTank) || HasDivingMask(character, conditionPercentage, requireOxygenTank);
/// <summary>
/// Check whether the character has a diving suit in usable condition plus some oxygen.
/// </summary>
public static bool HasDivingSuit(Character character, float conditionPercentage = 0)
=> HasItem(character, AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR, out _, AIObjectiveFindDivingGear.OXYGEN_SOURCE, conditionPercentage, requireEquipped: true,
public static bool HasDivingSuit(Character character, float conditionPercentage = 0, bool requireOxygenTank = true)
=> HasItem(character, AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR, out _, requireOxygenTank ? AIObjectiveFindDivingGear.OXYGEN_SOURCE : Identifier.Empty, conditionPercentage, requireEquipped: true,
predicate: (Item item) => character.HasEquippedItem(item, InvSlotType.OuterClothes));
/// <summary>
/// Check whether the character has a diving mask in usable condition plus some oxygen.
/// </summary>
public static bool HasDivingMask(Character character, float conditionPercentage = 0)
=> HasItem(character, AIObjectiveFindDivingGear.LIGHT_DIVING_GEAR, out _, AIObjectiveFindDivingGear.OXYGEN_SOURCE, conditionPercentage, requireEquipped: true);
public static bool HasDivingMask(Character character, float conditionPercentage = 0, bool requireOxygenTank = true)
=> HasItem(character, AIObjectiveFindDivingGear.LIGHT_DIVING_GEAR, out _, requireOxygenTank ? AIObjectiveFindDivingGear.OXYGEN_SOURCE : Identifier.Empty, conditionPercentage, requireEquipped: true);
private static List<Item> matchingItems = new List<Item>();
@@ -1589,7 +1598,27 @@ namespace Barotrauma
(!requireEquipped || character.HasEquippedItem(i)) &&
(predicate == null || predicate(i)), recursive, matchingItems);
items = matchingItems;
return matchingItems.Any(i => i != null && (containedTag.IsEmpty || i.OwnInventory == null || i.ContainedItems.Any(it => it.HasTag(containedTag) && it.ConditionPercentage > conditionPercentage)));
foreach (var item in matchingItems)
{
if (item == null) { continue; }
if (containedTag.IsEmpty || item.OwnInventory == null)
{
//no contained items required, this item's ok
return true;
}
var suitableSlot = item.GetComponent<ItemContainer>().FindSuitableSubContainerIndex(containedTag);
if (suitableSlot == null)
{
//no restrictions on the suitable slot
return item.ContainedItems.Any(it => it.HasTag(containedTag) && it.ConditionPercentage > conditionPercentage);
}
else
{
return item.ContainedItems.Any(it => it.HasTag(containedTag) && it.ConditionPercentage > conditionPercentage && it.ParentInventory.IsInSlot(it, suitableSlot.Value));
}
}
return false;
}
public static void StructureDamaged(Structure structure, float damageAmount, Character character)
@@ -2016,11 +2045,9 @@ namespace Barotrauma
public static bool IsFriendly(Character me, Character other, bool onlySameTeam = false)
{
bool sameTeam = me.TeamID == other.TeamID;
bool friendlyTeam = IsOnFriendlyTeam(me, other);
bool teamGood = sameTeam || friendlyTeam && !onlySameTeam;
bool teamGood = sameTeam || !onlySameTeam && me.IsOnFriendlyTeam(other);
if (!teamGood) { return false; }
bool speciesGood = other.SpeciesName == me.SpeciesName || other.Params.CompareGroup(me.Params.Group);
if (!speciesGood) { return false; }
if (!me.IsSameSpeciesOrGroup(other)) { return false; }
if (me.TeamID == CharacterTeamType.FriendlyNPC && other.TeamID == CharacterTeamType.Team1 && GameMain.GameSession?.GameMode is CampaignMode campaign)
{
var reputation = campaign.Map?.CurrentLocation?.Reputation;
@@ -2029,30 +2056,14 @@ namespace Barotrauma
return false;
}
}
if (!sameTeam && me.TeamID == CharacterTeamType.None && other.IsPet)
{
// Hostile NPCs are hostile to all pets, unless they are in the same team.
return false;
}
return true;
}
public static bool IsOnFriendlyTeam(CharacterTeamType myTeam, CharacterTeamType otherTeam)
{
if (myTeam == otherTeam) { return true; }
switch (myTeam)
{
case CharacterTeamType.None:
case CharacterTeamType.Team1:
case CharacterTeamType.Team2:
// Only friendly to the same team and friendly NPCs
return otherTeam == CharacterTeamType.FriendlyNPC;
case CharacterTeamType.FriendlyNPC:
// Friendly NPCs are friendly to both teams
return otherTeam == CharacterTeamType.Team1 || otherTeam == CharacterTeamType.Team2;
default:
return true;
}
}
public static bool IsOnFriendlyTeam(Character me, Character other) => IsOnFriendlyTeam(me.TeamID, other.TeamID);
public static bool IsActive(Character other) => other != null && !other.Removed && !other.IsDead && !other.IsUnconscious;
public static bool IsTrueForAllCrewMembers(Character character, Func<HumanAIController, bool> predicate)
@@ -352,7 +352,7 @@ namespace Barotrauma
Weapon = null;
continue;
}
if (WeaponComponent.IsLoaded(character))
if (WeaponComponent.IsNotEmpty(character))
{
// All good, the weapon is loaded
break;
@@ -470,7 +470,7 @@ namespace Barotrauma
// Not in the inventory anymore or cannot find the weapon component
return false;
}
if (!WeaponComponent.IsLoaded(character))
if (!WeaponComponent.IsNotEmpty(character))
{
// Try reloading (and seek ammo)
if (!Reload(seekAmmo))
@@ -541,7 +541,7 @@ namespace Barotrauma
priority /= 2;
}
}
if (!weapon.IsLoaded(character))
if (!weapon.IsNotEmpty(character))
{
if (weapon is RangedWeapon && !isAllowedToSeekWeapons)
{
@@ -554,7 +554,15 @@ namespace Barotrauma
priority /= 2;
}
}
if (Enemy.IsKnockedDown)
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);
@@ -98,7 +98,7 @@ namespace Barotrauma
int containedItemCount = 0;
foreach (Item it in container.Inventory.AllItems)
{
if (CheckItem(it))
if (CheckItem(it) && IsInTargetSlot(it))
{
containedItemCount++;
}
@@ -244,7 +244,8 @@ namespace Barotrauma
public bool IsInTargetSlot(Item item)
{
if (container?.Inventory is ItemInventory inventory && TargetSlot is not null)
if (TargetSlot == null) { return true; }
if (container?.Inventory is ItemInventory inventory)
{
return inventory.IsInSlot(item, (int)TargetSlot);
}
@@ -1,5 +1,5 @@
using Barotrauma.Items.Components;
using Barotrauma.Extensions;
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using System.Collections.Generic;
using System.Linq;
@@ -18,6 +18,7 @@ namespace Barotrauma
private AIObjectiveGetItem getDivingGear;
private AIObjectiveContainItem getOxygen;
private Item targetItem;
private int? oxygenSourceSlotIndex;
public const float MIN_OXYGEN = 10;
@@ -43,12 +44,15 @@ namespace Barotrauma
Abandon = true;
return;
}
targetItem = character.Inventory.FindItemByTag(gearTag, true);
TrySetTargetItem(character.Inventory.FindItemByTag(gearTag, true));
if (targetItem == null && gearTag == LIGHT_DIVING_GEAR)
{
targetItem = character.Inventory.FindItemByTag(HEAVY_DIVING_GEAR, true);
TrySetTargetItem(character.Inventory.FindItemByTag(HEAVY_DIVING_GEAR, true));
}
if (targetItem == null || !character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.Head | InvSlotType.InnerClothes) && targetItem.ContainedItems.Any(i => i.HasTag(OXYGEN_SOURCE) && i.Condition > 0))
if (targetItem == null ||
!character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.Head | InvSlotType.InnerClothes) &&
targetItem.ContainedItems.Any(it => IsSuitableContainedOxygenSource(it)))
{
TryAddSubObjective(ref getDivingGear, () =>
{
@@ -84,7 +88,7 @@ namespace Barotrauma
else
{
float min = GetMinOxygen(character);
if (targetItem.OwnInventory != null && targetItem.OwnInventory.AllItems.None(it => it != null && it.HasTag(OXYGEN_SOURCE) && it.Condition > min))
if (targetItem.OwnInventory != null && targetItem.OwnInventory.AllItems.None(it => IsSuitableContainedOxygenSource(it)))
{
TryAddSubObjective(ref getOxygen, () =>
{
@@ -93,7 +97,7 @@ namespace Barotrauma
if (HumanAIController.HasItem(character, OXYGEN_SOURCE, 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).Count == 1)
if (character.Inventory.FindAllItems(i => i.HasTag(OXYGEN_SOURCE) && i.Condition > min, recursive: true).Count == 1)
{
character.Speak(TextManager.Get("dialoglastoxygentank").Value, null, 0.0f, "dialoglastoxygentank".ToIdentifier(), 30.0f);
}
@@ -109,7 +113,8 @@ namespace Barotrauma
AllowToFindDivingGear = false,
AllowDangerousPressure = true,
ConditionLevel = MIN_OXYGEN,
RemoveExistingWhenNecessary = true
RemoveExistingWhenNecessary = true,
TargetSlot = oxygenSourceSlotIndex
};
if (container.HasSubContainers)
{
@@ -167,12 +172,36 @@ namespace Barotrauma
}
}
private bool IsSuitableContainedOxygenSource(Item item)
{
return
item != null &&
item.HasTag(OXYGEN_SOURCE) &&
item.Condition > 0 &&
(oxygenSourceSlotIndex == null || item.ParentInventory.IsInSlot(item, oxygenSourceSlotIndex.Value));
}
private void TrySetTargetItem(Item item)
{
if (targetItem == item) { return; }
targetItem = item;
if (targetItem != null)
{
oxygenSourceSlotIndex = targetItem.GetComponent<ItemContainer>()?.FindSuitableSubContainerIndex(OXYGEN_SOURCE);
}
else
{
oxygenSourceSlotIndex = null;
}
}
public override void Reset()
{
base.Reset();
getDivingGear = null;
getOxygen = null;
targetItem = null;
oxygenSourceSlotIndex = null;
}
public static float GetMinOxygen(Character character)
@@ -63,15 +63,16 @@ namespace Barotrauma
}
else
{
if (HumanAIController.NeedsDivingGear(character.CurrentHull, out bool needsSuit) &&
if ((character.IsLowInOxygen && !character.AnimController.HeadInWater && HumanAIController.HasDivingSuit(character, requireOxygenTank: false)) ||
(HumanAIController.NeedsDivingGear(character.CurrentHull, out bool needsSuit) &&
(needsSuit ?
!HumanAIController.HasDivingSuit(character, conditionPercentage: AIObjectiveFindDivingGear.GetMinOxygen(character)) :
!HumanAIController.HasDivingGear(character, conditionPercentage: AIObjectiveFindDivingGear.GetMinOxygen(character))))
!HumanAIController.HasDivingGear(character, conditionPercentage: AIObjectiveFindDivingGear.GetMinOxygen(character)))))
{
Priority = 100;
}
else if ((objectiveManager.IsCurrentOrder<AIObjectiveGoTo>() || objectiveManager.IsCurrentOrder<AIObjectiveReturn>()) &&
character.Submarine != null && !HumanAIController.IsOnFriendlyTeam(character.TeamID, character.Submarine.TeamID))
character.Submarine != null && !character.IsOnFriendlyTeam(character.Submarine.TeamID))
{
// Ordered to follow, hold position, or return back to main sub inside a hostile sub
// -> ignore find safety unless we need to find a diving gear
@@ -137,12 +138,14 @@ namespace Barotrauma
private float retryTimer;
protected override void Act(float deltaTime)
{
if (resetPriority) { return; }
var currentHull = character.CurrentHull;
bool shouldActOnSuffocation = character.IsLowInOxygen && !character.AnimController.HeadInWater && HumanAIController.HasDivingSuit(character, requireOxygenTank: false);
bool dangerousPressure = currentHull == null || currentHull.LethalPressure > 0 && character.PressureProtection <= 0;
if (!character.LockHands && (!dangerousPressure || cannotFindSafeHull))
if (!character.LockHands && (!dangerousPressure || shouldActOnSuffocation || cannotFindSafeHull))
{
bool needsDivingGear = HumanAIController.NeedsDivingGear(currentHull, out bool needsDivingSuit);
bool needsEquipment = false;
bool needsEquipment = shouldActOnSuffocation;
if (needsDivingSuit)
{
needsEquipment = !HumanAIController.HasDivingSuit(character, AIObjectiveFindDivingGear.GetMinOxygen(character));
@@ -178,7 +178,7 @@ namespace Barotrauma
requiredCondition = () =>
Leak.Submarine == character.Submarine &&
Leak.linkedTo.Any(e => e is Hull h && (character.CurrentHull == h || h.linkedTo.Contains(character.CurrentHull))),
endNodeFilter = n => n.Waypoint.CurrentHull != null && Leak.linkedTo.Any(e => e is Hull h && h == n.Waypoint.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()
},
@@ -197,6 +197,14 @@ namespace Barotrauma
}
},
onCompleted: () => RemoveSubObjective(ref gotoObjective));
bool IsSuitableEndNode(PathNode n)
{
if (n.Waypoint.CurrentHull is null) { return false; }
if (n.Waypoint.CurrentHull.ConnectedGaps.Contains(Leak)) { return true; }
// Accept also nodes located in the linked hulls (multi-hull rooms)
return Leak.linkedTo.Any(e => e is Hull h && h.linkedTo.Contains(n.Waypoint.CurrentHull));
}
}
}
@@ -54,7 +54,7 @@ namespace Barotrauma
public bool AllowVariants { get; set; }
public bool Equip { get; set; }
public bool Wear { get; set; }
public bool RequireLoaded { get; set; }
public bool RequireNonEmpty { get; set; }
public bool EvaluateCombatPriority { get; set; }
public bool CheckPathForEachItem { get; set; }
public bool SpeakIfFails { get; set; }
@@ -391,10 +391,10 @@ namespace Barotrauma
{
if (!itemInventory.Container.HasRequiredItems(character, addMessage: false)) { continue; }
}
float itemPriority = 1;
float itemPriority = item.Prefab.BotPriority;
if (GetItemPriority != null)
{
itemPriority = GetItemPriority(item);
itemPriority *= GetItemPriority(item);
}
Entity rootInventoryOwner = item.GetRootInventoryOwner();
if (rootInventoryOwner is Item ownerItem)
@@ -513,7 +513,7 @@ namespace Barotrauma
float lowestCost = float.MaxValue;
foreach (MapEntityPrefab prefab in MapEntityPrefab.List)
{
if (!(prefab is ItemPrefab itemPrefab)) { continue; }
if (prefab is not ItemPrefab itemPrefab) { continue; }
if (IdentifiersOrTags.Any(id => id == prefab.Identifier || prefab.Tags.Contains(id)))
{
float cost = itemPrefab.DefaultPrice != null && itemPrefab.CanBeBought ?
@@ -561,7 +561,7 @@ namespace Barotrauma
if (ignoredIdentifiersOrTags != null && CheckItemIdentifiersOrTags(item, ignoredIdentifiersOrTags)) { return false; }
if (item.Condition < TargetCondition) { return false; }
if (ItemFilter != null && !ItemFilter(item)) { return false; }
if (RequireLoaded && item.Components.Any(i => !i.IsLoaded(character))) { return false; }
if (RequireNonEmpty && item.Components.Any(i => !i.IsNotEmpty(character))) { return false; }
return CheckItemIdentifiersOrTags(item, IdentifiersOrTags) || (AllowVariants && !item.Prefab.VariantOf.IsEmpty && IdentifiersOrTags.Contains(item.Prefab.VariantOf));
}
@@ -21,7 +21,7 @@ namespace Barotrauma
public bool CheckInventory { get; set; }
public bool EvaluateCombatPriority { get; set; }
public bool CheckPathForEachItem { get; set; }
public bool RequireLoaded { get; set; }
public bool RequireNonEmpty { get; set; }
public bool RequireAllItems { get; set; }
private readonly ImmutableArray<Identifier> gearTags;
@@ -61,7 +61,7 @@ namespace Barotrauma
AllowStealing = AllowStealing,
ignoredIdentifiersOrTags = ignoredTags,
CheckPathForEachItem = CheckPathForEachItem,
RequireLoaded = RequireLoaded,
RequireNonEmpty = RequireNonEmpty,
ItemCount = count,
SpeakIfFails = RequireAllItems
},
@@ -364,8 +364,7 @@ namespace Barotrauma
CurrentOrders.RemoveAt(i);
continue;
}
var currentOrderInfo = character.GetCurrentOrder(currentOrder);
if (currentOrderInfo is Order)
if (character.GetCurrentOrder(currentOrder) is Order currentOrderInfo)
{
int currentPriority = currentOrderInfo.ManualPriority;
if (currentOrder.ManualPriority != currentPriority)
@@ -539,7 +538,8 @@ namespace Barotrauma
KeepActiveWhenReady = true,
CheckInventory = true,
Equip = false,
FindAllItems = true
FindAllItems = true,
RequireNonEmpty = false
};
break;
case "findweapon":
@@ -555,7 +555,8 @@ namespace Barotrauma
KeepActiveWhenReady = false,
CheckInventory = false,
EvaluateCombatPriority = true,
FindAllItems = false
FindAllItems = false,
RequireNonEmpty = true
};
}
prepareObjective.KeepActiveWhenReady = false;
@@ -600,9 +601,9 @@ namespace Barotrauma
Order dismissOrder = currentOrder.GetDismissal();
#if CLIENT
if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.IsSinglePlayer)
if (GameMain.GameSession?.CrewManager is CrewManager cm && cm.IsSinglePlayer)
{
GameMain.GameSession.CrewManager.SetCharacterOrder(character, dismissOrder);
character.SetOrder(dismissOrder, isNewOrder: true, speak: false);
}
#else
GameMain.Server?.SendOrderChatMessage(new OrderChatMessage(dismissOrder, character, character));
@@ -27,6 +27,7 @@ namespace Barotrauma
public bool FindAllItems { get; set; }
public bool Equip { get; set; }
public bool EvaluateCombatPriority { get; set; }
public bool RequireNonEmpty { get; set; }
private AIObjective GetSubObjective()
{
@@ -74,7 +75,7 @@ namespace Barotrauma
Abandon = true;
}
else if (items.Any(i => i.Components.Any(i => !i.IsLoaded(character))))
else if (items.Any(i => i.Components.Any(i => !i.IsNotEmpty(character))))
{
Reset();
}
@@ -106,7 +107,7 @@ namespace Barotrauma
CheckInventory = CheckInventory,
Equip = Equip,
EvaluateCombatPriority = EvaluateCombatPriority,
RequireLoaded = true,
RequireNonEmpty = RequireNonEmpty,
RequireAllItems = requireAll
},
onCompleted: () =>
@@ -157,7 +158,7 @@ namespace Barotrauma
{
EvaluateCombatPriority = EvaluateCombatPriority,
SpeakIfFails = true,
RequireLoaded = true
RequireNonEmpty = RequireNonEmpty
};
}
if (!TryAddSubObjective(ref getSingleItemObjective, getItemConstructor,
@@ -320,10 +320,10 @@ namespace Barotrauma
foreach (KeyValuePair<Identifier, float> treatmentSuitability in currentTreatmentSuitabilities)
{
if (treatmentSuitability.Value <= cprSuitability) { continue; }
if (MapEntityPrefab.Find(null, treatmentSuitability.Key, showErrorMessages: false) is ItemPrefab itemPrefab)
if (ItemPrefab.Prefabs.TryGet(treatmentSuitability.Key, out ItemPrefab itemPrefab))
{
if (!Item.ItemList.Any(it => ((MapEntity)it).Prefab.Identifier == treatmentSuitability.Key)) { continue; }
suitableItemIdentifiers.Add(treatmentSuitability.Key);
if (Item.ItemList.None(it => it.Prefab.Identifier == treatmentSuitability.Key)) { continue; }
suitableItemIdentifiers.Add(itemPrefab.Identifier);
//only list the first 4 items
if (itemNameList.Count < 4)
{
@@ -413,7 +413,7 @@ namespace Barotrauma
}
}
}
private void ApplyTreatment(Affliction affliction, Item item)
{
item.ApplyTreatment(character, targetCharacter, targetCharacter.CharacterHealth.GetAfflictionLimb(affliction));
@@ -482,18 +482,6 @@ namespace Barotrauma
public static IEnumerable<Affliction> GetSortedAfflictions(Character character, bool excludeBuffs = true) => CharacterHealth.SortAfflictionsBySeverity(character.CharacterHealth.GetAllAfflictions(), excludeBuffs);
public static IEnumerable<Affliction> GetTreatableAfflictions(Character character)
{
var allAfflictions = character.CharacterHealth.GetAllAfflictions();
foreach (Affliction affliction in allAfflictions)
{
if (affliction.Prefab.IsBuff || affliction.Strength < affliction.Prefab.TreatmentThreshold) { continue; }
if (!affliction.Prefab.TreatmentSuitability.Any(kvp => kvp.Value > 0)) { continue; }
if (allAfflictions.Any(otherAffliction => affliction.Prefab.IgnoreTreatmentIfAfflictedBy.Contains(otherAffliction.Identifier))) { continue; }
yield return affliction;
}
}
public override void Reset()
{
base.Reset();
@@ -26,7 +26,7 @@ namespace Barotrauma
// When targeting player characters, always treat them when ordered, else use the threshold so that minor/non-severe damage is ignored.
// If we ignore any damage when the player orders a bot to do healings, it's observed to cause confusion among the players.
// On the other hand, if the bots too eagerly heal characters when it's not necessary, it's inefficient and can feel frustrating, because it can't be controlled.
return character == target || manager.HasOrder<AIObjectiveRescueAll>() ? (target.IsPlayer ? 100 : vitalityThresholdForOrders) : vitalityThreshold;
return character == target || manager.HasOrder<AIObjectiveRescueAll>() ? (target.IsPlayer && target.HealthPercentage < 100 ? 100 : vitalityThresholdForOrders) : vitalityThreshold;
}
}
@@ -67,15 +67,34 @@ namespace Barotrauma
float vitality = 100;
vitality -= character.Bleeding * 2;
vitality += Math.Min(character.Oxygen, 0);
vitality -= character.CharacterHealth.GetAfflictionStrength("paralysis");
foreach (Affliction affliction in AIObjectiveRescue.GetTreatableAfflictions(character))
foreach (Affliction affliction in GetTreatableAfflictions(character))
{
float strength = character.CharacterHealth.GetPredictedStrength(affliction, predictFutureDuration: 10.0f);
vitality -= affliction.GetVitalityDecrease(character.CharacterHealth, strength) / character.MaxVitality * 100;
if (affliction.Prefab.AfflictionType == "paralysis")
{
vitality -= affliction.Strength;
}
else if (affliction.Prefab.AfflictionType == "poison")
{
vitality -= affliction.Strength;
}
}
return Math.Clamp(vitality, 0, 100);
}
public static IEnumerable<Affliction> GetTreatableAfflictions(Character character)
{
var allAfflictions = character.CharacterHealth.GetAllAfflictions();
foreach (Affliction affliction in allAfflictions)
{
if (affliction.Prefab.IsBuff || affliction.Strength < 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;
}
}
protected override AIObjective ObjectiveConstructor(Character target)
=> new AIObjectiveRescue(character, target, objectiveManager, PriorityModifier);
@@ -371,7 +371,8 @@ namespace Barotrauma
private int CalculateCellCount(int minValue, int maxValue)
{
if (maxValue == 0) { return 0; }
float t = MathUtils.InverseLerp(0, 100, Level.Loaded.Difficulty * Config.AgentSpawnCountDifficultyMultiplier);
float difficulty = Level.Loaded?.Difficulty ?? 0.0f;
float t = MathUtils.InverseLerp(0, 100, difficulty * Config.AgentSpawnCountDifficultyMultiplier);
return (int)Math.Round(MathHelper.Lerp(minValue, maxValue, t));
}
@@ -381,7 +382,8 @@ namespace Barotrauma
float delay = Config.AgentSpawnDelay;
float min = delay;
float max = delay * 6;
float t = Level.Loaded.Difficulty * Config.AgentSpawnDelayDifficultyMultiplier * Rand.Range(1 - randomFactor, 1 + randomFactor);
float difficulty = Level.Loaded?.Difficulty ?? 0.0f;
float t = difficulty * Config.AgentSpawnDelayDifficultyMultiplier * Rand.Range(1 - randomFactor, 1 + randomFactor);
return MathHelper.Lerp(max, min, MathUtils.InverseLerp(0, 100, t));
}