v1.0.13.1 (first post-1.0 patch)
This commit is contained in:
@@ -506,15 +506,29 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
protected static bool CanEquip(Character character, Item item)
|
||||
protected static bool CanEquip(Character character, Item item, bool allowWearing)
|
||||
{
|
||||
bool canEquip = item != null;
|
||||
if (canEquip && !item.AllowedSlots.Contains(InvSlotType.Any))
|
||||
if (item == null) { return false; }
|
||||
bool canEquip = false;
|
||||
if (item.AllowedSlots.Contains(InvSlotType.Any))
|
||||
{
|
||||
if (character.Inventory.IsAnySlotAvailable(item))
|
||||
{
|
||||
canEquip = true;
|
||||
}
|
||||
}
|
||||
if (!canEquip)
|
||||
{
|
||||
canEquip = false;
|
||||
var inv = character.Inventory;
|
||||
foreach (var allowedSlot in item.AllowedSlots)
|
||||
{
|
||||
if (!allowWearing)
|
||||
{
|
||||
if (!allowedSlot.HasFlag(InvSlotType.RightHand) && !allowedSlot.HasFlag(InvSlotType.LeftHand))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
foreach (var slotType in inv.SlotTypes)
|
||||
{
|
||||
if (!allowedSlot.HasFlag(slotType)) { continue; }
|
||||
@@ -530,18 +544,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
return canEquip;
|
||||
}
|
||||
protected bool CheckItemIdentifiersOrTags(Item item, ImmutableHashSet<Identifier> identifiersOrTags)
|
||||
{
|
||||
if (identifiersOrTags.Contains(item.Prefab.Identifier)) { return true; }
|
||||
foreach (var identifier in identifiersOrTags)
|
||||
{
|
||||
if (item.HasTag(identifier)) { return true; }
|
||||
}
|
||||
return false;
|
||||
return canEquip && character.Inventory.CanBePut(item);
|
||||
}
|
||||
|
||||
protected bool CanEquip(Item item) => CanEquip(character, item);
|
||||
protected bool CanEquip(Item item, bool allowWearing) => CanEquip(character, item, allowWearing);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-5
@@ -64,7 +64,6 @@ namespace Barotrauma
|
||||
if (subObjectives.Any()) { return; }
|
||||
if (HumanAIController.FindSuitableContainer(character, item, ignoredContainers, ref itemIndex, out Item suitableContainer))
|
||||
{
|
||||
itemIndex = 0;
|
||||
if (suitableContainer != null)
|
||||
{
|
||||
bool equip = item.GetComponent<Holdable>() != null ||
|
||||
@@ -112,10 +111,7 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
|
||||
}
|
||||
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific()
|
||||
|
||||
+1
-1
@@ -121,7 +121,7 @@ namespace Barotrauma
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return CanEquip(character, item);
|
||||
return CanEquip(character, item, allowWearing: false);
|
||||
}
|
||||
|
||||
public override void OnDeselected()
|
||||
|
||||
+54
-40
@@ -170,13 +170,33 @@ namespace Barotrauma
|
||||
return Priority;
|
||||
}
|
||||
}
|
||||
float damageFactor = MathUtils.InverseLerp(0.0f, 5.0f, character.GetDamageDoneByAttacker(Enemy) / 100.0f);
|
||||
Priority = TargetEliminated ? 0 : Math.Min((95 + damageFactor) * PriorityModifier, 100);
|
||||
if (Priority > 0)
|
||||
if (TargetEliminated)
|
||||
{
|
||||
if (EnemyAIController.IsLatchedToSomeoneElse(Enemy, character))
|
||||
Priority = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 91-100
|
||||
float minPriority = AIObjectiveManager.EmergencyObjectivePriority + 1;
|
||||
float maxPriority = AIObjectiveManager.MaxObjectivePriority;
|
||||
float priorityScale = maxPriority - minPriority;
|
||||
float xDist = Math.Abs(character.WorldPosition.X - Enemy.WorldPosition.X);
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - Enemy.WorldPosition.Y);
|
||||
if (HumanAIController.VisibleHulls.Contains(Enemy.CurrentHull))
|
||||
{
|
||||
Priority = 0;
|
||||
xDist /= 2;
|
||||
yDist /= 2;
|
||||
}
|
||||
float distanceFactor = MathUtils.InverseLerp(3000, 0, xDist + yDist * 5);
|
||||
float devotion = CumulatedDevotion / 100;
|
||||
float additionalPriority = MathHelper.Lerp(0, priorityScale, Math.Clamp(devotion + distanceFactor, 0, 1));
|
||||
Priority = Math.Min((minPriority + additionalPriority) * PriorityModifier, maxPriority);
|
||||
if (Priority > 0)
|
||||
{
|
||||
if (EnemyAIController.IsLatchedToSomeoneElse(Enemy, character))
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Priority;
|
||||
@@ -312,12 +332,10 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
AskHelp();
|
||||
Retreat(deltaTime);
|
||||
}
|
||||
break;
|
||||
case CombatMode.Retreat:
|
||||
AskHelp();
|
||||
Retreat(deltaTime);
|
||||
break;
|
||||
default:
|
||||
@@ -352,7 +370,7 @@ namespace Barotrauma
|
||||
Weapon = null;
|
||||
continue;
|
||||
}
|
||||
if (WeaponComponent.IsNotEmpty(character))
|
||||
if (!WeaponComponent.IsEmpty(character))
|
||||
{
|
||||
// All good, the weapon is loaded
|
||||
break;
|
||||
@@ -470,7 +488,7 @@ namespace Barotrauma
|
||||
// Not in the inventory anymore or cannot find the weapon component
|
||||
return false;
|
||||
}
|
||||
if (!WeaponComponent.IsNotEmpty(character))
|
||||
if (WeaponComponent.IsEmpty(character))
|
||||
{
|
||||
// Try reloading (and seek ammo)
|
||||
if (!Reload(seekAmmo))
|
||||
@@ -541,7 +559,7 @@ namespace Barotrauma
|
||||
priority /= 2;
|
||||
}
|
||||
}
|
||||
if (!weapon.IsNotEmpty(character))
|
||||
if (weapon.IsEmpty(character))
|
||||
{
|
||||
if (weapon is RangedWeapon && !isAllowedToSeekWeapons)
|
||||
{
|
||||
@@ -554,7 +572,6 @@ namespace Barotrauma
|
||||
priority /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (Enemy.Params.Health.StunImmunity)
|
||||
{
|
||||
if (weapon.Item.HasTag("stunner"))
|
||||
@@ -750,7 +767,7 @@ namespace Barotrauma
|
||||
private bool Equip()
|
||||
{
|
||||
if (character.LockHands) { return false; }
|
||||
if (!WeaponComponent.HasRequiredContainedItems(character, addMessage: false))
|
||||
if (WeaponComponent.IsEmpty(character))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -783,6 +800,10 @@ namespace Barotrauma
|
||||
|
||||
private void Retreat(float deltaTime)
|
||||
{
|
||||
if (!Enemy.IsHuman)
|
||||
{
|
||||
SpeakRetreating();
|
||||
}
|
||||
RemoveFollowTarget();
|
||||
RemoveSubObjective(ref seekAmmunitionObjective);
|
||||
if (retreatObjective != null && retreatObjective.Target != retreatTarget)
|
||||
@@ -793,6 +814,7 @@ namespace Barotrauma
|
||||
{
|
||||
// Swim away
|
||||
SteeringManager.Reset();
|
||||
character.ReleaseSecondaryItem();
|
||||
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.WorldPosition - Enemy.WorldPosition));
|
||||
SteeringManager.SteeringAvoid(deltaTime, 5, weight: 2);
|
||||
return;
|
||||
@@ -819,7 +841,8 @@ namespace Barotrauma
|
||||
{
|
||||
TryAddSubObjective(ref retreatObjective, () => new AIObjectiveGoTo(retreatTarget, character, objectiveManager)
|
||||
{
|
||||
UsePathingOutside = false
|
||||
UsePathingOutside = false,
|
||||
SpeakIfFails = false
|
||||
},
|
||||
onAbandon: () =>
|
||||
{
|
||||
@@ -861,6 +884,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (sqrDistance > MathUtils.Pow2(meleeWeapon.Range))
|
||||
{
|
||||
character.ReleaseSecondaryItem();
|
||||
// Swim towards the target
|
||||
SteeringManager.Reset();
|
||||
SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Enemy), weight: 10);
|
||||
@@ -882,7 +906,8 @@ namespace Barotrauma
|
||||
UsePathingOutside = false,
|
||||
IgnoreIfTargetDead = true,
|
||||
TargetName = Enemy.DisplayName,
|
||||
AlwaysUseEuclideanDistance = false
|
||||
AlwaysUseEuclideanDistance = false,
|
||||
SpeakIfFails = false
|
||||
},
|
||||
onAbandon: () =>
|
||||
{
|
||||
@@ -966,7 +991,7 @@ namespace Barotrauma
|
||||
item.GetComponent<RangedWeapon>() != null)
|
||||
{
|
||||
item.Drop(character);
|
||||
character.Inventory.TryPutItem(item, character, CharacterInventory.anySlot);
|
||||
character.Inventory.TryPutItem(item, character, CharacterInventory.AnySlot);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1028,54 +1053,43 @@ namespace Barotrauma
|
||||
if (Weapon.OwnInventory == null) { return true; }
|
||||
// Eject empty ammo
|
||||
HumanAIController.UnequipEmptyItems(Weapon);
|
||||
RelatedItem item = null;
|
||||
Item ammunition = null;
|
||||
ImmutableHashSet<Identifier> ammunitionIdentifiers = null;
|
||||
if (WeaponComponent.requiredItems.ContainsKey(RelatedItem.RelationType.Contained))
|
||||
{
|
||||
foreach (RelatedItem requiredItem in WeaponComponent.requiredItems[RelatedItem.RelationType.Contained])
|
||||
{
|
||||
ammunition = Weapon.OwnInventory.AllItems.FirstOrDefault(it => it.Condition > 0 && requiredItem.MatchesItem(it));
|
||||
if (ammunition != null)
|
||||
{
|
||||
// Ammunition still remaining
|
||||
return true;
|
||||
}
|
||||
item = requiredItem;
|
||||
if (Weapon.OwnInventory.AllItems.Any(it => it.Condition > 0 && requiredItem.MatchesItem(it))) { continue; }
|
||||
ammunitionIdentifiers = requiredItem.Identifiers;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (WeaponComponent is MeleeWeapon meleeWeapon)
|
||||
{
|
||||
ammunitionIdentifiers = meleeWeapon.PreferredContainedItems;
|
||||
}
|
||||
|
||||
// No ammo
|
||||
if (ammunition == null)
|
||||
if (ammunitionIdentifiers != null)
|
||||
{
|
||||
if (ammunitionIdentifiers != null)
|
||||
// Try reload ammunition from inventory
|
||||
static bool IsInsideHeadset(Item i) => i.ParentInventory?.Owner is Item ownerItem && ownerItem.HasTag("mobileradio");
|
||||
Item ammunition = character.Inventory.FindItem(i => i.HasIdentifierOrTags(ammunitionIdentifiers) && i.Condition > 0 && !IsInsideHeadset(i), recursive: true);
|
||||
if (ammunition != null)
|
||||
{
|
||||
// Try reload ammunition from inventory
|
||||
static bool IsInsideHeadset(Item i) => i.ParentInventory?.Owner is Item ownerItem && ownerItem.HasTag("mobileradio");
|
||||
ammunition = character.Inventory.FindItem(i => CheckItemIdentifiersOrTags(i, ammunitionIdentifiers) && i.Condition > 0 && !IsInsideHeadset(i), recursive: true);
|
||||
if (ammunition != null)
|
||||
var container = Weapon.GetComponent<ItemContainer>();
|
||||
if (!container.Inventory.TryPutItem(ammunition, user: character))
|
||||
{
|
||||
var container = Weapon.GetComponent<ItemContainer>();
|
||||
if (!container.Inventory.TryPutItem(ammunition, null))
|
||||
if (ammunition.ParentInventory == character.Inventory)
|
||||
{
|
||||
if (ammunition.ParentInventory == character.Inventory)
|
||||
{
|
||||
ammunition.Drop(character);
|
||||
}
|
||||
ammunition.Drop(character);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (WeaponComponent.HasRequiredContainedItems(character, addMessage: false))
|
||||
if (!WeaponComponent.IsEmpty(character))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (ammunition == null && !HoldPosition && IsOffensiveOrArrest && seekAmmo && ammunitionIdentifiers != null)
|
||||
else if (!HoldPosition && IsOffensiveOrArrest && seekAmmo && ammunitionIdentifiers != null)
|
||||
{
|
||||
SeekAmmunition(ammunitionIdentifiers);
|
||||
}
|
||||
@@ -1270,7 +1284,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private void SpeakNoWeapons() => Speak("dialogcombatnoweapons".ToIdentifier(), delay: 0, minDuration: 30);
|
||||
private void AskHelp() => Speak("dialogcombatretreating".ToIdentifier(), delay: Rand.Range(0f, 1f), minDuration: 20);
|
||||
private void SpeakRetreating() => Speak("dialogcombatretreating".ToIdentifier(), delay: Rand.Range(0f, 1f), minDuration: 20);
|
||||
|
||||
private void Speak(Identifier textIdentifier, float delay, float minDuration)
|
||||
{
|
||||
|
||||
+5
-5
@@ -109,7 +109,7 @@ namespace Barotrauma
|
||||
|
||||
private bool CheckItem(Item item)
|
||||
{
|
||||
return CheckItemIdentifiersOrTags(item, itemIdentifiers) && item.ConditionPercentage >= ConditionLevel && item.HasAccess(character);
|
||||
return item.HasIdentifierOrTags(itemIdentifiers) && item.ConditionPercentage >= ConditionLevel && item.HasAccess(character);
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
@@ -156,15 +156,15 @@ namespace Barotrauma
|
||||
Inventory originalInventory = ItemToContain.ParentInventory;
|
||||
var slots = originalInventory?.FindIndices(ItemToContain);
|
||||
|
||||
static bool TryPutItem(Inventory inventory, int? targetSlot, Item itemToContain)
|
||||
bool TryPutItem(Inventory inventory, int? targetSlot, Item itemToContain)
|
||||
{
|
||||
if (targetSlot.HasValue)
|
||||
{
|
||||
return inventory.TryPutItem(itemToContain, targetSlot.Value, allowSwapping: false, allowCombine: false, user: null);
|
||||
return inventory.TryPutItem(itemToContain, targetSlot.Value, allowSwapping: false, allowCombine: false, user: character);
|
||||
}
|
||||
else
|
||||
{
|
||||
return inventory.TryPutItem(itemToContain, user: null);
|
||||
return inventory.TryPutItem(itemToContain, user: character);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ namespace Barotrauma
|
||||
ItemToContain == null || ItemToContain.Removed ||
|
||||
!ItemToContain.IsOwnedBy(character) || container.Item.GetRootInventoryOwner() is Character c && c != character,
|
||||
SpeakIfFails = !objectiveManager.IsCurrentOrder<AIObjectiveCleanupItems>(),
|
||||
endNodeFilter = n => Vector2.DistanceSquared(n.Waypoint.WorldPosition, container.Item.WorldPosition) <= MathUtils.Pow2(AIObjectiveGetItem.DefaultReach)
|
||||
endNodeFilter = n => Vector2.DistanceSquared(n.Waypoint.WorldPosition, container.Item.WorldPosition) <= MathUtils.Pow2(AIObjectiveGetItem.MaxReach)
|
||||
},
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () => RemoveSubObjective(ref goToObjective));
|
||||
|
||||
+1
-1
@@ -66,7 +66,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
float devotion = CumulatedDevotion / 100;
|
||||
Priority = MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
|
||||
Priority = MathHelper.Lerp(0, AIObjectiveManager.MaxObjectivePriority, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
|
||||
}
|
||||
}
|
||||
return Priority;
|
||||
|
||||
+9
-4
@@ -28,7 +28,7 @@ namespace Barotrauma
|
||||
if (character.IsSecurity) { return 100; }
|
||||
if (objectiveManager.IsOrder(this)) { return 100; }
|
||||
// If there's any security officers onboard, leave fighting for them.
|
||||
return HumanAIController.IsTrueForAnyCrewMember(c => c.Character.IsSecurity && !c.Character.IsIncapacitated && c.Character.Submarine == character.Submarine) ? 0 : 100;
|
||||
return HumanAIController.IsTrueForAnyCrewMember(c => c.IsSecurity, onlyActive: true, onlyConnectedSubs: true) ? 0 : 100;
|
||||
}
|
||||
|
||||
protected override AIObjective ObjectiveConstructor(Character target)
|
||||
@@ -37,8 +37,7 @@ namespace Barotrauma
|
||||
var combatObjective = new AIObjectiveCombat(character, target, combatMode, objectiveManager, PriorityModifier);
|
||||
if (character.TeamID == CharacterTeamType.FriendlyNPC && target.TeamID == CharacterTeamType.Team1 && GameMain.GameSession?.GameMode is CampaignMode campaign)
|
||||
{
|
||||
var reputation = campaign.Map?.CurrentLocation?.Reputation;
|
||||
if (reputation != null && reputation.NormalizedValue < Reputation.HostileThreshold)
|
||||
if (campaign.CurrentLocation is { IsFactionHostile: true })
|
||||
{
|
||||
combatObjective.holdFireCondition = () =>
|
||||
{
|
||||
@@ -66,7 +65,13 @@ namespace Barotrauma
|
||||
if (target.CurrentHull == null) { return false; }
|
||||
if (HumanAIController.IsFriendly(character, target)) { return false; }
|
||||
if (!character.Submarine.IsConnectedTo(target.Submarine)) { return false; }
|
||||
if (!targetCharactersInOtherSubs && character.Submarine.TeamID != target.Submarine.TeamID && character.Submarine.TeamID != character.OriginalTeamID) { return false; }
|
||||
if (!targetCharactersInOtherSubs)
|
||||
{
|
||||
if (character.Submarine.TeamID != target.Submarine.TeamID && character.OriginalTeamID != target.Submarine.TeamID)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (target.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { return false; }
|
||||
if (target.IsArrested) { return false; }
|
||||
if (EnemyAIController.IsLatchedToSomeoneElse(target, character)) { return false; }
|
||||
|
||||
+1
-1
@@ -79,7 +79,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (mask != targetItem)
|
||||
{
|
||||
character.Inventory.TryPutItem(mask, character, CharacterInventory.anySlot);
|
||||
character.Inventory.TryPutItem(mask, character, CharacterInventory.AnySlot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+25
-19
@@ -52,17 +52,14 @@ namespace Barotrauma
|
||||
objectiveManager.HasOrder<AIObjectiveReturn>(o => o.Priority > 0) ||
|
||||
objectiveManager.HasActiveObjective<AIObjectiveRescue>() ||
|
||||
objectiveManager.Objectives.Any(o => o is AIObjectiveCombat && o.Priority > 0))
|
||||
&& ((character.IsImmuneToPressure && !character.IsLowInOxygen)|| HumanAIController.HasDivingSuit(character)) ? 0 : 100;
|
||||
&& ((!character.IsLowInOxygen && character.IsImmuneToPressure)|| HumanAIController.HasDivingSuit(character)) ? 0 : AIObjectiveManager.EmergencyObjectivePriority - 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
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)))))
|
||||
NeedMoreDivingGear(character.CurrentHull, AIObjectiveFindDivingGear.GetMinOxygen(character)))
|
||||
{
|
||||
Priority = 100;
|
||||
Priority = AIObjectiveManager.MaxObjectivePriority;
|
||||
}
|
||||
else if ((objectiveManager.IsCurrentOrder<AIObjectiveGoTo>() || objectiveManager.IsCurrentOrder<AIObjectiveReturn>()) &&
|
||||
character.Submarine != null && !character.IsOnFriendlyTeam(character.Submarine.TeamID))
|
||||
@@ -75,11 +72,11 @@ namespace Barotrauma
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
Priority = MathHelper.Clamp(Priority, 0, 100);
|
||||
Priority = MathHelper.Clamp(Priority, 0, AIObjectiveManager.MaxObjectivePriority);
|
||||
if (divingGearObjective != null && !divingGearObjective.IsCompleted && divingGearObjective.CanBeCompleted)
|
||||
{
|
||||
// Boost the priority while seeking the diving gear
|
||||
Priority = Math.Max(Priority, Math.Min(AIObjectiveManager.HighestOrderPriority + 20, 100));
|
||||
Priority = Math.Max(Priority, Math.Min(AIObjectiveManager.EmergencyObjectivePriority - 1, AIObjectiveManager.MaxObjectivePriority));
|
||||
}
|
||||
}
|
||||
return Priority;
|
||||
@@ -111,7 +108,7 @@ namespace Barotrauma
|
||||
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
Priority -= priorityDecrease * deltaTime;
|
||||
if (currenthullSafety >= 100)
|
||||
if (currenthullSafety >= 100 && !character.IsLowInOxygen)
|
||||
{
|
||||
// Reduce the priority to zero so that the bot can get switch to other objectives immediately, e.g. when entering the airlock.
|
||||
Priority = 0;
|
||||
@@ -122,7 +119,7 @@ namespace Barotrauma
|
||||
float dangerFactor = (100 - currenthullSafety) / 100;
|
||||
Priority += dangerFactor * priorityIncrease * deltaTime;
|
||||
}
|
||||
Priority = MathHelper.Clamp(Priority, 0, 100);
|
||||
Priority = MathHelper.Clamp(Priority, 0, AIObjectiveManager.MaxObjectivePriority);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +135,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (resetPriority) { return; }
|
||||
var currentHull = character.CurrentHull;
|
||||
bool dangerousPressure = !character.IsProtectedFromPressure && (currentHull == null || currentHull.LethalPressure > 0);
|
||||
bool dangerousPressure = (currentHull == null || currentHull.LethalPressure > 0) && !character.IsProtectedFromPressure;
|
||||
bool shouldActOnSuffocation = character.IsLowInOxygen && !character.AnimController.HeadInWater && HumanAIController.HasDivingSuit(character, requireOxygenTank: false);
|
||||
if (!character.LockHands && (!dangerousPressure || shouldActOnSuffocation || cannotFindSafeHull))
|
||||
{
|
||||
@@ -200,16 +197,11 @@ namespace Barotrauma
|
||||
UpdateSimpleEscape(deltaTime);
|
||||
return;
|
||||
}
|
||||
|
||||
searchHullTimer = SearchHullInterval * Rand.Range(0.9f, 1.1f);
|
||||
previousSafeHull = currentSafeHull;
|
||||
currentSafeHull = potentialSafeHull;
|
||||
|
||||
cannotFindSafeHull = currentSafeHull == null || HumanAIController.NeedsDivingGear(currentSafeHull, out _);
|
||||
if (currentSafeHull == null)
|
||||
{
|
||||
currentSafeHull = previousSafeHull;
|
||||
}
|
||||
cannotFindSafeHull = currentSafeHull == null || NeedMoreDivingGear(currentSafeHull);
|
||||
currentSafeHull ??= previousSafeHull;
|
||||
if (currentSafeHull != null && currentSafeHull != currentHull)
|
||||
{
|
||||
if (goToObjective?.Target != currentSafeHull)
|
||||
@@ -219,6 +211,7 @@ namespace Barotrauma
|
||||
TryAddSubObjective(ref goToObjective,
|
||||
constructor: () => new AIObjectiveGoTo(currentSafeHull, character, objectiveManager, getDivingGearIfNeeded: true)
|
||||
{
|
||||
SpeakIfFails = false,
|
||||
AllowGoingOutside =
|
||||
character.IsProtectedFromPressure ||
|
||||
character.CurrentHull == null ||
|
||||
@@ -300,6 +293,7 @@ namespace Barotrauma
|
||||
//only move if we haven't reached the edge of the room
|
||||
if (escapeVel.X < 0 && character.Position.X > left || escapeVel.X > 0 && character.Position.X < right)
|
||||
{
|
||||
character.ReleaseSecondaryItem();
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
|
||||
}
|
||||
else
|
||||
@@ -349,7 +343,6 @@ namespace Barotrauma
|
||||
if (ignoredHulls != null && ignoredHulls.Contains(hull)) { continue; }
|
||||
if (HumanAIController.UnreachableHulls.Contains(hull)) { continue; }
|
||||
if (connectedSubs != null && !connectedSubs.Contains(hull.Submarine)) { continue; }
|
||||
|
||||
//sort the hulls based on distance and which sub they're in
|
||||
//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
|
||||
@@ -493,5 +486,18 @@ namespace Barotrauma
|
||||
cannotFindDivingGear = false;
|
||||
cannotFindSafeHull = false;
|
||||
}
|
||||
|
||||
private bool NeedMoreDivingGear(Hull targetHull, float minOxygen = 0)
|
||||
{
|
||||
if (!HumanAIController.NeedsDivingGear(targetHull, out bool needsSuit)) { return false; }
|
||||
if (needsSuit)
|
||||
{
|
||||
return !HumanAIController.HasDivingSuit(character, minOxygen);
|
||||
}
|
||||
else
|
||||
{
|
||||
return !HumanAIController.HasDivingGear(character, minOxygen);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+43
-27
@@ -37,40 +37,56 @@ namespace Barotrauma
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
return Priority;
|
||||
}
|
||||
else if (HumanAIController.IsTrueForAnyCrewMember(
|
||||
other => other != HumanAIController &&
|
||||
other.Character.IsBot &&
|
||||
other.ObjectiveManager.GetActiveObjective<AIObjectiveFixLeaks>() is AIObjectiveFixLeaks fixLeaks &&
|
||||
fixLeaks.SubObjectives.Any(so => so is AIObjectiveFixLeak fixObjective && fixObjective.Leak == Leak)))
|
||||
float coopMultiplier = 1;
|
||||
foreach (var c in Character.CharacterList)
|
||||
{
|
||||
Priority = 0;
|
||||
if (!HumanAIController.IsActive(c)) { continue; }
|
||||
if (c.TeamID != character.TeamID) { continue; }
|
||||
if (c == character) { continue; }
|
||||
if (c.IsPlayer) { continue; }
|
||||
if (c.AIController is HumanAIController otherAI )
|
||||
{
|
||||
if (otherAI.ObjectiveManager.GetFirstActiveObjective<AIObjectiveFixLeak>() is AIObjectiveFixLeak fixLeak)
|
||||
{
|
||||
if (fixLeak.Leak == Leak)
|
||||
{
|
||||
// Ignore leaks that others are already targeting
|
||||
Priority = 0;
|
||||
return Priority;
|
||||
}
|
||||
if (fixLeak.Leak.FlowTargetHull == Leak.FlowTargetHull)
|
||||
{
|
||||
// Reduce the priority of leaks that others should be targeting
|
||||
coopMultiplier = 0.1f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
float reduction = isPriority ? 1 : 2;
|
||||
float maxPriority = AIObjectiveManager.LowestOrderPriority - reduction;
|
||||
if (operateObjective != null && objectiveManager.GetFirstActiveObjective<AIObjectiveFixLeaks>() is AIObjectiveFixLeaks fixLeaks && fixLeaks.CurrentSubObjective == this)
|
||||
{
|
||||
// Prioritize leaks that we are already fixing
|
||||
Priority = maxPriority;
|
||||
}
|
||||
else
|
||||
{
|
||||
float reduction = isPriority ? 1 : 2;
|
||||
float maxPriority = AIObjectiveManager.LowestOrderPriority - reduction;
|
||||
if (operateObjective != null && objectiveManager.GetActiveObjective<AIObjectiveFixLeaks>() is AIObjectiveFixLeaks fixLeaks && fixLeaks.CurrentSubObjective == this)
|
||||
float xDist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X);
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y);
|
||||
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally).
|
||||
// If the target is close, ignore the distance factor alltogether so that we keep fixing the leaks that are nearby.
|
||||
float distanceFactor = isPriority || xDist < 200 && yDist < 100 ? 1 : MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 3000, xDist + yDist * 3.0f));
|
||||
if (Leak.linkedTo.Any(e => e is Hull h && h == character.CurrentHull))
|
||||
{
|
||||
// Prioritize leaks that we are already fixing
|
||||
Priority = maxPriority;
|
||||
}
|
||||
else
|
||||
{
|
||||
float xDist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X);
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y);
|
||||
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally).
|
||||
// If the target is close, ignore the distance factor alltogether so that we keep fixing the leaks that are nearby.
|
||||
float distanceFactor = isPriority || xDist < 200 && yDist < 100 ? 1 : MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 3000, xDist + yDist * 3.0f));
|
||||
if (Leak.linkedTo.Any(e => e is Hull h && h == character.CurrentHull))
|
||||
{
|
||||
// Double the distance when the leak can be accessed from the current hull.
|
||||
distanceFactor *= 2;
|
||||
}
|
||||
float severity = isPriority ? 1 : AIObjectiveFixLeaks.GetLeakSeverity(Leak) / 100;
|
||||
float devotion = CumulatedDevotion / 100;
|
||||
Priority = MathHelper.Lerp(0, maxPriority, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
|
||||
// Double the distance when the leak can be accessed from the current hull.
|
||||
distanceFactor *= 2;
|
||||
}
|
||||
float severity = isPriority ? 1 : AIObjectiveFixLeaks.GetLeakSeverity(Leak) / 100;
|
||||
float devotion = CumulatedDevotion / 100;
|
||||
Priority = MathHelper.Lerp(0, maxPriority, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier * coopMultiplier), 0, 1));
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
|
||||
+3
-3
@@ -38,9 +38,9 @@ namespace Barotrauma
|
||||
|
||||
protected override float TargetEvaluation()
|
||||
{
|
||||
int totalLeaks = Targets.Count();
|
||||
int totalLeaks = Targets.Count;
|
||||
if (totalLeaks == 0) { return 0; }
|
||||
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveFixLeaks>() && !c.Character.IsIncapacitated && c.Character.Submarine == character.Submarine, onlyBots: true);
|
||||
int otherFixers = HumanAIController.CountBotsInTheCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveFixLeaks>() && c.Character.Submarine == character.Submarine);
|
||||
bool anyFixers = otherFixers > 0;
|
||||
if (objectiveManager.IsOrder(this))
|
||||
{
|
||||
@@ -52,7 +52,7 @@ namespace Barotrauma
|
||||
int secondaryLeaks = Targets.Count(l => l.IsRoomToRoom);
|
||||
int leaks = totalLeaks - secondaryLeaks;
|
||||
float ratio = leaks == 0 ? 1 : anyFixers ? leaks / (float)otherFixers : 1;
|
||||
if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / (float)HumanAIController.CountCrew(onlyBots: true) > 0.75f))
|
||||
if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / (float)HumanAIController.CountBotsInTheCrew() > 0.75f))
|
||||
{
|
||||
// Enough fixers
|
||||
return 0;
|
||||
|
||||
+103
-32
@@ -1,9 +1,11 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -31,14 +33,15 @@ namespace Barotrauma
|
||||
private ISpatialEntity moveToTarget;
|
||||
private bool isDoneSeeking;
|
||||
public Item TargetItem => targetItem;
|
||||
private int currSearchIndex;
|
||||
private int currentSearchIndex;
|
||||
public ImmutableHashSet<Identifier> ignoredContainerIdentifiers;
|
||||
public ImmutableHashSet<Identifier> ignoredIdentifiersOrTags;
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private float currItemPriority;
|
||||
private readonly bool checkInventory;
|
||||
|
||||
public static float DefaultReach = 100;
|
||||
public const float DefaultReach = 100;
|
||||
public const float MaxReach = 150;
|
||||
|
||||
public bool AllowToFindDivingGear { get; set; } = true;
|
||||
public bool MustBeSpecificItem { get; set; }
|
||||
@@ -76,7 +79,7 @@ namespace Barotrauma
|
||||
public AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip = true, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
currSearchIndex = -1;
|
||||
currentSearchIndex = 0;
|
||||
Equip = equip;
|
||||
originalTarget = targetItem;
|
||||
this.targetItem = targetItem;
|
||||
@@ -89,7 +92,7 @@ namespace Barotrauma
|
||||
public AIObjectiveGetItem(Character character, IEnumerable<Identifier> identifiersOrTags, AIObjectiveManager objectiveManager, bool equip = true, bool checkInventory = true, float priorityModifier = 1, bool spawnItemIfNotFound = false)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
currSearchIndex = -1;
|
||||
currentSearchIndex = 0;
|
||||
Equip = equip;
|
||||
this.spawnItemIfNotFound = spawnItemIfNotFound;
|
||||
this.checkInventory = checkInventory;
|
||||
@@ -125,7 +128,7 @@ namespace Barotrauma
|
||||
|
||||
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);
|
||||
return n => (n.Waypoint.Ladders == null || n.Waypoint.IsInWater) && Vector2.DistanceSquared(n.Waypoint.WorldPosition, targetEntity.WorldPosition) <= MathUtils.Pow2(MaxReach);
|
||||
}
|
||||
|
||||
private bool CheckInventory()
|
||||
@@ -246,7 +249,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
character.SelectCharacter(c);
|
||||
canInteract = character.CanInteractWith(c, maxDist: DefaultReach);
|
||||
canInteract = character.CanInteractWith(c);
|
||||
character.DeselectCharacter();
|
||||
}
|
||||
}
|
||||
@@ -268,7 +271,7 @@ namespace Barotrauma
|
||||
|
||||
Inventory itemInventory = targetItem.ParentInventory;
|
||||
var slots = itemInventory?.FindIndices(targetItem);
|
||||
if (HumanAIController.TakeItem(targetItem, character.Inventory, Equip, Wear, storeUnequipped: true))
|
||||
if (HumanAIController.TakeItem(targetItem, character.Inventory, Equip, Wear, storeUnequipped: true, targetTags: IdentifiersOrTags))
|
||||
{
|
||||
if (TakeWholeStack && slots != null)
|
||||
{
|
||||
@@ -298,8 +301,11 @@ namespace Barotrauma
|
||||
if (!Equip)
|
||||
{
|
||||
// Try equipping and wearing the item
|
||||
Wear = true;
|
||||
Equip = true;
|
||||
if (!objectiveManager.HasActiveObjective<AIObjectiveCleanupItem>() && !objectiveManager.HasActiveObjective<AIObjectiveLoadItem>())
|
||||
{
|
||||
Wear = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
#if DEBUG
|
||||
@@ -342,6 +348,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private Stopwatch sw;
|
||||
private Stopwatch StopWatch => sw ??= new Stopwatch();
|
||||
private readonly List<(Item item, float priority)> itemCandidates = new List<(Item, float)>();
|
||||
private List<Item> itemList;
|
||||
private void FindTargetItem()
|
||||
{
|
||||
if (IdentifiersOrTags == null)
|
||||
@@ -349,13 +359,16 @@ namespace Barotrauma
|
||||
if (targetItem == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot find an item, because neither identifiers nor item was defined.", Color.Red);
|
||||
DebugConsole.AddWarning($"{character.Name}: Cannot find an item, because neither identifiers nor item was defined.");
|
||||
#endif
|
||||
Abandon = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (HumanAIController.DebugAI)
|
||||
{
|
||||
StopWatch.Restart();
|
||||
}
|
||||
float priority = Math.Clamp(objectiveManager.GetCurrentPriority(), 10, 100);
|
||||
if (!CheckPathForEachItem)
|
||||
{
|
||||
@@ -366,12 +379,22 @@ namespace Barotrauma
|
||||
CheckPathForEachItem = priority >= AIObjectiveManager.LowestOrderPriority && (objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>() || objectiveManager.CurrentOrder is AIObjectiveGoTo gotoOrder && gotoOrder.IsFollowOrderObjective);
|
||||
}
|
||||
bool checkPath = CheckPathForEachItem;
|
||||
bool hasCalledPathFinder = false;
|
||||
int itemsPerFrame = (int)priority;
|
||||
for (int i = 0; i < itemsPerFrame && currSearchIndex < Item.ItemList.Count - 1; i++)
|
||||
// Reset if the character has switched subs.
|
||||
if (itemList != null && !character.Submarine.IsEntityFoundOnThisSub(itemList.FirstOrDefault(), includingConnectedSubs: true))
|
||||
{
|
||||
currSearchIndex++;
|
||||
var item = Item.ItemList[currSearchIndex];
|
||||
currentSearchIndex = 0;
|
||||
}
|
||||
if (currentSearchIndex == 0)
|
||||
{
|
||||
itemCandidates.Clear();
|
||||
itemList = character.Submarine.GetItems(alsoFromConnectedSubs: true);
|
||||
}
|
||||
int itemsPerFrame = (int)MathHelper.Lerp(30, 300, MathUtils.InverseLerp(10, 100, priority));
|
||||
int checkedItems = 0;
|
||||
for (int i = 0; i < itemsPerFrame && currentSearchIndex < itemList.Count; i++, currentSearchIndex++)
|
||||
{
|
||||
checkedItems++;
|
||||
var item = itemList[currentSearchIndex];
|
||||
Submarine itemSub = item.Submarine ?? item.ParentInventory?.Owner?.Submarine;
|
||||
if (itemSub == null) { continue; }
|
||||
Submarine mySub = character.Submarine;
|
||||
@@ -395,8 +418,6 @@ namespace Barotrauma
|
||||
if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; }
|
||||
}
|
||||
}
|
||||
// Don't allow going into another sub, unless it's connected and of the same team and type.
|
||||
if (!character.Submarine.IsEntityFoundOnThisSub(item, includingConnectedSubs: true)) { continue; }
|
||||
if (character.IsItemTakenBySomeoneElse(item)) { continue; }
|
||||
if (item.ParentInventory is ItemInventory itemInventory)
|
||||
{
|
||||
@@ -411,11 +432,14 @@ namespace Barotrauma
|
||||
if (rootInventoryOwner is Item ownerItem)
|
||||
{
|
||||
if (!ownerItem.IsInteractable(character)) { continue; }
|
||||
if (!(ownerItem.GetComponent<ItemContainer>()?.HasRequiredItems(character, addMessage: false) ?? true)) { continue; }
|
||||
//the item is inside an item inside an item (e.g. fuel tank in a welding tool in a cabinet -> reduce priority to prefer items that aren't inside a tool)
|
||||
if (ownerItem != item.Container)
|
||||
if (ownerItem != item)
|
||||
{
|
||||
itemPriority *= 0.1f;
|
||||
if (!(ownerItem.GetComponent<ItemContainer>()?.HasRequiredItems(character, addMessage: false) ?? true)) { continue; }
|
||||
//the item is inside an item inside an item (e.g. fuel tank in a welding tool in a cabinet -> reduce priority to prefer items that aren't inside a tool)
|
||||
if (ownerItem != item.Container)
|
||||
{
|
||||
itemPriority *= 0.1f;
|
||||
}
|
||||
}
|
||||
}
|
||||
Vector2 itemPos = (rootInventoryOwner ?? item).WorldPosition;
|
||||
@@ -463,22 +487,69 @@ namespace Barotrauma
|
||||
{
|
||||
itemPriority *= item.Condition / item.MaxCondition;
|
||||
}
|
||||
if (checkPath)
|
||||
{
|
||||
itemCandidates.Add((item, itemPriority));
|
||||
}
|
||||
// Ignore if the item has a lower priority than the currently selected one
|
||||
if (itemPriority < currItemPriority) { continue; }
|
||||
if (!hasCalledPathFinder && PathSteering != null && checkPath)
|
||||
if (EvaluateCombatPriority && itemPriority <= 0)
|
||||
{
|
||||
hasCalledPathFinder = true;
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, item.SimPosition, character.Submarine, errorMsgStr: $"AIObjectiveGetItem {character.DisplayName}", nodeFilter: node => node.Waypoint.CurrentHull != null);
|
||||
if (path.Unreachable) { continue; }
|
||||
// Not good enough
|
||||
continue;
|
||||
}
|
||||
currItemPriority = itemPriority;
|
||||
targetItem = item;
|
||||
moveToTarget = rootInventoryOwner ?? item;
|
||||
}
|
||||
if (currSearchIndex >= Item.ItemList.Count - 1)
|
||||
if (currentSearchIndex >= itemList.Count - 1)
|
||||
{
|
||||
isDoneSeeking = true;
|
||||
if (targetItem == null)
|
||||
}
|
||||
if (checkedItems > 0)
|
||||
{
|
||||
if (isDoneSeeking && itemCandidates.Any())
|
||||
{
|
||||
itemCandidates.Sort((x, y) => y.priority.CompareTo(x.priority));
|
||||
}
|
||||
if (HumanAIController.DebugAI && targetItem != null && StopWatch.ElapsedMilliseconds > 2)
|
||||
{
|
||||
var msg = $"Went through {checkedItems} of total {itemList.Count} items. Found item {targetItem.Name} in {StopWatch.ElapsedMilliseconds} ms. Completed: {isDoneSeeking}";
|
||||
if (StopWatch.ElapsedMilliseconds > 5)
|
||||
{
|
||||
DebugConsole.ThrowError(msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
// An occasional warning now and then can be ignored, but multiple at the same time might indicate a performance issue.
|
||||
DebugConsole.AddWarning(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isDoneSeeking)
|
||||
{
|
||||
if (PathSteering == null)
|
||||
{
|
||||
itemCandidates.Clear();
|
||||
}
|
||||
if (itemCandidates.Any())
|
||||
{
|
||||
if (itemCandidates.FirstOrDefault() is { } itemCandidate)
|
||||
{
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, itemCandidate.item.SimPosition, character.Submarine, errorMsgStr: $"AIObjectiveGetItem {character.DisplayName}", nodeFilter: node => node.Waypoint.CurrentHull != null);
|
||||
if (path.Unreachable)
|
||||
{
|
||||
// Remove the invalid candidates and continue on the next frame.
|
||||
itemCandidates.Remove(itemCandidate);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The path was valid -> we are done.
|
||||
itemCandidates.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetItem == null && itemCandidates.None())
|
||||
{
|
||||
if (spawnItemIfNotFound)
|
||||
{
|
||||
@@ -569,11 +640,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (!item.HasAccess(character)) { return false; }
|
||||
if (ignoredItems.Contains(item)) { return false; };
|
||||
if (ignoredIdentifiersOrTags != null && CheckItemIdentifiersOrTags(item, ignoredIdentifiersOrTags)) { return false; }
|
||||
if (ignoredIdentifiersOrTags != null && item.HasIdentifierOrTags(ignoredIdentifiersOrTags)) { return false; }
|
||||
if (item.Condition < TargetCondition) { return false; }
|
||||
if (ItemFilter != null && !ItemFilter(item)) { 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));
|
||||
if (RequireNonEmpty && item.Components.Any(i => i.IsEmpty(character))) { return false; }
|
||||
return item.HasIdentifierOrTags(IdentifiersOrTags) || (AllowVariants && !item.Prefab.VariantOf.IsEmpty && IdentifiersOrTags.Contains(item.Prefab.VariantOf));
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
@@ -591,7 +662,7 @@ namespace Barotrauma
|
||||
targetItem = originalTarget;
|
||||
moveToTarget = targetItem?.GetRootInventoryOwner();
|
||||
isDoneSeeking = false;
|
||||
currSearchIndex = 0;
|
||||
currentSearchIndex = 0;
|
||||
currItemPriority = 0;
|
||||
}
|
||||
|
||||
|
||||
+38
-11
@@ -257,7 +257,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (HumanAIController.HasValidPath(requireNonDirty: true, requireUnfinished: false))
|
||||
else if (HumanAIController.HasValidPath(requireUnfinished: false))
|
||||
{
|
||||
waitUntilPathUnreachable = pathWaitingTime;
|
||||
}
|
||||
@@ -364,7 +364,8 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
bool isRuins = character.Submarine?.Info.IsRuin != null || Target.Submarine?.Info.IsRuin != null;
|
||||
if (!isRuins || !HumanAIController.HasValidPath(requireNonDirty: true, requireUnfinished: true))
|
||||
bool isEitherOneInside = isInside || Target.Submarine != null;
|
||||
if (isEitherOneInside && (!isRuins || !HumanAIController.HasValidPath()))
|
||||
{
|
||||
SeekGaps(maxGapDistance);
|
||||
seekGapsTimer = seekGapsInterval * Rand.Range(0.1f, 1.1f);
|
||||
@@ -388,6 +389,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TargetGap = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -436,9 +441,9 @@ namespace Barotrauma
|
||||
{
|
||||
var leftHandItem = character.GetEquippedItem(slotType: InvSlotType.LeftHand);
|
||||
var rightHandItem = character.GetEquippedItem(slotType: InvSlotType.RightHand);
|
||||
bool handsFull =
|
||||
(leftHandItem != null && character.Inventory.CheckIfAnySlotAvailable(leftHandItem, inWrongSlot: false) == -1) ||
|
||||
(rightHandItem != null && character.Inventory.CheckIfAnySlotAvailable(rightHandItem, inWrongSlot: false) == -1);
|
||||
bool handsFull =
|
||||
(leftHandItem != null && !character.Inventory.IsAnySlotAvailable(leftHandItem)) ||
|
||||
(rightHandItem != null && !character.Inventory.IsAnySlotAvailable(rightHandItem));
|
||||
if (!handsFull)
|
||||
{
|
||||
bool hasBattery = false;
|
||||
@@ -473,7 +478,7 @@ namespace Barotrauma
|
||||
// Try to switch batteries
|
||||
if (HumanAIController.HasItem(character, batteryTag, out IEnumerable<Item> batteries, conditionPercentage: 1, recursive: false))
|
||||
{
|
||||
scooter.ContainedItems.ForEachMod(emptyBattery => character.Inventory.TryPutItem(emptyBattery, character, CharacterInventory.anySlot));
|
||||
scooter.ContainedItems.ForEachMod(emptyBattery => character.Inventory.TryPutItem(emptyBattery, character, CharacterInventory.AnySlot));
|
||||
if (!scooter.Combine(batteries.OrderByDescending(b => b.Condition).First(), character))
|
||||
{
|
||||
useScooter = false;
|
||||
@@ -488,7 +493,7 @@ namespace Barotrauma
|
||||
if (!useScooter)
|
||||
{
|
||||
// Unequip
|
||||
character.Inventory.TryPutItem(scooter, character, CharacterInventory.anySlot);
|
||||
character.Inventory.TryPutItem(scooter, character, CharacterInventory.AnySlot);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -511,13 +516,20 @@ namespace Barotrauma
|
||||
{
|
||||
nodeFilter = n => n.Waypoint.CurrentHull != null;
|
||||
}
|
||||
else if (!isInside && HumanAIController.UseIndoorSteeringOutside)
|
||||
else if (!isInside)
|
||||
{
|
||||
nodeFilter = n => n.Waypoint.Submarine == null;
|
||||
if (HumanAIController.UseOutsideWaypoints)
|
||||
{
|
||||
nodeFilter = n => n.Waypoint.Submarine == null;
|
||||
}
|
||||
else
|
||||
{
|
||||
nodeFilter = n => n.Waypoint.Submarine != null || n.Waypoint.Ruin != null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isInside && !UsePathingOutside)
|
||||
{
|
||||
character.ReleaseSecondaryItem();
|
||||
PathSteering.SteeringSeekSimple(character.GetRelativeSimPosition(Target), 10);
|
||||
if (character.AnimController.InWater)
|
||||
{
|
||||
@@ -540,6 +552,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
character.ReleaseSecondaryItem();
|
||||
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(Target.WorldPosition - character.WorldPosition));
|
||||
if (character.AnimController.InWater)
|
||||
{
|
||||
@@ -560,6 +573,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
character.ReleaseSecondaryItem();
|
||||
SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Target), 10);
|
||||
if (character.AnimController.InWater)
|
||||
{
|
||||
@@ -573,6 +587,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!character.HasEquippedItem("scooter".ToIdentifier())) { return; }
|
||||
SteeringManager.Reset();
|
||||
character.ReleaseSecondaryItem();
|
||||
character.CursorPosition = targetWorldPos;
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
@@ -624,7 +639,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (target is Character c)
|
||||
{
|
||||
return c.CurrentHull;
|
||||
return c.CurrentHull ?? c.AnimController.CurrentHull;
|
||||
}
|
||||
else if (target is Structure structure)
|
||||
{
|
||||
@@ -772,6 +787,14 @@ namespace Barotrauma
|
||||
{
|
||||
StopMovement();
|
||||
HumanAIController.FaceTarget(Target);
|
||||
if (Target is WayPoint { Ladders: null })
|
||||
{
|
||||
// Release ladders when ordered to wait at a spawnpoint.
|
||||
// This is a special case specifically meant for NPCs that spawn in outposts with a wait order.
|
||||
// Otherwise they might keep holding to the ladders when the target is just next to it.
|
||||
// Releasing too early should be handled inside the IsCloseEnough property.
|
||||
character.ReleaseSecondaryItem();
|
||||
}
|
||||
base.OnCompleted();
|
||||
}
|
||||
|
||||
@@ -781,6 +804,10 @@ namespace Barotrauma
|
||||
findDivingGear = null;
|
||||
seekGapsTimer = 0;
|
||||
TargetGap = null;
|
||||
if (SteeringManager is IndoorsSteeringManager pathSteering)
|
||||
{
|
||||
pathSteering.ResetPath();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-9
@@ -163,15 +163,22 @@ namespace Barotrauma
|
||||
|
||||
character.SelectedItem = null;
|
||||
|
||||
CleanupItems(deltaTime);
|
||||
if (!character.IsClimbing)
|
||||
{
|
||||
CleanupItems(deltaTime);
|
||||
}
|
||||
|
||||
if (behavior == BehaviorType.StayInHull && TargetHull == null && character.CurrentHull != null)
|
||||
{
|
||||
TargetHull = character.CurrentHull;
|
||||
}
|
||||
|
||||
bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) || (PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)));
|
||||
if (behavior == BehaviorType.StayInHull && !currentTargetIsInvalid)
|
||||
bool currentTargetIsInvalid =
|
||||
currentTarget == null ||
|
||||
IsForbidden(currentTarget) ||
|
||||
(PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)));
|
||||
|
||||
if (behavior == BehaviorType.StayInHull && !currentTargetIsInvalid && !HumanAIController.UnsafeHulls.Contains(TargetHull))
|
||||
{
|
||||
currentTarget = TargetHull;
|
||||
bool stayInHull = character.CurrentHull == currentTarget && IsSteeringFinished() && !character.IsClimbing;
|
||||
@@ -305,12 +312,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (character.IsClimbing)
|
||||
{
|
||||
if (character.AnimController.GetHeightFromFloor() < 0.1f)
|
||||
{
|
||||
character.AnimController.Anim = AnimController.Animation.None;
|
||||
character.SelectedSecondaryItem = null;
|
||||
}
|
||||
return;
|
||||
PathSteering.Reset();
|
||||
character.StopClimbing();
|
||||
}
|
||||
var currentHull = character.CurrentHull;
|
||||
if (!character.AnimController.InWater && currentHull != null)
|
||||
@@ -362,6 +365,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
character.ReleaseSecondaryItem();
|
||||
PathSteering.SteeringManual(deltaTime, Vector2.Normalize(diff));
|
||||
}
|
||||
return;
|
||||
|
||||
+5
-13
@@ -24,7 +24,7 @@ namespace Barotrauma
|
||||
private ImmutableHashSet<Identifier> ValidContainableItemIdentifiers { get; }
|
||||
private static Dictionary<ItemPrefab, ImmutableHashSet<Identifier>> AllValidContainableItemIdentifiers { get; } = new Dictionary<ItemPrefab, ImmutableHashSet<Identifier>>();
|
||||
|
||||
private int itemIndex = 0;
|
||||
private int itemIndex;
|
||||
private AIObjectiveDecontainItem decontainObjective;
|
||||
private readonly HashSet<Item> ignoredItems = new HashSet<Item>();
|
||||
private Item targetItem;
|
||||
@@ -219,9 +219,8 @@ namespace Barotrauma
|
||||
return Priority;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
if (targetItem == null)
|
||||
{
|
||||
if (character.FindItem(ref itemIndex, out Item item, identifiers: ValidContainableItemIdentifiers, ignoreBroken: false, customPredicate: IsValidContainable, customPriorityFunction: GetPriority))
|
||||
@@ -233,6 +232,7 @@ namespace Barotrauma
|
||||
}
|
||||
targetItem = item;
|
||||
}
|
||||
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
|
||||
float GetPriority(Item item)
|
||||
{
|
||||
try
|
||||
@@ -256,11 +256,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (targetItem != null)
|
||||
else
|
||||
{
|
||||
if(decontainObjective == null && !IsValidContainable(targetItem))
|
||||
{
|
||||
@@ -290,10 +286,6 @@ namespace Barotrauma
|
||||
Reset();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValidContainable(Item item)
|
||||
@@ -310,7 +302,7 @@ namespace Barotrauma
|
||||
if (parentItem.HasTag("donttakeitems")) { return false; }
|
||||
}
|
||||
if (!item.HasAccess(character)) { return false; }
|
||||
if (!character.HasItem(item) && !CanEquip(item)) { return false; }
|
||||
if (!character.HasItem(item) && !CanEquip(item, allowWearing: false)) { return false; }
|
||||
if (!ItemContainer.CanBeContained(item)) { return false; }
|
||||
if (AIObjectiveLoadItems.ItemMatchesTargetCondition(item, TargetItemCondition)) { return false; }
|
||||
if (TargetItemCondition == AIObjectiveLoadItems.ItemCondition.Full)
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (item == null || item.Removed) { return false; }
|
||||
if (targetContainerTags.HasValue && !OrderPrefab.TargetItemsMatchItem(targetContainerTags.Value, item)) { return false; }
|
||||
if (!(item.GetComponent<ItemContainer>() is ItemContainer container)) { return false; }
|
||||
if ((item.GetComponent<ItemContainer>() is not ItemContainer container)) { return false; }
|
||||
if (container.Inventory == null) { return false; }
|
||||
if (targetCondition.HasValue && container.Inventory.IsFull() && container.Inventory.AllItems.None(i => ItemMatchesTargetCondition(i, targetCondition.Value))) { return false; }
|
||||
if (!AIObjectiveCleanupItems.IsItemInsideValidSubmarine(item, character)) { return false; }
|
||||
|
||||
+6
-6
@@ -89,12 +89,7 @@ namespace Barotrauma
|
||||
var target = objective.Key;
|
||||
if (!Targets.Contains(target))
|
||||
{
|
||||
var subObjective = objective.Value;
|
||||
if (CurrentSubObjective == subObjective)
|
||||
{
|
||||
CurrentSubObjective.Abandon = !CurrentSubObjective.IsCompleted;
|
||||
}
|
||||
subObjectives.Remove(subObjective);
|
||||
subObjectives.Remove(objective.Value);
|
||||
}
|
||||
}
|
||||
SyncRemovedObjectives(Objectives, GetList());
|
||||
@@ -157,6 +152,11 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
float max = AIObjectiveManager.LowestOrderPriority - 1;
|
||||
if (this is AIObjectiveRescueAll rescueObjective && rescueObjective.Targets.Contains(character))
|
||||
{
|
||||
// Allow higher prio
|
||||
max = AIObjectiveManager.EmergencyObjectivePriority;
|
||||
}
|
||||
float value = MathHelper.Clamp((CumulatedDevotion + (targetValue * PriorityModifier)) / 100, 0, 1);
|
||||
Priority = MathHelper.Lerp(0, max, value);
|
||||
}
|
||||
|
||||
+20
-7
@@ -20,6 +20,8 @@ namespace Barotrauma
|
||||
MaxValue = 2
|
||||
}
|
||||
|
||||
public const float MaxObjectivePriority = 100;
|
||||
public const float EmergencyObjectivePriority = 90;
|
||||
public const float HighestOrderPriority = 70;
|
||||
public const float LowestOrderPriority = 60;
|
||||
public const float RunPriority = 50;
|
||||
@@ -125,11 +127,21 @@ namespace Barotrauma
|
||||
{
|
||||
CoroutineManager.StopCoroutines(delayedObjective.Value);
|
||||
}
|
||||
|
||||
var prevIdleObjective = GetObjective<AIObjectiveIdle>();
|
||||
|
||||
DelayedObjectives.Clear();
|
||||
Objectives.Clear();
|
||||
FailedAutonomousObjectives = false;
|
||||
AddObjective(new AIObjectiveFindSafety(character, this));
|
||||
AddObjective(new AIObjectiveIdle(character, this));
|
||||
var newIdleObjective = new AIObjectiveIdle(character, this);
|
||||
if (prevIdleObjective != null)
|
||||
{
|
||||
newIdleObjective.TargetHull = prevIdleObjective.TargetHull;
|
||||
newIdleObjective.Behavior = prevIdleObjective.Behavior;
|
||||
prevIdleObjective.PreferredOutpostModuleTypes.ForEach(t => newIdleObjective.PreferredOutpostModuleTypes.Add(t));
|
||||
}
|
||||
AddObjective(newIdleObjective);
|
||||
int objectiveCount = Objectives.Count;
|
||||
foreach (var autonomousObjective in character.Info.Job.Prefab.AutonomousObjectives)
|
||||
{
|
||||
@@ -155,7 +167,7 @@ namespace Barotrauma
|
||||
AddObjective(objective, delay: Rand.Value() / 2);
|
||||
objectiveCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
_waitTimer = Math.Max(_waitTimer, Rand.Range(0.5f, 1f) * objectiveCount);
|
||||
}
|
||||
|
||||
@@ -410,7 +422,7 @@ namespace Barotrauma
|
||||
newObjective = new AIObjectiveGoTo(order.OrderGiver, character, this, repeat: true, priorityModifier: priorityModifier)
|
||||
{
|
||||
CloseEnough = Rand.Range(80f, 100f),
|
||||
CloseEnoughMultiplier = Math.Min(1 + HumanAIController.CountCrew(c => c.ObjectiveManager.HasOrder<AIObjectiveGoTo>(o => o.Target == order.OrderGiver), onlyBots: true) * Rand.Range(0.8f, 1f), 4),
|
||||
CloseEnoughMultiplier = Math.Min(1 + HumanAIController.CountBotsInTheCrew(c => c.ObjectiveManager.HasOrder<AIObjectiveGoTo>(o => o.Target == order.OrderGiver)) * Rand.Range(0.8f, 1f), 4),
|
||||
ExtraDistanceOutsideSub = 100,
|
||||
ExtraDistanceWhileSwimming = 100,
|
||||
AllowGoingOutside = true,
|
||||
@@ -633,10 +645,11 @@ namespace Barotrauma
|
||||
public AIObjective GetActiveObjective() => CurrentObjective?.GetActiveObjective();
|
||||
public T GetOrder<T>() where T : AIObjective => CurrentOrders.FirstOrDefault(o => o.Objective is T)?.Objective as T;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the last active objective of the specific type.
|
||||
/// </summary>
|
||||
public T GetActiveObjective<T>() where T : AIObjective => CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).LastOrDefault(so => so is T) as T;
|
||||
public T GetLastActiveObjective<T>() where T : AIObjective
|
||||
=> CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).LastOrDefault(so => so is T) as T;
|
||||
|
||||
public T GetFirstActiveObjective<T>() where T : AIObjective
|
||||
=> CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).FirstOrDefault(so => so is T) as T;
|
||||
|
||||
/// <summary>
|
||||
/// Returns all active objectives of the specific type. Creates a new collection -> don't use too frequently.
|
||||
|
||||
+2
-2
@@ -122,7 +122,7 @@ namespace Barotrauma
|
||||
else if (!isOrder)
|
||||
{
|
||||
var steering = component?.Item.GetComponent<Steering>();
|
||||
if (steering != null && (steering.AutoPilot || HumanAIController.IsTrueForAnyCrewMember(c => c != HumanAIController && c.Character.IsCaptain)))
|
||||
if (steering != null && (steering.AutoPilot || HumanAIController.IsTrueForAnyCrewMember(c => c != character && c.IsCaptain, onlyActive: true, onlyConnectedSubs: true)))
|
||||
{
|
||||
// Ignore if already set to autopilot or if there's a captain onboard
|
||||
Priority = 0;
|
||||
@@ -204,7 +204,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (operateTarget != null)
|
||||
{
|
||||
if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.Character.IsBot && other.ObjectiveManager.GetActiveObjective() is AIObjectiveOperateItem operateObjective && operateObjective.operateTarget == operateTarget))
|
||||
if (HumanAIController.IsTrueForAnyBotInTheCrew(other => other != HumanAIController && other.ObjectiveManager.GetActiveObjective() is AIObjectiveOperateItem operateObjective && operateObjective.operateTarget == operateTarget))
|
||||
{
|
||||
// Another crew member is already targeting this entity (leak).
|
||||
Abandon = true;
|
||||
|
||||
+14
-24
@@ -69,16 +69,6 @@ namespace Barotrauma
|
||||
if (subObjective != null && subObjective.IsCompleted)
|
||||
{
|
||||
Priority = 0;
|
||||
items.RemoveWhere(i => i == null || i.Removed || !i.IsOwnedBy(character));
|
||||
if (items.None())
|
||||
{
|
||||
Abandon = true;
|
||||
|
||||
}
|
||||
else if (items.Any(i => i.Components.Any(i => !i.IsNotEmpty(character))))
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
@@ -162,25 +152,25 @@ namespace Barotrauma
|
||||
};
|
||||
}
|
||||
if (!TryAddSubObjective(ref getSingleItemObjective, getItemConstructor,
|
||||
onCompleted: () =>
|
||||
{
|
||||
if (KeepActiveWhenReady)
|
||||
onCompleted: () =>
|
||||
{
|
||||
if (getSingleItemObjective != null)
|
||||
if (KeepActiveWhenReady)
|
||||
{
|
||||
var item = getSingleItemObjective?.TargetItem;
|
||||
if (item?.IsOwnedBy(character) != null)
|
||||
if (getSingleItemObjective != null)
|
||||
{
|
||||
items.Add(item);
|
||||
var item = getSingleItemObjective?.TargetItem;
|
||||
if (item?.IsOwnedBy(character) != null)
|
||||
{
|
||||
items.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IsCompleted = true;
|
||||
}
|
||||
},
|
||||
onAbandon: () => Abandon = true))
|
||||
else
|
||||
{
|
||||
IsCompleted = true;
|
||||
}
|
||||
},
|
||||
onAbandon: () => Abandon = true))
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
|
||||
+2
-2
@@ -102,7 +102,7 @@ namespace Barotrauma
|
||||
// Don't stop fixing until completely done
|
||||
return 100;
|
||||
}
|
||||
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveRepairItems>() && !c.Character.IsIncapacitated, onlyBots: true);
|
||||
int otherFixers = HumanAIController.CountBotsInTheCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveRepairItems>());
|
||||
int items = Targets.Count;
|
||||
if (items == 0)
|
||||
{
|
||||
@@ -116,7 +116,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / (float)HumanAIController.CountCrew(onlyBots: true) > 0.75f))
|
||||
if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / (float)HumanAIController.CountBotsInTheCrew() > 0.75f))
|
||||
{
|
||||
// Enough fixers
|
||||
return 0;
|
||||
|
||||
+34
-35
@@ -139,14 +139,14 @@ namespace Barotrauma
|
||||
recursive: true);
|
||||
}
|
||||
}
|
||||
if (character.Submarine != null)
|
||||
if (character.Submarine != null && targetCharacter.CurrentHull != null)
|
||||
{
|
||||
if (HumanAIController.GetHullSafety(targetCharacter.CurrentHull, targetCharacter) < HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
// Incapacitated target is not in a safe place -> Move to a safe place first
|
||||
if (character.SelectedCharacter != targetCharacter)
|
||||
{
|
||||
if (targetCharacter.CurrentHull != null && HumanAIController.VisibleHulls.Contains(targetCharacter.CurrentHull) && targetCharacter.CurrentHull.DisplayName != null)
|
||||
if (HumanAIController.VisibleHulls.Contains(targetCharacter.CurrentHull) && targetCharacter.CurrentHull.DisplayName != null)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariables("DialogFoundUnconsciousTarget",
|
||||
("[targetname]", targetCharacter.Name, FormatCapitals.No),
|
||||
@@ -293,6 +293,11 @@ namespace Barotrauma
|
||||
currentTreatmentSuitabilities[treatmentSuitability.Key] > bestSuitability)
|
||||
{
|
||||
Item matchingItem = character.Inventory.FindItemByIdentifier(treatmentSuitability.Key, true);
|
||||
//allow taking items from the target's inventory too if the target is unconscious
|
||||
if (matchingItem == null && targetCharacter.IsIncapacitated)
|
||||
{
|
||||
matchingItem ??= targetCharacter.Inventory?.FindItemByIdentifier(treatmentSuitability.Key, true);
|
||||
}
|
||||
if (matchingItem != null)
|
||||
{
|
||||
bestItem = matchingItem;
|
||||
@@ -379,24 +384,24 @@ namespace Barotrauma
|
||||
onAbandon: () =>
|
||||
{
|
||||
Abandon = true;
|
||||
if (character != targetCharacter && character.IsOnPlayerTeam)
|
||||
if (character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("dialogcannottreatpatient", "[name]", targetCharacter.DisplayName, FormatCapitals.No).Value, identifier: "cannottreatpatient".ToIdentifier(), minDurationBetweenSimilar: 20.0f);
|
||||
SpeakCannotTreat();
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (cprSuitability <= 0)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("dialogcannottreatpatient", "[name]", targetCharacter.DisplayName, formatCapitals: FormatCapitals.No).Value, identifier: "cannottreatpatient".ToIdentifier(), minDurationBetweenSimilar: 20.0f);
|
||||
Abandon = true;
|
||||
SpeakCannotTreat();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!targetCharacter.IsUnconscious)
|
||||
{
|
||||
//no suitable treatments found, not inside our own sub (= can't search for more treatments), the target isn't unconscious (= can't give CPR)
|
||||
character.Speak(TextManager.GetWithVariable("dialogcannottreatpatient", "[name]", targetCharacter.DisplayName, formatCapitals: FormatCapitals.No).Value, identifier: "cannottreatpatient".ToIdentifier(), minDurationBetweenSimilar: 20.0f);
|
||||
Abandon = true;
|
||||
//no suitable treatments found, not inside our own sub (= can't search for more treatments), the target isn't unconscious (= can't give CPR)
|
||||
SpeakCannotTreat();
|
||||
return;
|
||||
}
|
||||
if (character != targetCharacter)
|
||||
@@ -414,6 +419,14 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void SpeakCannotTreat()
|
||||
{
|
||||
LocalizedString msg = character == targetCharacter ?
|
||||
TextManager.Get("dialogcannottreatself") :
|
||||
TextManager.GetWithVariable("dialogcannottreatpatient", "[name]", targetCharacter.DisplayName, FormatCapitals.No);
|
||||
character.Speak(msg.Value, identifier: "cannottreatpatient".ToIdentifier(), minDurationBetweenSimilar: 20.0f);
|
||||
}
|
||||
|
||||
private void ApplyTreatment(Affliction affliction, Item item)
|
||||
{
|
||||
item.ApplyTreatment(character, targetCharacter, targetCharacter.CharacterHealth.GetAfflictionLimb(affliction));
|
||||
@@ -433,50 +446,36 @@ namespace Barotrauma
|
||||
|
||||
protected override float GetPriority()
|
||||
{
|
||||
if (!IsAllowed)
|
||||
if (!IsAllowed || targetCharacter == null)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
return Priority;
|
||||
}
|
||||
if (character.CurrentHull == null)
|
||||
if (character.CurrentHull != null)
|
||||
{
|
||||
if (!objectiveManager.HasOrder<AIObjectiveRescueAll>())
|
||||
if (Character.CharacterList.Any(c => c.CurrentHull == targetCharacter.CurrentHull && !HumanAIController.IsFriendly(character, c) && HumanAIController.IsActive(c)))
|
||||
{
|
||||
// Don't go into rooms that have enemies
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
return Priority;
|
||||
}
|
||||
}
|
||||
else if (Character.CharacterList.Any(c => c.CurrentHull == targetCharacter.CurrentHull && !HumanAIController.IsFriendly(character, c) && HumanAIController.IsActive(c)))
|
||||
float horizontalDistance = Math.Abs(character.WorldPosition.X - targetCharacter.WorldPosition.X);
|
||||
float verticalDistance = Math.Abs(character.WorldPosition.Y - targetCharacter.WorldPosition.Y);
|
||||
if (character.Submarine?.Info is { IsRuin: false })
|
||||
{
|
||||
// Don't go into rooms that have enemies
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
return Priority;
|
||||
verticalDistance *= 2;
|
||||
}
|
||||
if (targetCharacter == null)
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, horizontalDistance + verticalDistance));
|
||||
if (character.CurrentHull != null && targetCharacter.CurrentHull == character.CurrentHull)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
float horizontalDistance = Math.Abs(character.WorldPosition.X - targetCharacter.WorldPosition.X);
|
||||
float verticalDistance = Math.Abs(character.WorldPosition.Y - targetCharacter.WorldPosition.Y);
|
||||
if (character.Submarine?.Info is { IsRuin: false })
|
||||
{
|
||||
verticalDistance *= 2;
|
||||
}
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, horizontalDistance + verticalDistance));
|
||||
if (targetCharacter.CurrentHull == character.CurrentHull)
|
||||
{
|
||||
distanceFactor = 1;
|
||||
}
|
||||
float vitalityFactor = 1 - AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) / 100;
|
||||
float devotion = CumulatedDevotion / 100;
|
||||
Priority = MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + (vitalityFactor * distanceFactor * PriorityModifier), 0, 1));
|
||||
distanceFactor = 1;
|
||||
}
|
||||
float vitalityFactor = 1 - AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) / 100;
|
||||
float devotion = CumulatedDevotion / 100;
|
||||
Priority = MathHelper.Lerp(0, AIObjectiveManager.EmergencyObjectivePriority, MathHelper.Clamp(devotion + (vitalityFactor * distanceFactor * PriorityModifier), 0, 1));
|
||||
return Priority;
|
||||
}
|
||||
|
||||
|
||||
+9
-8
@@ -42,15 +42,16 @@ namespace Barotrauma
|
||||
if (Targets.None()) { return 100; }
|
||||
if (!objectiveManager.IsOrder(this))
|
||||
{
|
||||
if (!character.IsMedic && HumanAIController.IsTrueForAnyCrewMember(c => c != HumanAIController && c.Character.IsMedic && !c.Character.IsUnconscious))
|
||||
if (!character.IsMedic && HumanAIController.IsTrueForAnyCrewMember(c => c != character && c.IsMedic, onlyActive: true, onlyConnectedSubs: true))
|
||||
{
|
||||
// Don't do anything if there's a medic on board and we are not a medic
|
||||
// Don't do anything if there's a medic on board actively treating and we are not a medic
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
float worstCondition = Targets.Min(t => GetVitalityFactor(t));
|
||||
if (Targets.Contains(character))
|
||||
{
|
||||
// Targeting self -> higher prio
|
||||
if (character.Bleeding > 10)
|
||||
{
|
||||
// Enforce the highest priority when bleeding out.
|
||||
@@ -117,7 +118,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!character.IsMedic && target != character)
|
||||
{
|
||||
// Don't allow to treat others autonomously
|
||||
// Don't allow to treat others autonomously, unless we are a medic
|
||||
return false;
|
||||
}
|
||||
// Ignore unsafe hulls, unless ordered
|
||||
@@ -136,17 +137,17 @@ namespace Barotrauma
|
||||
// Don't allow going into another sub, unless it's connected and of the same team and type.
|
||||
if (!character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, includingConnectedSubs: true)) { return false; }
|
||||
}
|
||||
else
|
||||
else if (target.Submarine != null)
|
||||
{
|
||||
return target.Submarine == null;
|
||||
// We are outside, but the target is inside.
|
||||
return false;
|
||||
}
|
||||
if (target != character && target.IsBot && HumanAIController.IsActive(target) && target.AIController is HumanAIController targetAI)
|
||||
{
|
||||
// Ignore all concious targets that are currently fighting, fleeing, fixing, or treating characters
|
||||
// Ignore all concious targets that are currently fighting, fleeing, or treating characters
|
||||
if (targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveCombat>() ||
|
||||
targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveFindSafety>() ||
|
||||
targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveRescue>() ||
|
||||
targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveFixLeak>())
|
||||
targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveRescue>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user