Faction Test v1.0.1.0

This commit is contained in:
Regalis11
2023-02-16 15:01:28 +02:00
parent caa5a2f762
commit 2c5a7923b0
309 changed files with 7502 additions and 4335 deletions
@@ -352,21 +352,6 @@ namespace Barotrauma
}
}
public static bool IsOnFriendlyTeam(CharacterTeamType myTeam, CharacterTeamType otherTeam)
{
if (myTeam == otherTeam) { return true; }
return myTeam switch
{
// NPCs are friendly to the same team and the friendly NPCs
CharacterTeamType.None or CharacterTeamType.Team1 or CharacterTeamType.Team2 => otherTeam == CharacterTeamType.FriendlyNPC,
// Friendly NPCs are friendly to both player teams
CharacterTeamType.FriendlyNPC => otherTeam == CharacterTeamType.Team1 || otherTeam == CharacterTeamType.Team2,
_ => true
};
}
public static bool IsOnFriendlyTeam(Character me, Character other) => IsOnFriendlyTeam(me.TeamID, other.TeamID);
public void ReequipUnequipped()
{
foreach (var item in unequippedItems)
@@ -460,7 +445,7 @@ namespace Barotrauma
if (EscapeTarget != null)
{
var door = EscapeTarget.ConnectedDoor;
bool isClosedDoor = door != null && !door.IsOpen;
bool isClosedDoor = door != null && door.IsClosed;
Vector2 diff = EscapeTarget.WorldPosition - Character.WorldPosition;
float sqrDist = diff.LengthSquared();
bool isClose = sqrDist < MathUtils.Pow2(100);
@@ -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
@@ -308,11 +314,10 @@ namespace Barotrauma
insideSteering = new IndoorsSteeringManager(this, Character.Params.AI.CanOpenDoors, canAttackDoors);
steeringManager = outsideSteering;
State = AIState.Idle;
requiredHoleCount = (int)Math.Ceiling(ConvertUnits.ToDisplayUnits(colliderWidth) / Structure.WallSectionSize);
myBodies = Character.AnimController.Limbs.Select(l => l.body.FarseerBody).ToList();
myBodies.Add(Character.AnimController.Collider.FarseerBody);
CreatureMetrics.UnlockInEditor(Character.SpeciesName);
}
private CharacterParams.AIParams _aiParams;
@@ -354,7 +359,7 @@ namespace Barotrauma
{
targetingTag = "owner";
}
else if (targetCharacter.AIController is HumanAIController && !IsOnFriendlyTeam(Character, targetCharacter))
else if (PetBehavior != null && (!Character.IsOnFriendlyTeam(targetCharacter) || IsAttackingOwner(targetCharacter)))
{
targetingTag = "hostile";
}
@@ -683,19 +688,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 (attacker?.AiTarget != null && !Character.IsSameSpeciesOrGroup(attacker) && !targetCharacter.IsSameSpeciesOrGroup(attacker))
Character attacker = targetCharacter.LastAttackers.LastOrDefault(ShouldRetaliate)?.Character;
if (attacker?.AiTarget != null)
{
// Can't retaliate on characters of same species or group because that would make us hostile to all friendly characters in the same group.
ChangeTargetState(attacker, AIState.Attack, selectedTargetingParams.Priority * 2);
SelectTarget(attacker.AiTarget);
State = AIState.Attack;
@@ -869,7 +877,7 @@ namespace Barotrauma
var pathSteering = SteeringManager as IndoorsSteeringManager;
if (pathSteering == null)
{
if (SimPosition.Y < ConvertUnits.ToSimUnits(Character.CharacterHealth.CrushDepth * 0.75f))
if (Level.Loaded != null && Level.Loaded.GetRealWorldDepth(WorldPosition.Y) > Character.CharacterHealth.CrushDepth * 0.75f)
{
// Steer straight up if very deep
SteeringManager.SteeringManual(deltaTime, Vector2.UnitY);
@@ -1503,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;
}
@@ -2413,7 +2421,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;
}
@@ -3062,7 +3070,8 @@ namespace Barotrauma
// In the attack state allow going into non-allowed zone only when chasing a target.
if (State == targetParams.State && SelectedAiTarget == aiTarget) { break; }
}
if (!IsPositionInsideAllowedZone(aiTarget.WorldPosition, out _))
bool insideSameSub = aiTarget?.Entity?.Submarine != null && aiTarget.Entity.Submarine == Character.Submarine;
if (!insideSameSub && !IsPositionInsideAllowedZone(aiTarget.WorldPosition, out _))
{
// If we have recently been damaged by the target (or another player/bot in the same team) allow targeting it even when we are in the idle state.
bool isTargetInPlayerTeam = IsTargetInPlayerTeam(aiTarget);
@@ -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
@@ -444,7 +444,7 @@ namespace Barotrauma
if (objectiveManager.CurrentObjective == null) { return; }
objectiveManager.DoCurrentObjective(deltaTime);
bool run = objectiveManager.CurrentObjective.ForceRun || !objectiveManager.CurrentObjective.ForceWalk && objectiveManager.GetCurrentPriority() > AIObjectiveManager.RunPriority;
bool run = (objectiveManager.CurrentObjective.ForceRun && !objectiveManager.CurrentObjective.ForceWalk) || (!objectiveManager.CurrentObjective.ForceWalk && objectiveManager.GetCurrentPriority() > AIObjectiveManager.RunPriority);
if (ObjectiveManager.CurrentObjective is AIObjectiveGoTo goTo && goTo.Target != null)
{
if (Character.CurrentHull == null)
@@ -541,12 +541,12 @@ 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)
{
if (!Character.NeedsAir) { return false; }
if (Character.IsImmuneToPressure) { return false; }
bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty;
Hull targetHull = gotoObjective.GetTargetHull();
return gotoObjective.Target != null && targetHull == null ||
@@ -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 =
@@ -584,21 +584,21 @@ namespace Barotrauma
Character.AnimController.InWater ||
Character.AnimController.HeadInWater ||
Character.Submarine == null ||
(Character.Submarine.TeamID != Character.TeamID && !Character.IsEscorted) ||
(!Character.IsOnFriendlyTeam(Character.TeamID, Character.Submarine.TeamID) && !Character.IsEscorted) ||
ObjectiveManager.CurrentOrders.Any(o => o.Objective.KeepDivingGearOnAlsoWhenInactive) ||
ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn) ||
Character.CurrentHull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 10 ||
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>())
{
@@ -621,9 +621,10 @@ namespace Barotrauma
}
else if (gotoObjective.Mimic)
{
bool targetHasDivingGear = HasDivingGear(gotoObjective.Target as Character, requireOxygenTank: false);
if (!removeSuit)
{
removeDivingSuit = !HasDivingSuit(gotoObjective.Target as Character);
removeDivingSuit = !targetHasDivingGear;
if (removeDivingSuit)
{
removeSuit = true;
@@ -631,7 +632,7 @@ namespace Barotrauma
}
if (!removeMask)
{
takeMaskOff = !HasDivingMask(gotoObjective.Target as Character);
takeMaskOff = !targetHasDivingGear;
if (takeMaskOff)
{
removeMask = true;
@@ -647,7 +648,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);
@@ -783,20 +784,23 @@ namespace Barotrauma
private void HandleRelocation(Item item)
{
if (item.Submarine?.TeamID == CharacterTeamType.FriendlyNPC)
if (item.SpawnedInCurrentOutpost) { return; }
if (item.Submarine == null) { return; }
// Only affects bots in the player team
if (!Character.IsOnPlayerTeam) { return; }
// Don't relocate if the item is on a sub of the same team
if (item.Submarine.TeamID == Character.TeamID) { return; }
if (itemsToRelocate.Contains(item)) { return; }
itemsToRelocate.Add(item);
if (item.Submarine.ConnectedDockingPorts.TryGetValue(Submarine.MainSub, out DockingPort myPort))
{
if (itemsToRelocate.Contains(item)) { return; }
itemsToRelocate.Add(item);
if (item.Submarine.ConnectedDockingPorts.TryGetValue(Submarine.MainSub, out DockingPort myPort))
{
myPort.OnUnDocked += Relocate;
}
var campaign = GameMain.GameSession.Campaign;
if (campaign != null)
{
// In the campaign mode, undocking happens after leaving the outpost, so we can't use that.
campaign.BeforeLevelLoading += Relocate;
}
myPort.OnUnDocked += Relocate;
}
var campaign = GameMain.GameSession.Campaign;
if (campaign != null)
{
// In the campaign mode, undocking happens after leaving the outpost, so we can't use that.
campaign.BeforeLevelLoading += Relocate;
}
void Relocate()
@@ -982,15 +986,15 @@ 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);
targetHull = hull;
}
}
}
foreach (Item item in Item.ItemList)
}
foreach (Item item in Item.RepairableItems)
{
if (item.CurrentHull != hull) { continue; }
if (AIObjectiveRepairItems.IsValidTarget(item, Character))
@@ -1161,7 +1165,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 +1203,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)
@@ -1209,7 +1213,7 @@ namespace Barotrauma
}
else
{
isAttackerInfected = attacker.CharacterHealth.GetAfflictionStrength("alieninfection") > 0;
isAttackerInfected = attacker.CharacterHealth.GetAfflictionStrength(AfflictionPrefab.AlienInfectedType) > 0;
// Inform other NPCs
if (isAttackerInfected || cumulativeDamage > minorDamageThreshold || totalDamage > minorDamageThreshold)
{
@@ -1279,7 +1283,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 +1303,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 +1331,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 +1349,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 +1363,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
@@ -1546,7 +1550,7 @@ namespace Barotrauma
public bool NeedsDivingGear(Hull hull, out bool needsSuit)
{
if (!Character.NeedsAir)
if (Character.IsImmuneToPressure)
{
needsSuit = false;
return false;
@@ -1557,30 +1561,30 @@ namespace Barotrauma
hull.LethalPressure > 0 ||
hull.ConnectedGaps.Any(gap => !gap.IsRoomToRoom && gap.Open > 0.9f))
{
needsSuit = !Character.HasAbilityFlag(AbilityFlags.ImmuneToPressure);
needsSuit = true;
return true;
}
if (hull.WaterPercentage > 60 || hull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 1)
if (Character.CharacterHealth.OxygenLowResistance < 1 && (hull.WaterPercentage > 60 || hull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 1))
{
return true;
}
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>();
@@ -1745,7 +1749,9 @@ namespace Barotrauma
}
if (!someoneSpoke)
{
if (!item.StolenDuringRound && GameMain.GameSession?.Campaign?.Map?.CurrentLocation != null)
if (!item.StolenDuringRound &&
Level.Loaded?.Type == LevelData.LevelType.Outpost &&
GameMain.GameSession?.Campaign?.Map?.CurrentLocation != null)
{
var reputationLoss = MathHelper.Clamp(
(item.Prefab.GetMinPrice() ?? 0) * Reputation.ReputationLossPerStolenItemPrice,
@@ -1843,7 +1849,7 @@ namespace Barotrauma
}
break;
case "reportbrokendevices":
foreach (var item in Item.ItemList)
foreach (var item in Item.RepairableItems)
{
if (item.CurrentHull != hull) { continue; }
if (AIObjectiveRepairItems.IsValidTarget(item, character))
@@ -1924,11 +1930,12 @@ namespace Barotrauma
bool isCurrentHull = character == Character && character.CurrentHull == hull;
if (hull == null)
{
float hullSafety = character.IsProtectedFromPressure ? 0 : 100;
if (isCurrentHull)
{
CurrentHullSafety = character.NeedsAir ? 0 : 100;
CurrentHullSafety = hullSafety;
}
return CurrentHullSafety;
return hullSafety;
}
if (isCurrentHull && visibleHulls == null)
{
@@ -1936,10 +1943,9 @@ namespace Barotrauma
visibleHulls = VisibleHulls;
}
bool ignoreFire = objectiveManager.CurrentOrder is AIObjectiveExtinguishFires extinguishOrder && extinguishOrder.Priority > 0 || objectiveManager.HasActiveObjective<AIObjectiveExtinguishFire>();
bool ignoreWater = character.IsProtectedFromPressure();
bool ignoreOxygen = HasDivingGear(character);
bool ignoreOxygen = character.IsProtectedFromPressure || HasDivingGear(character);
bool ignoreEnemies = ObjectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() || ObjectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>();
float safety = CalculateHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies);
float safety = CalculateHullSafety(hull, visibleHulls, character, ignoreWater: false, ignoreOxygen, ignoreFire, ignoreEnemies);
if (isCurrentHull)
{
CurrentHullSafety = safety;
@@ -1949,15 +1955,33 @@ namespace Barotrauma
private static float CalculateHullSafety(Hull hull, IEnumerable<Hull> visibleHulls, Character character, bool ignoreWater = false, bool ignoreOxygen = false, bool ignoreFire = false, bool ignoreEnemies = false)
{
if (hull == null) { return character.NeedsAir ? 0 : 100; }
if (hull.LethalPressure > 0 && character.PressureProtection <= 0 && !character.HasAbilityFlag(AbilityFlags.ImmuneToPressure)) { return 0; }
bool isProtectedFromPressure = character.IsProtectedFromPressure;
if (hull == null) { return isProtectedFromPressure ? 100 : 0; }
if (hull.LethalPressure > 0 && !isProtectedFromPressure) { return 0; }
// Oxygen factor should be 1 with 70% oxygen or more and 0.1 when the oxygen level is 30% or lower.
// With insufficient oxygen, the safety of the hull should be 39, all the other factors aside. So, just below the HULL_SAFETY_THRESHOLD.
float oxygenFactor = ignoreOxygen ? 1 : MathHelper.Lerp((HULL_SAFETY_THRESHOLD - 1) / 100, 1, MathUtils.InverseLerp(HULL_LOW_OXYGEN_PERCENTAGE, 100 - HULL_LOW_OXYGEN_PERCENTAGE, hull.OxygenPercentage));
float waterFactor = ignoreWater ? 1 : MathHelper.Lerp(1, HULL_SAFETY_THRESHOLD / 2 / 100, hull.WaterPercentage / 100);
if (!character.NeedsAir)
float waterFactor = 1;
if (!ignoreWater)
{
if (visibleHulls != null)
{
// Take the visible hulls into account too, because otherwise multi-hull rooms on several floors (with platforms) will yield unexpected results.
float relativeWaterVolume = visibleHulls.Sum(s => s.WaterVolume) / visibleHulls.Sum(s => s.Volume);
waterFactor = MathHelper.Lerp(1, HULL_SAFETY_THRESHOLD / 2 / 100, relativeWaterVolume);
}
else
{
float relativeWaterVolume = hull.WaterVolume / hull.Volume;
waterFactor = MathHelper.Lerp(1, HULL_SAFETY_THRESHOLD / 2 / 100, relativeWaterVolume);
}
}
if (character.CharacterHealth.OxygenLowResistance >= 1)
{
oxygenFactor = 1;
}
if (isProtectedFromPressure)
{
waterFactor = 1;
}
float fireFactor = 1;
@@ -2045,21 +2069,39 @@ namespace Barotrauma
public static bool IsFriendly(Character me, Character other, bool onlySameTeam = false)
{
bool sameTeam = me.TeamID == other.TeamID;
bool teamGood = sameTeam || !onlySameTeam && IsOnFriendlyTeam(me, other);
bool teamGood = sameTeam || !onlySameTeam && me.IsOnFriendlyTeam(other);
if (!teamGood) { 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;
if (reputation != null && reputation.NormalizedValue < Reputation.HostileThreshold)
{
return false;
}
}
if (!sameTeam && me.TeamID == CharacterTeamType.None && other.IsPet)
if (other.IsPet)
{
// Hostile NPCs are hostile to all pets, unless they are in the same team.
return false;
if (!sameTeam && me.TeamID == CharacterTeamType.None) { return false; }
}
else
{
if (!me.IsSameSpeciesOrGroup(other)) { return false; }
}
if (GameMain.GameSession?.GameMode is CampaignMode campaign)
{
if ((me.TeamID == CharacterTeamType.FriendlyNPC && other.TeamID == CharacterTeamType.Team1) ||
(me.TeamID == CharacterTeamType.Team1 && other.TeamID == CharacterTeamType.FriendlyNPC))
{
Character npc = me.TeamID == CharacterTeamType.FriendlyNPC ? me : other;
Identifier npcFaction = npc.Faction;
Identifier currentLocationFaction = campaign.Map?.CurrentLocation?.Faction?.Prefab.Identifier ?? Identifier.Empty;
if (npcFaction.IsEmpty)
{
//if faction identifier is not specified, assume the NPC is a member of the faction that owns the outpost
npcFaction = currentLocationFaction;
}
if (!currentLocationFaction.IsEmpty && npcFaction == currentLocationFaction)
{
var reputation = campaign.Map?.CurrentLocation?.Reputation;
if (reputation != null && reputation.NormalizedValue < Reputation.HostileThreshold)
{
return false;
}
}
}
}
return true;
}
@@ -96,7 +96,7 @@ namespace Barotrauma
base.Update(speed);
float step = 1.0f / 60.0f;
checkDoorsTimer -= step;
if (lastDoor.door == null || !lastDoor.shouldBeOpen || lastDoor.door.IsOpen)
if (lastDoor.door == null || !lastDoor.shouldBeOpen || lastDoor.door.IsFullyOpen)
{
buttonPressTimer = 0;
}
@@ -211,7 +211,7 @@ namespace Barotrauma
currentTarget = target;
Vector2 currentPos = host.SimPosition;
pathFinder.InsideSubmarine = character.Submarine != null && !character.Submarine.Info.IsRuin;
pathFinder.ApplyPenaltyToOutsideNodes = character.Submarine != null && character.PressureProtection <= 0;
pathFinder.ApplyPenaltyToOutsideNodes = character.Submarine != null && !character.IsProtectedFromPressure;
var newPath = pathFinder.FindPath(currentPos, target, character.Submarine, "(Character: " + character.Name + ")", minGapSize, startNodeFilter, endNodeFilter, nodeFilter, checkVisibility: checkVisibility);
bool useNewPath = needsNewPath || currentPath == null || currentPath.CurrentNode == null || character.Submarine != null && findPathTimer < -1 && Math.Abs(character.AnimController.TargetMovement.Combine()) <= 0;
if (!useNewPath && currentPath?.CurrentNode != null && newPath.Nodes.Any() && !newPath.Unreachable)
@@ -342,7 +342,7 @@ namespace Barotrauma
CheckDoorsInPath();
doorsChecked = true;
}
if (buttonPressTimer > 0 && lastDoor.door != null && lastDoor.shouldBeOpen && !lastDoor.door.IsOpen)
if (buttonPressTimer > 0 && lastDoor.door != null && lastDoor.shouldBeOpen && lastDoor.door.IsOpening)
{
// We have pressed the button and are waiting for the door to open -> Hold still until we can press the button again.
Reset();
@@ -510,7 +510,7 @@ namespace Barotrauma
private bool CanAccessDoor(Door door, Func<Controller, bool> buttonFilter = null)
{
if (door.IsBroken) { return true; }
if (!door.IsOpen)
if (door.IsClosed)
{
if (!door.Item.IsInteractable(character)) { return false; }
if (!ShouldBreakDoor(door))
@@ -536,7 +536,7 @@ namespace Barotrauma
}
foreach (var linked in door.Item.linkedTo)
{
if (!(linked is Item linkedItem)) { continue; }
if (linked is not Item linkedItem) { continue; }
var button = linkedItem.GetComponent<Controller>();
if (button == null) { continue; }
if (button.HasAccess(character) && (buttonFilter == null || buttonFilter(button)))
@@ -785,7 +785,7 @@ namespace Barotrauma
{
if (hull.WaterVolume / hull.Rect.Width > 100.0f)
{
if (!HumanAIController.HasDivingSuit(character))
if (!HumanAIController.HasDivingSuit(character) && character.CharacterHealth.OxygenLowResistance < 1)
{
penalty += 500.0f;
}
@@ -808,7 +808,7 @@ namespace Barotrauma
private float? GetSingleNodePenalty(PathNode node)
{
if (node.Waypoint.isObstructed) { return null; }
if (!node.Waypoint.IsTraversable) { return null; }
if (node.IsBlocked()) { return null; }
float penalty = 0.0f;
if (node.Waypoint.ConnectedGap != null && node.Waypoint.ConnectedGap.Open < 0.9f)
@@ -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);
@@ -640,11 +648,11 @@ namespace Barotrauma
{
statusEffects = statusEffects.Concat(hitEffects);
}
float afflictionsStun = attack.Afflictions.Keys.Sum(a => a.Identifier == "stun" ? a.Strength : 0);
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 == "stun");
var stunAffliction = se.Afflictions.Find(a => a.Identifier == AfflictionPrefab.StunType);
if (stunAffliction != null)
{
stunAmount = stunAffliction.Strength;
@@ -1168,30 +1176,31 @@ namespace Barotrauma
if (sqrDistance > repairTool.Range * repairTool.Range) { return; }
}
float aimFactor = MathHelper.PiOver2 * (1 - AimAccuracy);
if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - Weapon.Position) < MathHelper.PiOver4 + aimFactor)
if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.WorldPosition - Weapon.WorldPosition) < MathHelper.PiOver4 + aimFactor)
{
if (myBodies == null)
{
myBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody);
}
var collisionCategories = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel;
var pickedBody = Submarine.PickBody(Weapon.SimPosition, Enemy.SimPosition, myBodies, collisionCategories, allowInsideFixture: true);
if (pickedBody != null)
// 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);
foreach (var body in pickedBodies)
{
Character target = null;
if (pickedBody.UserData is Character c)
if (body.UserData is Character c)
{
target = c;
}
else if (pickedBody.UserData is Limb limb)
else if (body.UserData is Limb limb)
{
target = limb.character;
}
if (target != null && (target == Enemy || !HumanAIController.IsFriendly(target)))
if (target != null && (target != Enemy || HumanAIController.IsFriendly(target)))
{
UseWeapon(deltaTime);
return;
}
}
UseWeapon(deltaTime);
}
}
}
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
@@ -200,7 +201,8 @@ namespace Barotrauma
(container.Item.GetRootContainer()?.OwnInventory?.Locked ?? false) ||
ItemToContain == null || ItemToContain.Removed ||
!ItemToContain.IsOwnedBy(character) || container.Item.GetRootInventoryOwner() is Character c && c != character,
SpeakIfFails = !objectiveManager.IsCurrentOrder<AIObjectiveCleanupItems>()
SpeakIfFails = !objectiveManager.IsCurrentOrder<AIObjectiveCleanupItems>(),
endNodeFilter = n => Vector2.DistanceSquared(n.Waypoint.WorldPosition, container.Item.WorldPosition) <= MathUtils.Pow2(AIObjectiveGetItem.DefaultReach)
},
onAbandon: () => Abandon = true,
onCompleted: () => RemoveSubObjective(ref goToObjective));
@@ -244,7 +246,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);
}
@@ -19,7 +19,6 @@ namespace Barotrauma
private AIObjectiveGetItem getExtinguisherObjective;
private AIObjectiveGoTo gotoObjective;
private float useExtinquisherTimer;
public AIObjectiveExtinguishFire(Character character, Hull targetHull, AIObjectiveManager objectiveManager, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier)
@@ -44,7 +43,8 @@ namespace Barotrauma
}
else
{
float yDist = Math.Abs(character.WorldPosition.Y - targetHull.WorldPosition.Y);
float characterY = character.CurrentHull?.WorldPosition.Y ?? character.WorldPosition.Y;
float yDist = Math.Abs(characterY - targetHull.WorldPosition.Y);
yDist = yDist > 100 ? yDist * 3 : 0;
float dist = Math.Abs(character.WorldPosition.X - targetHull.WorldPosition.X) + yDist;
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, dist));
@@ -119,24 +119,18 @@ namespace Barotrauma
Abandon = true;
break;
}
float xDist = Math.Abs(character.WorldPosition.X - fs.WorldPosition.X) - fs.DamageRange;
float yDist = Math.Abs(character.WorldPosition.Y - fs.WorldPosition.Y);
bool inRange = xDist + yDist < extinguisher.Range;
// Use the hull position, because the fire x pos is sometimes inside a wall -> the bot can't ever see it and continues running towards the wall.
ISpatialEntity lookTarget = character.CurrentHull == targetHull || character.CurrentHull.linkedTo.Contains(targetHull) ? targetHull : fs as ISpatialEntity;
bool move = !inRange || !character.CanSeeTarget(lookTarget);
if ((inRange && character.CanSeeTarget(lookTarget)) || useExtinquisherTimer > 0)
float xDist = Math.Abs(character.WorldPosition.X - fs.WorldPosition.X);
float yDist = Math.Abs(character.CurrentHull.WorldPosition.Y - targetHull.WorldPosition.Y);
float dist = xDist + yDist;
bool inRange = dist < extinguisher.Range;
bool isInDamageRange = fs.IsInDamageRange(character, fs.DamageRange) && character.CanSeeTarget(targetHull);
bool moveCloser = !isInDamageRange && (!inRange || !character.CanSeeTarget(targetHull));
bool operateExtinguisher = !moveCloser || (dist < extinguisher.Range * 1.2f && character.CanSeeTarget(targetHull));
if (operateExtinguisher)
{
useExtinquisherTimer += deltaTime;
if (useExtinquisherTimer > 2.0f)
{
useExtinquisherTimer = 0.0f;
}
// Aim
character.CursorPosition = fs.Position;
Vector2 fromCharacterToFireSource = fs.WorldPosition - character.WorldPosition;
float dist = fromCharacterToFireSource.Length();
character.CursorPosition += VectorExtensions.Forward(extinguisherItem.body.TransformedRotation + (float)Math.Sin(sinTime) / 2, dist / 2);
character.CursorPosition += VectorExtensions.Forward(extinguisherItem.body.TransformedRotation + (float)Math.Sin(sinTime) / 2, fromCharacterToFireSource.Length() / 2);
if (extinguisherItem.RequireAimToUse)
{
character.SetInput(InputType.Aim, false, true);
@@ -148,25 +142,29 @@ namespace Barotrauma
{
character.Speak(TextManager.GetWithVariable("DialogPutOutFire", "[roomname]", targetHull.DisplayName, FormatCapitals.Yes).Value, null, 0, "putoutfire".ToIdentifier(), 10.0f);
}
// Prevents running into the flames.
objectiveManager.CurrentObjective.ForceWalk = true;
}
if (move)
if (moveCloser)
{
//go to the first firesource
if (TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(fs, character, objectiveManager, closeEnough: Math.Max(fs.DamageRange, extinguisher.Range * 0.7f))
{
DialogueIdentifier = "dialogcannotreachfire".ToIdentifier(),
TargetName = fs.Hull.DisplayName
},
onAbandon: () => Abandon = true,
onCompleted: () => RemoveSubObjective(ref gotoObjective)))
if (TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(fs, character, objectiveManager, closeEnough: extinguisher.Range * 0.8f)
{
DialogueIdentifier = "dialogcannotreachfire".ToIdentifier(),
TargetName = fs.Hull.DisplayName,
},
onAbandon: () => Abandon = true,
onCompleted: () => RemoveSubObjective(ref gotoObjective)))
{
gotoObjective.requiredCondition = () => character.CanSeeTarget(targetHull);
}
}
else
else if (!operateExtinguisher || isInDamageRange)
{
character.AIController.SteeringManager.Reset();
// Don't walk into the flames.
RemoveSubObjective(ref gotoObjective);
SteeringManager.Reset();
}
// Only target one fire source at the time.
break;
}
}
@@ -177,8 +175,20 @@ namespace Barotrauma
base.Reset();
getExtinguisherObjective = null;
gotoObjective = null;
useExtinquisherTimer = 0;
sinTime = 0;
SteeringManager.Reset();
}
protected override void OnCompleted()
{
base.OnCompleted();
SteeringManager.Reset();
}
protected override void OnAbandon()
{
base.OnAbandon();
SteeringManager.Reset();
}
}
}
@@ -47,31 +47,25 @@ namespace Barotrauma
}
if (character.CurrentHull == null)
{
if (!character.NeedsAir)
{
Priority = 0;
}
else
{
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))
&& HumanAIController.HasDivingSuit(character) ? 0 : 100;
}
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))
&& character.IsProtectedFromPressure ? 0 : 100;
}
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 && !AIController.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
@@ -117,6 +111,11 @@ namespace Barotrauma
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
{
Priority -= priorityDecrease * deltaTime;
if (currenthullSafety >= 100)
{
// Reduce the priority to zero so that the bot can get switch to other objectives immediately, e.g. when entering the airlock.
Priority = 0;
}
}
else
{
@@ -137,12 +136,14 @@ namespace Barotrauma
private float retryTimer;
protected override void Act(float deltaTime)
{
if (resetPriority) { return; }
var currentHull = character.CurrentHull;
bool dangerousPressure = currentHull == null || currentHull.LethalPressure > 0 && character.PressureProtection <= 0;
if (!character.LockHands && (!dangerousPressure || cannotFindSafeHull))
bool dangerousPressure = !character.IsProtectedFromPressure && (currentHull == null || currentHull.LethalPressure > 0);
bool shouldActOnSuffocation = character.IsLowInOxygen && !character.AnimController.HeadInWater && HumanAIController.HasDivingSuit(character, requireOxygenTank: false);
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));
@@ -218,7 +219,11 @@ namespace Barotrauma
TryAddSubObjective(ref goToObjective,
constructor: () => new AIObjectiveGoTo(currentSafeHull, character, objectiveManager, getDivingGearIfNeeded: true)
{
AllowGoingOutside = HumanAIController.HasDivingSuit(character, conditionPercentage: 50)
AllowGoingOutside =
character.IsProtectedFromPressure ||
character.CurrentHull == null ||
character.CurrentHull.IsTaggedAirlock() ||
character.CurrentHull.LeadsOutside(character)
},
onCompleted: () =>
{
@@ -349,8 +354,8 @@ namespace Barotrauma
//tends to make the method much faster, because we find a potential hull earlier and can discard further-away hulls more easily
//(for instance, an NPC in an outpost might otherwise go through all the hulls in the main sub first and do tons of expensive
//path calculations, only to discard all of them when going through the hulls in the outpost)
float hullSuitability = EstimateHullSuitability(character, hull);
if (!hulls.Any())
float hullSuitability = EstimateHullSuitability(character, hull);
if (hulls.None())
{
hulls.Add(hull);
}
@@ -445,9 +450,12 @@ namespace Barotrauma
{
hullSafety = 100;
}
float characterY = character.CurrentHull?.WorldPosition.Y ?? character.WorldPosition.Y;
float yDist = Math.Abs(characterY - potentialHull.WorldPosition.Y);
yDist = yDist > 100 ? yDist * 3 : 0;
float distance = Math.Abs(character.WorldPosition.X - potentialHull.WorldPosition.X) + yDist;
// Huge preference for closer targets
float distance = Vector2.DistanceSquared(character.WorldPosition, potentialHull.WorldPosition);
float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, MathUtils.Pow(100000, 2), distance));
float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, 10000, distance));
hullSafety *= distanceFactor;
// If the target is not inside a friendly submarine, considerably reduce the hull safety.
// Intentionally exclude wrecks from this check
@@ -155,17 +155,21 @@ namespace Barotrauma
bool canOperate = toLeak.LengthSquared() < reach * reach;
if (canOperate)
{
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: Identifier.Empty, requireEquip: true, operateTarget: Leak),
onAbandon: () => Abandon = true,
onCompleted: () =>
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: Identifier.Empty, requireEquip: true, operateTarget: Leak)
{
// Use an empty filter to override the default
EndNodeFilter = n => true
},
onAbandon: () => Abandon = true,
onCompleted: () =>
{
if (CheckObjectiveSpecific()) { IsCompleted = true; }
else
{
if (CheckObjectiveSpecific()) { IsCompleted = true; }
else
{
// Failed to operate. Probably too far.
Abandon = true;
}
});
// Failed to operate. Probably too far.
Abandon = true;
}
});
}
else
{
@@ -178,7 +182,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 +201,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; }
@@ -123,6 +123,11 @@ namespace Barotrauma
return ignoredTags;
}
public static Func<PathNode, bool> CreateEndNodeFilter(ISpatialEntity targetEntity)
{
return n => (n.Waypoint.Ladders == null || n.Waypoint.IsInWater) && Vector2.DistanceSquared(n.Waypoint.WorldPosition, targetEntity.WorldPosition) <= MathUtils.Pow2(DefaultReach);
}
private bool CheckInventory()
{
if (IdentifiersOrTags == null) { return false; }
@@ -155,11 +160,6 @@ namespace Barotrauma
Abandon = true;
return;
}
if (character.Submarine == null)
{
Abandon = true;
return;
}
if (IdentifiersOrTags != null && !isDoneSeeking)
{
if (checkInventory)
@@ -171,9 +171,14 @@ namespace Barotrauma
}
if (!isDoneSeeking)
{
if (character.Submarine == null)
{
Abandon = true;
return;
}
if (!AllowDangerousPressure)
{
bool dangerousPressure = character.CurrentHull == null || character.CurrentHull.LethalPressure > 0 && character.PressureProtection <= 0;
bool dangerousPressure = !character.IsProtectedFromPressure && (character.CurrentHull == null || character.CurrentHull.LethalPressure > 0);
if (dangerousPressure)
{
#if DEBUG
@@ -192,6 +197,11 @@ namespace Barotrauma
return;
}
}
else if (character.Submarine == null)
{
Abandon = true;
return;
}
if (targetItem == null || targetItem.Removed)
{
#if DEBUG
@@ -307,7 +317,8 @@ namespace Barotrauma
{
// If the root container changes, the item is no longer where it was (taken by someone -> need to find another item)
AbortCondition = obj => targetItem == null || targetItem.GetRootInventoryOwner() != moveToTarget,
SpeakIfFails = false
SpeakIfFails = false,
endNodeFilter = CreateEndNodeFilter(moveToTarget)
};
},
onAbandon: () =>
@@ -391,10 +402,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 +524,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 +572,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
},
@@ -50,6 +50,7 @@ namespace Barotrauma
private readonly float minDistance = 50;
private readonly float seekGapsInterval = 1;
private float seekGapsTimer;
private bool cantFindDivingGear;
/// <summary>
/// Display units
@@ -90,7 +91,7 @@ namespace Barotrauma
/// </summary>
public bool UseDistanceRelativeToAimSourcePos { get; set; } = false;
public override bool AbandonWhenCannotCompleteSubjectives => !repeat;
public override bool AbandonWhenCannotCompleteSubjectives => false;
public override bool AllowOutsideSubmarine => AllowGoingOutside;
public override bool AllowInAnySub => true;
@@ -263,48 +264,73 @@ namespace Barotrauma
}
if (!Abandon)
{
if (getDivingGearIfNeeded && !character.LockHands)
if (getDivingGearIfNeeded)
{
Character followTarget = Target as Character;
bool needsDivingSuit = (!isInside || hasOutdoorNodes) && character.NeedsAir && !character.HasAbilityFlag(AbilityFlags.ImmuneToPressure);
bool needsDivingGear = needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit);
if (Mimic)
bool needsDivingSuit = (!isInside || hasOutdoorNodes) && !character.IsImmuneToPressure;
bool tryToGetDivingGear = needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit);
bool tryToGetDivingSuit = needsDivingSuit;
if (Mimic && !character.IsImmuneToPressure)
{
if (HumanAIController.HasDivingSuit(followTarget))
{
needsDivingGear = true;
needsDivingSuit = true;
tryToGetDivingGear = true;
tryToGetDivingSuit = true;
}
else if (HumanAIController.HasDivingMask(followTarget))
else if (HumanAIController.HasDivingMask(followTarget) && character.CharacterHealth.OxygenLowResistance < 1)
{
needsDivingGear = true;
tryToGetDivingGear = true;
}
}
bool needsEquipment = false;
float minOxygen = AIObjectiveFindDivingGear.GetMinOxygen(character);
if (needsDivingSuit)
if (tryToGetDivingSuit)
{
needsEquipment = !HumanAIController.HasDivingSuit(character, minOxygen);
}
else if (needsDivingGear)
else if (tryToGetDivingGear)
{
needsEquipment = !HumanAIController.HasDivingGear(character, minOxygen);
}
if (needsEquipment)
if (character.LockHands)
{
cantFindDivingGear = true;
}
if (cantFindDivingGear && needsDivingSuit)
{
// Don't try to reach the target without a suit because it's lethal.
Abandon = true;
return;
}
if (needsEquipment && !cantFindDivingGear)
{
SteeringManager.Reset();
if (findDivingGear != null && !findDivingGear.CanBeCompleted)
{
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: false, objectiveManager),
onAbandon: () => Abandon = true,
onCompleted: () => RemoveSubObjective(ref findDivingGear));
}
else
{
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager),
onAbandon: () => Abandon = true,
onCompleted: () => RemoveSubObjective(ref findDivingGear));
}
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: tryToGetDivingSuit, objectiveManager),
onAbandon: () =>
{
cantFindDivingGear = true;
if (needsDivingSuit)
{
// Shouldn't try to reach the target without a suit, because it's lethal.
Abandon = true;
}
else
{
// Try again without requiring the diving suit
RemoveSubObjective(ref findDivingGear);
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: false, objectiveManager),
onAbandon: () =>
{
Abandon = character.CurrentHull != null && (objectiveManager.CurrentOrder != this || Target.Submarine == null);
RemoveSubObjective(ref findDivingGear);
},
onCompleted: () =>
{
RemoveSubObjective(ref findDivingGear);
});
}
},
onCompleted: () => RemoveSubObjective(ref findDivingGear));
return;
}
}
@@ -170,7 +170,8 @@ namespace Barotrauma
TargetHull = character.CurrentHull;
}
if (behavior == BehaviorType.StayInHull)
bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) || (PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)));
if (behavior == BehaviorType.StayInHull && !currentTargetIsInvalid)
{
currentTarget = TargetHull;
bool stayInHull = character.CurrentHull == currentTarget && IsSteeringFinished() && !character.IsClimbing;
@@ -190,9 +191,6 @@ namespace Barotrauma
}
else
{
bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) ||
(PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)));
if (currentTarget != null && !currentTargetIsInvalid)
{
if (character.TeamID == CharacterTeamType.FriendlyNPC && !character.IsEscorted)
@@ -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));
@@ -23,6 +23,11 @@ namespace Barotrauma
private AIObjectiveGoTo goToObjective;
private AIObjectiveGetItem getItemObjective;
/// <summary>
/// If undefined, a default filter will be used.
/// </summary>
public Func<PathNode, bool> EndNodeFilter;
public bool Override { get; set; } = true;
public override bool CanBeCompleted => base.CanBeCompleted && (!useController || controller != null);
@@ -232,7 +237,7 @@ namespace Barotrauma
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(target.Item, character, objectiveManager, closeEnough: 50)
{
TargetName = target.Item.Name,
endNodeFilter = node => node.Waypoint.Ladders == null
endNodeFilter = EndNodeFilter ?? AIObjectiveGetItem.CreateEndNodeFilter(target.Item)
},
onAbandon: () => Abandon = true,
onCompleted: () => RemoveSubObjective(ref goToObjective));
@@ -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,
@@ -17,7 +17,6 @@ namespace Barotrauma
private AIObjectiveGoTo goToObjective;
private AIObjectiveContainItem refuelObjective;
private float previousCondition = -1;
private RepairTool repairTool;
private const float WaitTimeBeforeRepair = 0.5f;
@@ -196,15 +195,7 @@ namespace Barotrauma
Abandon = true;
}
}
if (previousCondition == -1)
{
previousCondition = Item.Condition;
}
else if (Item.Condition < previousCondition)
{
// If the current condition is less than the previous condition, we can't complete the task, so let's abandon it. The item is probably deteriorating at a greater speed than we can repair it.
Abandon = true;
}
CheckPreviousCondition(deltaTime);
}
if (Abandon)
{
@@ -229,7 +220,6 @@ namespace Barotrauma
TryAddSubObjective(ref goToObjective,
constructor: () =>
{
previousCondition = -1;
var objective = new AIObjectiveGoTo(Item, character, objectiveManager)
{
TargetName = Item.Name
@@ -251,6 +241,27 @@ namespace Barotrauma
}
}
private const float conditionCheckDelay = 1;
private float conditionCheckTimer;
private float previousCondition;
private void CheckPreviousCondition(float deltaTime)
{
if (Item == null || Item.Removed) { return; }
conditionCheckTimer -= deltaTime;
if (conditionCheckTimer > 0) { return; }
conditionCheckTimer = conditionCheckDelay;
if (previousCondition > -1 && Item.Condition < previousCondition)
{
// If the current condition is less than the previous condition, we can't complete the task, so let's abandon it. The item is probably deteriorating at a greater speed than we can repair it.
Abandon = true;
}
else
{
// If the previous condition is not yet stored or if it's valid (greater or equal to current condition), save the condition for the next check here.
previousCondition = Item.Condition;
}
}
private void FindRepairTool()
{
foreach (Repairable repairable in Item.Repairables)
@@ -303,7 +314,6 @@ namespace Barotrauma
base.Reset();
goToObjective = null;
refuelObjective = null;
previousCondition = -1;
repairTool = null;
}
}
@@ -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)
{
@@ -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 == AfflictionPrefab.ParalysisType)
{
vitality -= affliction.Strength;
}
else if (affliction.Prefab.AfflictionType == AfflictionPrefab.PoisonType)
{
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);
@@ -12,6 +12,9 @@ namespace Barotrauma
private AIObjectiveGoTo moveInsideObjective, moveOutsideObjective;
private bool usingEscapeBehavior, isSteeringThroughGap;
public override bool AllowOutsideSubmarine => true;
public override bool AllowInAnySub => true;
public AIObjectiveReturn(Character character, Character orderGiver, AIObjectiveManager objectiveManager, float priorityModifier = 1.0f) : base(character, objectiveManager, priorityModifier)
{
ReturnTarget = GetReturnTarget(Submarine.MainSubs) ?? GetReturnTarget(Submarine.Loaded);
@@ -27,6 +27,15 @@ namespace Barotrauma
turrets.Add(turret);
// Set false, because we manage the turrets in the Update method.
turret.AutoOperate = false;
// Set to full condition, because items don't work when they are broken.
turret.Item.Condition = turret.Item.MaxCondition;
foreach (MapEntity linkedEntity in turret.Item.linkedTo)
{
if (linkedEntity is Item linkedItem)
{
linkedItem.Condition = linkedItem.MaxCondition;
}
}
}
}
LoadAllTurrets();
@@ -264,27 +273,52 @@ namespace Barotrauma
}
}
destroyedOrgans.ForEach(o => spawnOrgans.Remove(o));
bool someoneNearby = false;
bool isSomeoneNearby = false;
float minDist = Sonar.DefaultSonarRange * 2.0f;
foreach (Submarine submarine in Submarine.Loaded)
#if SERVER
foreach (var client in GameMain.Server.ConnectedClients)
{
if (submarine.Info.Type != SubmarineType.Player) { continue; }
if (Vector2.DistanceSquared(submarine.WorldPosition, Submarine.WorldPosition) < minDist * minDist)
var spectatePos = client.SpectatePos;
if (spectatePos.HasValue)
{
someoneNearby = true;
break;
if (IsCloseEnough(spectatePos.Value, minDist))
{
isSomeoneNearby = true;
break;
}
}
}
foreach (Character c in Character.CharacterList)
#else
if (IsCloseEnough(GameMain.GameScreen.Cam.Position, minDist))
{
if (c != Character.Controlled && !c.IsRemotePlayer) { continue; }
if (Vector2.DistanceSquared(c.WorldPosition, Submarine.WorldPosition) < minDist * minDist)
isSomeoneNearby = true;
}
#endif
if (!isSomeoneNearby)
{
foreach (Submarine submarine in Submarine.Loaded)
{
someoneNearby = true;
break;
if (submarine.Info.Type != SubmarineType.Player) { continue; }
if (IsCloseEnough(submarine.WorldPosition, minDist))
{
isSomeoneNearby = true;
break;
}
}
}
if (!someoneNearby) { return; }
if (!isSomeoneNearby)
{
foreach (Character c in Character.CharacterList)
{
if (!c.IsPlayer && !c.IsOnPlayerTeam) { continue; }
if (IsCloseEnough(c.WorldPosition, minDist))
{
isSomeoneNearby = true;
break;
}
}
}
if (!isSomeoneNearby) { return; }
OperateTurrets(deltaTime, Config.Entity);
if (!IsClient)
{
@@ -292,6 +326,7 @@ namespace Barotrauma
UpdateReinforcements(deltaTime);
}
}
private bool IsCloseEnough(Vector2 targetPos, float minDist) => Vector2.DistanceSquared(targetPos, Submarine.WorldPosition) < minDist * minDist;
private void SpawnInitialCells()
{