v1.6.17.0 (Unto the Breach update)
This commit is contained in:
@@ -513,18 +513,19 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
private bool Check()
|
||||
{
|
||||
if (isCompleted) { return true; }
|
||||
if (AbortCondition != null && AbortCondition(this))
|
||||
{
|
||||
Abandon = true;
|
||||
return false;
|
||||
}
|
||||
return CheckObjectiveSpecific();
|
||||
return CheckObjectiveState();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Should return whether the objective is completed or not.
|
||||
/// </summary>
|
||||
protected abstract bool CheckObjectiveSpecific();
|
||||
protected abstract bool CheckObjectiveState();
|
||||
|
||||
private bool CheckState()
|
||||
{
|
||||
@@ -574,8 +575,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void SpeakAfterOrderReceived() { }
|
||||
|
||||
protected static bool CanPutInInventory(Character character, Item item, bool allowWearing)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ namespace Barotrauma
|
||||
InitTimers();
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific() => false;
|
||||
protected override bool CheckObjectiveState() => false;
|
||||
|
||||
protected override float GetPriority()
|
||||
{
|
||||
|
||||
+1
-1
@@ -117,7 +117,7 @@ namespace Barotrauma
|
||||
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific()
|
||||
protected override bool CheckObjectiveState()
|
||||
{
|
||||
if (item.IgnoreByAI(character) || Item.DeconstructItems.Contains(item))
|
||||
{
|
||||
|
||||
+2
@@ -81,6 +81,8 @@ namespace Barotrauma
|
||||
|
||||
public static bool IsItemInsideValidSubmarine(Item item, Character character)
|
||||
{
|
||||
if (item == null || item.Removed) { return false; }
|
||||
if (character == null || character.Removed) { return false; }
|
||||
if (item.CurrentHull == null) { return false; }
|
||||
if (item.Submarine == null) { return false; }
|
||||
if (item.Submarine.TeamID != character.TeamID) { return false; }
|
||||
|
||||
+25
-29
@@ -257,7 +257,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific()
|
||||
protected override bool CheckObjectiveState()
|
||||
{
|
||||
if (character.Submarine is { TeamID: CharacterTeamType.FriendlyNPC } && character.Submarine == Enemy.Submarine)
|
||||
{
|
||||
@@ -898,23 +898,13 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Unequip()
|
||||
|
||||
private void UnequipWeapon()
|
||||
{
|
||||
if (!character.LockHands && character.HeldItems.Contains(Weapon))
|
||||
{
|
||||
if (!Weapon.AllowedSlots.Contains(InvSlotType.Any) || !character.Inventory.TryPutItem(Weapon, character, new List<InvSlotType>() { InvSlotType.Any }))
|
||||
{
|
||||
if (Weapon.AllowedSlots.Contains(InvSlotType.Bag))
|
||||
{
|
||||
if (character.Inventory.TryPutItem(Weapon, character, new List<InvSlotType>() { InvSlotType.Bag }))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
Weapon.Drop(character);
|
||||
}
|
||||
}
|
||||
if (Weapon == null) { return; }
|
||||
if (character.LockHands) { return; }
|
||||
if (character.HeldItems.Contains(Weapon)) { return; }
|
||||
character.Unequip(Weapon);
|
||||
}
|
||||
|
||||
private bool Equip()
|
||||
@@ -929,7 +919,15 @@ namespace Barotrauma
|
||||
ClearInputs();
|
||||
Weapon.TryInteract(character, forceSelectKey: true);
|
||||
var slots = Weapon.AllowedSlots.Where(CharacterInventory.IsHandSlotType);
|
||||
if (character.Inventory.TryPutItem(Weapon, character, slots))
|
||||
bool successfullyEquipped = character.TryPutItem(Weapon, slots);
|
||||
if (!successfullyEquipped && character.HasHandsFull(out (Item leftHandItem, Item rightHandItem) items))
|
||||
{
|
||||
// Unequip and try again.
|
||||
character.Unequip(items.leftHandItem);
|
||||
character.Unequip(items.rightHandItem);
|
||||
successfullyEquipped = character.TryPutItem(Weapon, slots);
|
||||
}
|
||||
if (successfullyEquipped)
|
||||
{
|
||||
SetAimTimer(Rand.Range(0.2f, 0.4f) / AimSpeed);
|
||||
SetReloadTime(WeaponComponent);
|
||||
@@ -1322,8 +1320,6 @@ namespace Barotrauma
|
||||
aimTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
if (reloadTimer > 0) { return; }
|
||||
if (holdFireCondition != null && holdFireCondition()) { return; }
|
||||
sqrDistance = Vector2.DistanceSquared(character.WorldPosition, Enemy.WorldPosition);
|
||||
distanceTimer = DistanceCheckInterval;
|
||||
if (WeaponComponent is MeleeWeapon meleeWeapon)
|
||||
@@ -1353,9 +1349,11 @@ namespace Barotrauma
|
||||
if (closeEnough && Enemy.WorldPosition.Y < character.WorldPosition.Y && yDiff > 25)
|
||||
{
|
||||
// The target is probably knocked down? -> try to reach it by crouching.
|
||||
HumanAIController.AnimController.Crouching = true;
|
||||
HumanAIController.AnimController.Crouch();
|
||||
}
|
||||
}
|
||||
if (reloadTimer > 0) { return; }
|
||||
if (holdFireCondition != null && holdFireCondition()) { return; }
|
||||
if (closeEnough)
|
||||
{
|
||||
UseWeapon(deltaTime);
|
||||
@@ -1371,7 +1369,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (WeaponComponent is RepairTool repairTool)
|
||||
{
|
||||
if (sqrDistance > repairTool.Range * repairTool.Range) { return; }
|
||||
float reach = AIObjectiveFixLeak.CalculateReach(repairTool, character);
|
||||
if (sqrDistance > reach * reach) { return; }
|
||||
}
|
||||
float aimFactor = MathHelper.PiOver2 * (1 - AimAccuracy);
|
||||
if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.WorldPosition - Weapon.WorldPosition) < MathHelper.PiOver4 + aimFactor)
|
||||
@@ -1420,11 +1419,8 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
case MeleeWeapon mw:
|
||||
{
|
||||
if (character.AnimController is HumanoidAnimController { Crouching: false })
|
||||
{
|
||||
reloadTime = mw.Reload;
|
||||
}
|
||||
{
|
||||
reloadTime = mw.Reload;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1485,7 +1481,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (ShouldUnequipWeapon)
|
||||
{
|
||||
Unequip();
|
||||
UnequipWeapon();
|
||||
}
|
||||
SteeringManager?.Reset();
|
||||
}
|
||||
@@ -1495,7 +1491,7 @@ namespace Barotrauma
|
||||
base.OnAbandon();
|
||||
if (ShouldUnequipWeapon)
|
||||
{
|
||||
Unequip();
|
||||
UnequipWeapon();
|
||||
}
|
||||
SteeringManager?.Reset();
|
||||
}
|
||||
|
||||
+1
-2
@@ -77,9 +77,8 @@ namespace Barotrauma
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific()
|
||||
protected override bool CheckObjectiveState()
|
||||
{
|
||||
if (IsCompleted) { return true; }
|
||||
if (container?.Item == null || !container.Item.HasAccess(character))
|
||||
{
|
||||
Abandon = true;
|
||||
|
||||
+30
-7
@@ -15,6 +15,7 @@ namespace Barotrauma
|
||||
private Deconstructor deconstructor;
|
||||
|
||||
private AIObjectiveDecontainItem decontainObjective;
|
||||
private AIObjectiveGoTo gotoObjective;
|
||||
|
||||
public AIObjectiveDeconstructItem(Item item, Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
@@ -45,14 +46,24 @@ namespace Barotrauma
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
StartDeconstructor();
|
||||
//make sure the item gets moved to the main sub if the crew leaves while a bot is deconstructing something in the outpost
|
||||
if (deconstructor.Item.Submarine is { Info.IsOutpost: true })
|
||||
if (character.CanInteractWith(deconstructor.Item))
|
||||
{
|
||||
HumanAIController.HandleRelocation(Item);
|
||||
deconstructor.RelocateOutputToMainSub = true;
|
||||
StartDeconstruction();
|
||||
}
|
||||
else
|
||||
{
|
||||
TryAddSubObjective(ref gotoObjective,
|
||||
constructor: () => new AIObjectiveGoTo(Item, character, objectiveManager, priorityModifier: PriorityModifier),
|
||||
onCompleted: () =>
|
||||
{
|
||||
StartDeconstruction();
|
||||
RemoveSubObjective(ref gotoObjective);
|
||||
},
|
||||
onAbandon: () =>
|
||||
{
|
||||
Abandon = true;
|
||||
});
|
||||
}
|
||||
IsCompleted = true;
|
||||
RemoveSubObjective(ref decontainObjective);
|
||||
},
|
||||
onAbandon: () =>
|
||||
@@ -61,6 +72,18 @@ namespace Barotrauma
|
||||
});
|
||||
}
|
||||
|
||||
private void StartDeconstruction()
|
||||
{
|
||||
StartDeconstructor();
|
||||
//make sure the item gets moved to the main sub if the crew leaves while a bot is deconstructing something in the outpost
|
||||
if (deconstructor.Item.Submarine is { Info.IsOutpost: true })
|
||||
{
|
||||
HumanAIController.HandleRelocation(Item);
|
||||
deconstructor.RelocateOutputToMainSub = true;
|
||||
}
|
||||
IsCompleted = true;
|
||||
}
|
||||
|
||||
private Deconstructor FindDeconstructor()
|
||||
{
|
||||
Deconstructor closestDeconstructor = null;
|
||||
@@ -86,7 +109,7 @@ namespace Barotrauma
|
||||
deconstructor.SetActive(active: true, user: character, createNetworkEvent: true);
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific()
|
||||
protected override bool CheckObjectiveState()
|
||||
{
|
||||
if (Item.IgnoreByAI(character))
|
||||
{
|
||||
|
||||
+3
-1
@@ -59,12 +59,14 @@ namespace Barotrauma
|
||||
|
||||
protected override bool IsValidTarget(Item target)
|
||||
{
|
||||
if (target == null || target.Removed) { return false; }
|
||||
// If the target was selected as a valid target, we'll have to accept it so that the objective can be completed.
|
||||
// The validity changes when a character picks the item up.
|
||||
if (!IsValidTarget(target, character, checkInventory: true))
|
||||
{
|
||||
return Objectives.ContainsKey(target) && AIObjectiveCleanupItems.IsItemInsideValidSubmarine(target, character);
|
||||
}
|
||||
//note that the item can be outside hulls and still be a valid target - it can be in the character's inventory
|
||||
if (target.CurrentHull != null && target.CurrentHull.FireSources.Count > 0) { return false; }
|
||||
|
||||
foreach (Character c in Character.CharacterList)
|
||||
@@ -96,7 +98,7 @@ namespace Barotrauma
|
||||
|
||||
private static bool IsValidTarget(Item item, Character character, bool checkInventory)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
if (item == null || item.Removed) { return false; }
|
||||
if (item.GetRootInventoryOwner() == character) { return true; }
|
||||
return AIObjectiveCleanupItems.IsValidTarget(
|
||||
item,
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ namespace Barotrauma
|
||||
this.targetContainer = targetContainer;
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific() => IsCompleted;
|
||||
protected override bool CheckObjectiveState() => IsCompleted;
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public override bool CanBeCompleted => true;
|
||||
protected override bool CheckObjectiveSpecific() => false;
|
||||
protected override bool CheckObjectiveState() => false;
|
||||
|
||||
// escape timer is set to 60 by default to allow players to locate prisoners in time
|
||||
private float escapeTimer = 60f;
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ namespace Barotrauma
|
||||
return Priority;
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific() => targetHull.FireSources.None();
|
||||
protected override bool CheckObjectiveState() => targetHull.FireSources.None();
|
||||
|
||||
private float sinTime;
|
||||
protected override void Act(float deltaTime)
|
||||
|
||||
+73
-65
@@ -23,7 +23,7 @@ namespace Barotrauma
|
||||
|
||||
public const float MIN_OXYGEN = 10;
|
||||
|
||||
protected override bool CheckObjectiveSpecific() =>
|
||||
protected override bool CheckObjectiveState() =>
|
||||
targetItem != null && character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.InnerClothes | InvSlotType.Head);
|
||||
|
||||
public AIObjectiveFindDivingGear(Character character, bool needsDivingSuit, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier)
|
||||
@@ -39,83 +39,98 @@ namespace Barotrauma
|
||||
TrySetTargetItem(character.Inventory.FindItem(
|
||||
it => it.HasTag(Tags.HeavyDivingGear) && IsSuitablePressureProtection(it, Tags.HeavyDivingGear, character), recursive: true));
|
||||
}
|
||||
if (targetItem == null ||
|
||||
!character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.InnerClothes | InvSlotType.Head) &&
|
||||
targetItem.ContainedItems.Any(it => IsSuitableContainedOxygenSource(it)))
|
||||
|
||||
bool findDivingGear = targetItem == null ||
|
||||
(!character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.InnerClothes | InvSlotType.Head) && targetItem.ContainedItems.Any(IsSuitableContainedOxygenSource));
|
||||
|
||||
if (findDivingGear)
|
||||
{
|
||||
bool mustFindMorePressureProtection =
|
||||
!objectiveManager.FailedToFindDivingGearForDepth &&
|
||||
character.Inventory.FindItem(
|
||||
it => it.HasTag(Tags.HeavyDivingGear) && !IsSuitablePressureProtection(it, Tags.HeavyDivingGear, character), recursive: true) != null;
|
||||
TryAddSubObjective(ref getDivingGear, () =>
|
||||
bool mustFindMorePressureProtection = !objectiveManager.FailedToFindDivingGearForDepth &&
|
||||
character.Inventory.FindItem(it => it.HasTag(Tags.HeavyDivingGear) && !IsSuitablePressureProtection(it, Tags.HeavyDivingGear, character), recursive: true) != null;
|
||||
|
||||
if (gearTag == Tags.LightDivingGear)
|
||||
{
|
||||
if (targetItem == null && character.IsOnPlayerTeam)
|
||||
if (character.GetEquippedItem(Tags.HeavyDivingGear, slotType: InvSlotType.OuterClothes | InvSlotType.InnerClothes) is Item divingSuit && divingSuit.ContainedItems.None(IsSuitableContainedOxygenSource))
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogGetDivingGear").Value, null, 0.0f, "getdivinggear".ToIdentifier(), 30.0f);
|
||||
// A special case: we are already wearing a suit without enough oxygen, but seeking for a mask, because a suit is not really needed.
|
||||
// This would result into wearing boh the mask and the suit (because the suit shouldn't be unequipped in this situation), which is a bit weird and also suboptimal, because the mask uses the oxygen 2x faster.
|
||||
// So, let's target the diving suit and try to find oxygen instead.
|
||||
targetItem = divingSuit;
|
||||
findDivingGear = false;
|
||||
}
|
||||
var getItemObjective = new AIObjectiveGetItem(character, gearTag, objectiveManager, equip: true)
|
||||
}
|
||||
if (findDivingGear)
|
||||
{
|
||||
TryAddSubObjective(ref getDivingGear, () =>
|
||||
{
|
||||
AllowStealing = HumanAIController.NeedsDivingGear(character.CurrentHull, out _),
|
||||
AllowToFindDivingGear = false,
|
||||
AllowDangerousPressure = true,
|
||||
EquipSlotType = InvSlotType.OuterClothes | InvSlotType.InnerClothes | InvSlotType.Head,
|
||||
Wear = true
|
||||
};
|
||||
if (gearTag == Tags.HeavyDivingGear)
|
||||
{
|
||||
if (mustFindMorePressureProtection)
|
||||
if (targetItem == null && character.IsOnPlayerTeam)
|
||||
{
|
||||
//if we're looking for a suit specifically because the current suit isn't enough,
|
||||
//let's ignore unsuitable suits altogether...
|
||||
getItemObjective.ItemFilter = it => IsSuitablePressureProtection(it, gearTag, character);
|
||||
character.Speak(TextManager.Get("DialogGetDivingGear").Value, null, 0.0f, "getdivinggear".ToIdentifier(), 30.0f);
|
||||
}
|
||||
else
|
||||
var getItemObjective = new AIObjectiveGetItem(character, gearTag, objectiveManager, equip: true)
|
||||
{
|
||||
//...Otherwise it's fine to give a very small priority
|
||||
//to inadequate suits (a suit not adequate for the depth is better than no suit)
|
||||
getItemObjective.GetItemPriority = it => IsSuitablePressureProtection(it, gearTag, character) ? 1000.0f : 1.0f;
|
||||
}
|
||||
getItemObjective.GetItemPriority = it =>
|
||||
AllowStealing = HumanAIController.NeedsDivingGear(character.CurrentHull, out _),
|
||||
AllowToFindDivingGear = false,
|
||||
AllowDangerousPressure = true,
|
||||
EquipSlotType = InvSlotType.OuterClothes | InvSlotType.InnerClothes | InvSlotType.Head,
|
||||
Wear = true
|
||||
};
|
||||
if (gearTag == Tags.HeavyDivingGear)
|
||||
{
|
||||
if (IsSuitablePressureProtection(it, gearTag, character))
|
||||
if (mustFindMorePressureProtection)
|
||||
{
|
||||
return 1000.0f;
|
||||
//if we're looking for a suit specifically because the current suit isn't enough,
|
||||
//let's ignore unsuitable suits altogether...
|
||||
getItemObjective.ItemFilter = it => IsSuitablePressureProtection(it, gearTag, character);
|
||||
}
|
||||
else
|
||||
{
|
||||
//if we're looking for a suit specifically because the current suit isn't enough,
|
||||
//let's ignore unsuitable suits altogether. Otherwise it's fine to give a very small priority
|
||||
//...Otherwise it's fine to give a very small priority
|
||||
//to inadequate suits (a suit not adequate for the depth is better than no suit)
|
||||
return mustFindMorePressureProtection ? 0.0f : 1.0f;
|
||||
getItemObjective.GetItemPriority = it => IsSuitablePressureProtection(it, gearTag, character) ? 1000.0f : 1.0f;
|
||||
}
|
||||
};
|
||||
}
|
||||
return getItemObjective;
|
||||
},
|
||||
onAbandon: () =>
|
||||
{
|
||||
if (mustFindMorePressureProtection) { objectiveManager.FailedToFindDivingGearForDepth = true; }
|
||||
Abandon = true;
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
RemoveSubObjective(ref getDivingGear);
|
||||
if (gearTag == Tags.HeavyDivingGear && HumanAIController.HasItem(character, Tags.LightDivingGear, out IEnumerable<Item> masks, requireEquipped: true))
|
||||
{
|
||||
foreach (Item mask in masks)
|
||||
{
|
||||
if (mask != targetItem)
|
||||
getItemObjective.GetItemPriority = it =>
|
||||
{
|
||||
character.Inventory.TryPutItem(mask, character, CharacterInventory.AnySlot);
|
||||
if (IsSuitablePressureProtection(it, gearTag, character))
|
||||
{
|
||||
return 1000.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
//if we're looking for a suit specifically because the current suit isn't enough,
|
||||
//let's ignore unsuitable suits altogether. Otherwise it's fine to give a very small priority
|
||||
//to inadequate suits (a suit not adequate for the depth is better than no suit)
|
||||
return mustFindMorePressureProtection ? 0.0f : 1.0f;
|
||||
}
|
||||
};
|
||||
}
|
||||
return getItemObjective;
|
||||
},
|
||||
onAbandon: () =>
|
||||
{
|
||||
if (mustFindMorePressureProtection) { objectiveManager.FailedToFindDivingGearForDepth = true; }
|
||||
Abandon = true;
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
RemoveSubObjective(ref getDivingGear);
|
||||
if (gearTag == Tags.HeavyDivingGear && HumanAIController.HasItem(character, Tags.LightDivingGear, out IEnumerable<Item> masks, requireEquipped: true))
|
||||
{
|
||||
foreach (Item mask in masks)
|
||||
{
|
||||
if (mask != targetItem)
|
||||
{
|
||||
character.Inventory.TryPutItem(mask, character, CharacterInventory.AnySlot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
if (!findDivingGear)
|
||||
{
|
||||
float min = GetMinOxygen(character);
|
||||
if (targetItem.OwnInventory != null && targetItem.OwnInventory.AllItems.None(it => IsSuitableContainedOxygenSource(it)))
|
||||
if (targetItem.OwnInventory != null && targetItem.OwnInventory.AllItems.None(IsSuitableContainedOxygenSource))
|
||||
{
|
||||
TryAddSubObjective(ref getOxygen, () =>
|
||||
{
|
||||
@@ -226,14 +241,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (targetItem == item) { return; }
|
||||
targetItem = item;
|
||||
if (targetItem != null)
|
||||
{
|
||||
oxygenSourceSlotIndex = targetItem.GetComponent<ItemContainer>()?.FindSuitableSubContainerIndex(Tags.OxygenSource);
|
||||
}
|
||||
else
|
||||
{
|
||||
oxygenSourceSlotIndex = null;
|
||||
}
|
||||
oxygenSourceSlotIndex = targetItem?.GetComponent<ItemContainer>()?.FindSuitableSubContainerIndex(Tags.OxygenSource);
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
|
||||
+61
-21
@@ -3,6 +3,7 @@ using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -31,7 +32,7 @@ namespace Barotrauma
|
||||
|
||||
public AIObjectiveFindSafety(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
|
||||
|
||||
protected override bool CheckObjectiveSpecific() => false;
|
||||
protected override bool CheckObjectiveState() => false;
|
||||
public override bool CanBeCompleted => true;
|
||||
|
||||
private bool resetPriority;
|
||||
@@ -339,6 +340,10 @@ namespace Barotrauma
|
||||
float bestHullValue = 0;
|
||||
bool bestHullIsAirlock = false;
|
||||
Hull potentialBestHull;
|
||||
|
||||
#if DEBUG
|
||||
private readonly Stopwatch stopWatch = new Stopwatch();
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find the best (safe, nearby) hull the character can find a path to.
|
||||
@@ -353,6 +358,9 @@ namespace Barotrauma
|
||||
bestHullIsAirlock = false;
|
||||
hulls.Clear();
|
||||
var connectedSubs = character.Submarine?.GetConnectedSubs();
|
||||
#if DEBUG
|
||||
stopWatch.Restart();
|
||||
#endif
|
||||
foreach (Hull hull in Hull.HullList)
|
||||
{
|
||||
if (hull.Submarine == null) { continue; }
|
||||
@@ -363,25 +371,66 @@ 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
|
||||
//path calculations, only to discard all of them when going through the hulls in the outpost)
|
||||
float hullSuitability = EstimateHullSuitability(character, hull);
|
||||
if (hulls.None())
|
||||
{
|
||||
hulls.Add(hull);
|
||||
}
|
||||
else
|
||||
{
|
||||
//sort the hulls first based on distance and a rough suitability estimation
|
||||
//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)
|
||||
bool addLast = true;
|
||||
float hullSuitability = EstimateHullSuitability(hull);
|
||||
for (int i = 0; i < hulls.Count; i++)
|
||||
{
|
||||
if (hullSuitability > EstimateHullSuitability(character, hulls[i]))
|
||||
Hull otherHull = hulls[i];
|
||||
float otherHullSuitability = EstimateHullSuitability(otherHull);
|
||||
if (hullSuitability > otherHullSuitability)
|
||||
{
|
||||
hulls.Insert(i, hull);
|
||||
addLast = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (addLast)
|
||||
{
|
||||
hulls.Add(hull);
|
||||
}
|
||||
}
|
||||
|
||||
float EstimateHullSuitability(Hull h)
|
||||
{
|
||||
float distX = Math.Abs(h.WorldPosition.X - character.WorldPosition.X);
|
||||
float distY = Math.Abs(h.WorldPosition.Y - character.WorldPosition.Y);
|
||||
if (character.CurrentHull != null)
|
||||
{
|
||||
distY *= 3;
|
||||
}
|
||||
float dist = distX + distY;
|
||||
float suitability = -dist;
|
||||
const float suitabilityReduction = 10000.0f;
|
||||
if (h.Submarine != character.Submarine)
|
||||
{
|
||||
suitability -= suitabilityReduction;
|
||||
}
|
||||
if (character.CurrentHull != null)
|
||||
{
|
||||
if (h.AvoidStaying)
|
||||
{
|
||||
suitability -= suitabilityReduction;
|
||||
}
|
||||
if (HumanAIController.UnsafeHulls.Contains(h))
|
||||
{
|
||||
suitability -= suitabilityReduction;
|
||||
}
|
||||
if (HumanAIController.NeedsDivingGear(h, out _))
|
||||
{
|
||||
suitability -= suitabilityReduction;
|
||||
}
|
||||
}
|
||||
return suitability;
|
||||
}
|
||||
}
|
||||
if (hulls.None())
|
||||
@@ -390,19 +439,10 @@ namespace Barotrauma
|
||||
return HullSearchStatus.Finished;
|
||||
}
|
||||
hullSearchIndex = 0;
|
||||
}
|
||||
|
||||
static float EstimateHullSuitability(Character character, Hull hull)
|
||||
{
|
||||
float dist =
|
||||
Math.Abs(hull.WorldPosition.X - character.WorldPosition.X) +
|
||||
Math.Abs(hull.WorldPosition.Y - character.WorldPosition.Y) * 3;
|
||||
float suitability = -dist;
|
||||
if (hull.Submarine != character.Submarine)
|
||||
{
|
||||
suitability -= 10000.0f;
|
||||
}
|
||||
return suitability;
|
||||
#if DEBUG
|
||||
stopWatch.Stop();
|
||||
DebugConsole.NewMessage($"({character.DisplayName}) Sorted hulls by suitability in {stopWatch.ElapsedMilliseconds} ms", debugOnly: true);
|
||||
#endif
|
||||
}
|
||||
|
||||
Hull potentialHull = hulls[hullSearchIndex];
|
||||
@@ -420,7 +460,7 @@ namespace Barotrauma
|
||||
if (hullSafety > bestHullValue)
|
||||
{
|
||||
//avoid airlock modules if not allowed to change the sub
|
||||
if (allowChangingSubmarine || !potentialHull.OutpostModuleTags.Any(t => t == "airlock"))
|
||||
if (allowChangingSubmarine || potentialHull.OutpostModuleTags.All(t => t != "airlock"))
|
||||
{
|
||||
// Don't allow to go outside if not already outside.
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, character.GetRelativeSimPosition(potentialHull), character.Submarine, nodeFilter: node => node.Waypoint.CurrentHull != null);
|
||||
|
||||
+2
@@ -176,6 +176,8 @@ namespace Barotrauma
|
||||
//only player's crew can steal, ignore other teams
|
||||
if (!target.IsOnPlayerTeam) { return false; }
|
||||
if (target.IsHandcuffed) { return false; }
|
||||
//ignore thieves in the same team
|
||||
if (character.OriginalTeamID == target.TeamID || character.TeamID == target.TeamID) { return false; }
|
||||
// Ignore targets that are climbing, because might need to use ladders to get to them.
|
||||
if (target.IsClimbing) { return false; }
|
||||
if (HumanAIController.IsTrueForAnyBotInTheCrew(bot =>
|
||||
|
||||
+5
-5
@@ -31,7 +31,7 @@ namespace Barotrauma
|
||||
this.isPriority = isPriority;
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific() => Leak.Open <= 0 || Leak.Removed;
|
||||
protected override bool CheckObjectiveState() => Leak.Open <= 0 || Leak.Removed;
|
||||
|
||||
protected override float GetPriority()
|
||||
{
|
||||
@@ -166,7 +166,7 @@ namespace Barotrauma
|
||||
// TODO: use the collider size/reach?
|
||||
if (!character.AnimController.InWater && Math.Abs(toLeak.X) < 100 && toLeak.Y < 0.0f && toLeak.Y > -150)
|
||||
{
|
||||
HumanAIController.AnimController.Crouching = true;
|
||||
HumanAIController.AnimController.Crouch();
|
||||
}
|
||||
float reach = CalculateReach(repairTool, character);
|
||||
bool canOperate = toLeak.LengthSquared() < reach * reach;
|
||||
@@ -180,7 +180,7 @@ namespace Barotrauma
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () =>
|
||||
{
|
||||
if (CheckObjectiveSpecific()) { IsCompleted = true; }
|
||||
if (CheckObjectiveState()) { IsCompleted = true; }
|
||||
else
|
||||
{
|
||||
// Failed to operate. Probably too far.
|
||||
@@ -202,11 +202,11 @@ namespace Barotrauma
|
||||
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)
|
||||
// Only report about contextual targets.
|
||||
SpeakCannotReachCondition = () => isPriority && !CheckObjectiveSpecific()
|
||||
SpeakCannotReachCondition = () => isPriority && !CheckObjectiveState()
|
||||
},
|
||||
onAbandon: () =>
|
||||
{
|
||||
if (CheckObjectiveSpecific()) { IsCompleted = true; }
|
||||
if (CheckObjectiveState()) { IsCompleted = true; }
|
||||
else if ((Leak.WorldPosition - character.AnimController.AimSourceWorldPos).LengthSquared() > MathUtils.Pow(reach * 2, 2))
|
||||
{
|
||||
// Too far
|
||||
|
||||
+1
-2
@@ -658,9 +658,8 @@ namespace Barotrauma
|
||||
return bestItem;
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific()
|
||||
protected override bool CheckObjectiveState()
|
||||
{
|
||||
if (IsCompleted) { return true; }
|
||||
if (targetItem == null)
|
||||
{
|
||||
// Not yet ready
|
||||
|
||||
+2
-2
@@ -44,7 +44,7 @@ namespace Barotrauma
|
||||
ignoredTags = AIObjectiveGetItem.ParseIgnoredTags(identifiersOrTags).ToImmutableHashSet();
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific() => subObjectivesCreated && subObjectives.None();
|
||||
protected override bool CheckObjectiveState() => subObjectivesCreated && subObjectives.None();
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
@@ -56,7 +56,7 @@ namespace Barotrauma
|
||||
AIObjectiveGetItem? getItem = null;
|
||||
TryAddSubObjective(ref getItem, () =>
|
||||
{
|
||||
var getItem = new AIObjectiveGetItem(character, tag, objectiveManager, Equip, CheckInventory && count <= 1)
|
||||
getItem = new AIObjectiveGetItem(character, tag, objectiveManager, Equip, CheckInventory && count <= 1)
|
||||
{
|
||||
AllowVariants = AllowVariants,
|
||||
Wear = Wear,
|
||||
|
||||
+63
-20
@@ -3,6 +3,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -95,6 +96,9 @@ namespace Barotrauma
|
||||
protected override bool AllowInAnySub => true;
|
||||
|
||||
public Identifier DialogueIdentifier { get; set; } = "dialogcannotreachtarget".ToIdentifier();
|
||||
private readonly Identifier ExoSuitRefuel = "dialog.exosuit.refuel".ToIdentifier();
|
||||
private readonly Identifier ExoSuitOutOfFuel = "dialog.exosuit.outoffuel".ToIdentifier();
|
||||
|
||||
public LocalizedString TargetName { get; set; }
|
||||
|
||||
public ISpatialEntity Target { get; private set; }
|
||||
@@ -194,6 +198,43 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (checkExoSuitTimer <= 0)
|
||||
{
|
||||
checkExoSuitTimer = CheckExoSuitTime * Rand.Range(0.9f, 1.1f);
|
||||
if (character.GetEquippedItem(Tags.PoweredDivingSuit, InvSlotType.OuterClothes) is { OwnInventory: Inventory exoSuitInventory } exoSuit &&
|
||||
exoSuit.GetComponent<Powered>() is not { HasPower: true })
|
||||
{
|
||||
if (HumanAIController.HasItem(character, Tags.DivingSuitFuel, out IEnumerable<Item> fuelRods, conditionPercentage: 1, recursive: true))
|
||||
{
|
||||
// Try to switch the fuel sources
|
||||
if (character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.Get(ExoSuitRefuel).Value, minDurationBetweenSimilar: 10f, identifier: ExoSuitRefuel);
|
||||
}
|
||||
// Have to copy the list, because it's modified when we unequip the item.
|
||||
foreach (Item containedItem in exoSuit.ContainedItems.ToList())
|
||||
{
|
||||
if (containedItem.HasTag(Tags.DivingSuitFuel) && containedItem.Condition <= 0)
|
||||
{
|
||||
character.Unequip(containedItem);
|
||||
}
|
||||
}
|
||||
// Refuel
|
||||
// The information about the target slot is defined in a status effect. We could parse it, but let's keep it simple and just presume that the target slot is the second slot, as it the case with the vanilla exosuits.
|
||||
const int targetSlot = 1;
|
||||
Item fuelRod = fuelRods.MaxBy(b => b.Condition);
|
||||
exoSuitInventory.TryPutItem(fuelRod, targetSlot, allowSwapping: true, allowCombine: true, user: character);
|
||||
}
|
||||
else if (character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.Get(ExoSuitOutOfFuel).Value, minDurationBetweenSimilar: 30.0f, identifier: ExoSuitOutOfFuel);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
checkExoSuitTimer -= deltaTime;
|
||||
}
|
||||
if (Target == character || character.SelectedBy != null && HumanAIController.IsFriendly(character.SelectedBy))
|
||||
{
|
||||
// Wait
|
||||
@@ -353,9 +394,9 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try again without requiring the diving suit
|
||||
// Try again without requiring the diving suit (or mask)
|
||||
RemoveSubObjective(ref findDivingGear);
|
||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: false, objectiveManager),
|
||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: !tryToGetDivingSuit, objectiveManager),
|
||||
onAbandon: () =>
|
||||
{
|
||||
Abandon = character.CurrentHull != null && (objectiveManager.CurrentOrder != this || Target.Submarine == null);
|
||||
@@ -442,7 +483,7 @@ namespace Barotrauma
|
||||
if (checkScooterTimer <= 0)
|
||||
{
|
||||
useScooter = false;
|
||||
checkScooterTimer = checkScooterTime * Rand.Range(0.75f, 1.25f);
|
||||
checkScooterTimer = CheckScooterTime * Rand.Range(0.9f, 1.1f);
|
||||
Item scooter = null;
|
||||
bool shouldUseScooter = Mimic && targetCharacter != null && targetCharacter.HasEquippedItem(Tags.Scooter, allowBroken: false);
|
||||
if (!shouldUseScooter)
|
||||
@@ -465,24 +506,25 @@ namespace Barotrauma
|
||||
}
|
||||
else if (shouldUseScooter)
|
||||
{
|
||||
var leftHandItem = character.GetEquippedItem(slotType: InvSlotType.LeftHand);
|
||||
var rightHandItem = character.GetEquippedItem(slotType: InvSlotType.RightHand);
|
||||
bool handsFull =
|
||||
(leftHandItem != null && !character.Inventory.IsAnySlotAvailable(leftHandItem) && !character.Inventory.TryPutItem(leftHandItem, character, InvSlotType.Bag.ToEnumerable())) ||
|
||||
(rightHandItem != null && !character.Inventory.IsAnySlotAvailable(rightHandItem) && !character.Inventory.TryPutItem(rightHandItem, character, InvSlotType.Bag.ToEnumerable()));
|
||||
if (!handsFull)
|
||||
bool hasHandsFull = character.HasHandsFull(out (Item leftHandItem, Item rightHandItem) items);
|
||||
if (hasHandsFull)
|
||||
{
|
||||
hasHandsFull = !character.TryPutItemInAnySlot(items.leftHandItem) &&
|
||||
!character.TryPutItemInAnySlot(items.rightHandItem) &&
|
||||
!character.TryPutItemInBag(items.leftHandItem) &&
|
||||
!character.TryPutItemInBag(items.rightHandItem);
|
||||
}
|
||||
if (!hasHandsFull)
|
||||
{
|
||||
bool hasBattery = false;
|
||||
if (HumanAIController.HasItem(character, Tags.Scooter, out IEnumerable<Item> nonEquippedScooters, containedTag: Tags.MobileBattery, conditionPercentage: 1, requireEquipped: false))
|
||||
if (HumanAIController.HasItem(character, Tags.Scooter, out IEnumerable<Item> nonEquippedScootersWithBattery, containedTag: Tags.MobileBattery, conditionPercentage: 1, requireEquipped: false))
|
||||
{
|
||||
// Non-equipped scooter with a battery
|
||||
scooter = nonEquippedScooters.FirstOrDefault();
|
||||
scooter = nonEquippedScootersWithBattery.FirstOrDefault();
|
||||
hasBattery = true;
|
||||
}
|
||||
else if (HumanAIController.HasItem(character, Tags.Scooter, out IEnumerable<Item> _nonEquippedScooters, requireEquipped: false))
|
||||
else if (HumanAIController.HasItem(character, Tags.Scooter, out IEnumerable<Item> nonEquippedScootersWithoutBattery, requireEquipped: false))
|
||||
{
|
||||
// Non-equipped scooter without a battery
|
||||
scooter = _nonEquippedScooters.FirstOrDefault();
|
||||
scooter = nonEquippedScootersWithoutBattery.FirstOrDefault();
|
||||
// Non-recursive so that the bots won't take batteries from other items. Also means that they can't find batteries inside containers. Not sure how to solve this.
|
||||
hasBattery = HumanAIController.HasItem(character, Tags.MobileBattery, out _, requireEquipped: false, conditionPercentage: 1, recursive: false);
|
||||
}
|
||||
@@ -518,8 +560,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (!useScooter)
|
||||
{
|
||||
// Unequip
|
||||
character.Inventory.TryPutItem(scooter, character, CharacterInventory.AnySlot);
|
||||
character.TryPutItemInAnySlot(scooter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -663,7 +704,10 @@ namespace Barotrauma
|
||||
|
||||
private bool useScooter;
|
||||
private float checkScooterTimer;
|
||||
private readonly float checkScooterTime = 0.5f;
|
||||
private const float CheckScooterTime = 0.5f;
|
||||
|
||||
private float checkExoSuitTimer;
|
||||
private const float CheckExoSuitTime = 2.0f;
|
||||
|
||||
public Hull GetTargetHull() => GetTargetHull(Target);
|
||||
|
||||
@@ -764,9 +808,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific()
|
||||
protected override bool CheckObjectiveState()
|
||||
{
|
||||
if (IsCompleted) { return true; }
|
||||
// First check the distance and then if can interact (heaviest)
|
||||
if (Target == null)
|
||||
{
|
||||
|
||||
+30
-20
@@ -88,7 +88,7 @@ namespace Barotrauma
|
||||
CalculatePriority();
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific() => false;
|
||||
protected override bool CheckObjectiveState() => false;
|
||||
public override bool CanBeCompleted => true;
|
||||
|
||||
public readonly HashSet<Identifier> PreferredOutpostModuleTypes = new HashSet<Identifier>();
|
||||
@@ -158,8 +158,17 @@ namespace Barotrauma
|
||||
{
|
||||
character.DeselectCharacter();
|
||||
}
|
||||
|
||||
character.SelectedItem = null;
|
||||
if (character.SelectedItem != null)
|
||||
{
|
||||
if (character.SelectedItem.Prefab.AllowDeselectWhenIdling)
|
||||
{
|
||||
character.SelectedItem = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!character.IsClimbing)
|
||||
{
|
||||
@@ -489,27 +498,26 @@ namespace Barotrauma
|
||||
if (checkItemsTimer <= 0)
|
||||
{
|
||||
checkItemsTimer = checkItemsInterval * Rand.Range(0.9f, 1.1f);
|
||||
var hull = character.CurrentHull;
|
||||
if (hull != null)
|
||||
if (character.Submarine is not Submarine sub) { return; }
|
||||
if (sub.TeamID != character.TeamID) { return; }
|
||||
if (character.CurrentHull is not Hull currentHull) { return; }
|
||||
itemsToClean.Clear();
|
||||
foreach (Item item in Item.CleanableItems)
|
||||
{
|
||||
itemsToClean.Clear();
|
||||
foreach (Item item in Item.CleanableItems)
|
||||
if (item.CurrentHull != currentHull) { continue; }
|
||||
if (AIObjectiveCleanupItems.IsValidTarget(item, character, checkInventory: true, allowUnloading: false) && !ignoredItems.Contains(item))
|
||||
{
|
||||
if (item.CurrentHull != hull) { continue; }
|
||||
if (AIObjectiveCleanupItems.IsValidTarget(item, character, checkInventory: true, allowUnloading: false) && !ignoredItems.Contains(item))
|
||||
{
|
||||
itemsToClean.Add(item);
|
||||
}
|
||||
itemsToClean.Add(item);
|
||||
}
|
||||
if (itemsToClean.Any())
|
||||
}
|
||||
if (itemsToClean.Any())
|
||||
{
|
||||
var targetItem = itemsToClean.MinBy(i => Math.Abs(character.WorldPosition.X - i.WorldPosition.X));
|
||||
if (targetItem != null)
|
||||
{
|
||||
var targetItem = itemsToClean.OrderBy(i => Math.Abs(character.WorldPosition.X - i.WorldPosition.X)).FirstOrDefault();
|
||||
if (targetItem != null)
|
||||
{
|
||||
var cleanupObjective = new AIObjectiveCleanupItem(targetItem, character, objectiveManager, PriorityModifier);
|
||||
cleanupObjective.Abandoned += () => ignoredItems.Add(targetItem);
|
||||
subObjectives.Add(cleanupObjective);
|
||||
}
|
||||
var cleanupObjective = new AIObjectiveCleanupItem(targetItem, character, objectiveManager, PriorityModifier);
|
||||
cleanupObjective.Abandoned += () => ignoredItems.Add(targetItem);
|
||||
subObjectives.Add(cleanupObjective);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -534,6 +542,8 @@ namespace Barotrauma
|
||||
itemsToClean.Clear();
|
||||
ignoredItems.Clear();
|
||||
autonomousObjectiveRetryTimer = 10;
|
||||
timerMargin = 0;
|
||||
newTargetTimer = 0;
|
||||
}
|
||||
|
||||
public override void OnDeselected()
|
||||
|
||||
+1
-2
@@ -120,7 +120,6 @@ namespace Barotrauma
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific() => false;
|
||||
|
||||
protected override bool CheckObjectiveState() => false;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -318,7 +318,7 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific() => IsCompleted;
|
||||
protected override bool CheckObjectiveState() => IsCompleted;
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ namespace Barotrauma
|
||||
if (item.IsClaimedByBallastFlora) { return false; }
|
||||
if (!item.HasAccess(character)) { return false; }
|
||||
// Ignore items that require power but don't have it
|
||||
if (item.GetComponent<Powered>() is Powered powered && powered.PowerConsumption > 0 && powered.Voltage < powered.MinVoltage) { return false; }
|
||||
if (item.GetComponent<Powered>() is { PowerConsumption: > 0, HasPower: false }) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ namespace Barotrauma
|
||||
: base(character, objectiveManager, priorityModifier, option) { }
|
||||
|
||||
protected override void Act(float deltaTime) { }
|
||||
protected override bool CheckObjectiveSpecific() => false;
|
||||
protected override bool CheckObjectiveState() => false;
|
||||
public override bool CanBeCompleted => true;
|
||||
public override bool AbandonWhenCannotCompleteSubObjectives => false;
|
||||
public override bool AllowSubObjectiveSorting => true;
|
||||
|
||||
+1
-5
@@ -228,11 +228,7 @@ namespace Barotrauma
|
||||
coroutine = CoroutineManager.Invoke(() =>
|
||||
{
|
||||
//round ended before the coroutine finished
|
||||
#if CLIENT
|
||||
if (GameMain.GameSession == null || Level.Loaded == null && !(GameMain.GameSession.GameMode is TestGameMode)) { return; }
|
||||
#else
|
||||
if (GameMain.GameSession == null || Level.Loaded == null) { return; }
|
||||
#endif
|
||||
if (GameMain.GameSession == null || Level.Loaded == null && GameMain.GameSession.GameMode is not TestGameMode) { return; }
|
||||
DelayedObjectives.Remove(objective);
|
||||
AddObjective(objective);
|
||||
callback?.Invoke();
|
||||
|
||||
+1
-1
@@ -312,7 +312,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific() => isDoneOperating && !Repeat;
|
||||
protected override bool CheckObjectiveState() => isDoneOperating && !Repeat;
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific() => IsCompleted;
|
||||
protected override bool CheckObjectiveState() => IsCompleted;
|
||||
|
||||
protected override float GetPriority()
|
||||
{
|
||||
|
||||
+1
-1
@@ -91,7 +91,7 @@ namespace Barotrauma
|
||||
return Priority;
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific()
|
||||
protected override bool CheckObjectiveState()
|
||||
{
|
||||
IsCompleted = Item.IsFullCondition;
|
||||
if (character.IsOnPlayerTeam && IsCompleted && IsRepairing())
|
||||
|
||||
+15
-6
@@ -70,7 +70,7 @@ namespace Barotrauma
|
||||
if (otherRescuer != null && otherRescuer != character)
|
||||
{
|
||||
// Someone else is rescuing/holding the target.
|
||||
Abandon = otherRescuer.IsPlayer || character.GetSkillLevel("medical") < otherRescuer.GetSkillLevel("medical");
|
||||
Abandon = otherRescuer.IsPlayer || character.GetSkillLevel(Tags.MedicalSkill) < otherRescuer.GetSkillLevel(Tags.MedicalSkill);
|
||||
return;
|
||||
}
|
||||
if (Target != character)
|
||||
@@ -391,9 +391,18 @@ namespace Barotrauma
|
||||
("[treatmentlist]", itemListStr, FormatCapitals.Yes)).Value,
|
||||
null, 2.0f, $"listrequiredtreatments{Target.Name}".ToIdentifier(), 60.0f);
|
||||
}
|
||||
|
||||
var itemsToFind = currentTreatmentSuitabilities
|
||||
//items that have a positive effect and that the bot doesn't yet have
|
||||
.Where(kvp => kvp.Value > 0.0f && character.Inventory.AllItems.None(it => it.Prefab.Identifier == kvp.Key))
|
||||
.Select(kvp => kvp.Key);
|
||||
|
||||
RemoveSubObjective(ref getItemObjective);
|
||||
TryAddSubObjective(ref getItemObjective,
|
||||
constructor: () => new AIObjectiveGetItem(character, suitableItemIdentifiers.ToArray(), objectiveManager, equip: true, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC),
|
||||
constructor: () => new AIObjectiveGetItem(character, itemsToFind, objectiveManager, equip: true, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
GetItemPriority = it => currentTreatmentSuitabilities.GetValueOrDefault(it.Prefab.Identifier)
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref getItemObjective),
|
||||
onAbandon: () =>
|
||||
{
|
||||
@@ -468,16 +477,16 @@ namespace Barotrauma
|
||||
item.ApplyTreatment(character, Target, Target.CharacterHealth.GetAfflictionLimb(affliction));
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific()
|
||||
protected override bool CheckObjectiveState()
|
||||
{
|
||||
bool isCompleted = AIObjectiveRescueAll.GetVitalityFactor(Target) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, Target);
|
||||
if (isCompleted && Target != character && character.IsOnPlayerTeam)
|
||||
IsCompleted = AIObjectiveRescueAll.GetVitalityFactor(Target) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, Target);
|
||||
if (IsCompleted && Target != character && character.IsOnPlayerTeam)
|
||||
{
|
||||
string textTag = performedCpr ? "DialogTargetResuscitated" : "DialogTargetHealed";
|
||||
string message = TextManager.GetWithVariable(textTag, "[targetname]", Target.Name)?.Value;
|
||||
character.Speak(message, delay: 1.0f, identifier: $"targethealed{Target.Name}".ToIdentifier(), minDurationBetweenSimilar: 60.0f);
|
||||
}
|
||||
return isCompleted;
|
||||
return IsCompleted;
|
||||
}
|
||||
|
||||
protected override float GetPriority()
|
||||
|
||||
+13
-6
@@ -96,13 +96,20 @@ namespace Barotrauma
|
||||
{
|
||||
float strength = character.CharacterHealth.GetPredictedStrength(affliction, predictFutureDuration: 10.0f);
|
||||
vitality -= affliction.GetVitalityDecrease(character.CharacterHealth, strength) / character.MaxVitality * 100;
|
||||
if (affliction.Prefab.AfflictionType == AfflictionPrefab.ParalysisType)
|
||||
if (affliction.Strength > affliction.Prefab.TreatmentThreshold)
|
||||
{
|
||||
vitality -= affliction.Strength;
|
||||
}
|
||||
else if (affliction.Prefab.AfflictionType == AfflictionPrefab.PoisonType)
|
||||
{
|
||||
vitality -= affliction.Strength;
|
||||
if (affliction.Prefab.AfflictionType == AfflictionPrefab.ParalysisType)
|
||||
{
|
||||
vitality -= affliction.Strength;
|
||||
}
|
||||
else if (affliction.Prefab.AfflictionType == AfflictionPrefab.PoisonType)
|
||||
{
|
||||
vitality -= affliction.Strength;
|
||||
}
|
||||
else if (affliction.Prefab == AfflictionPrefab.HuskInfection)
|
||||
{
|
||||
vitality -= affliction.Strength;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Math.Clamp(vitality, 0, 100);
|
||||
|
||||
+15
-16
@@ -7,7 +7,7 @@ namespace Barotrauma
|
||||
class AIObjectiveReturn : AIObjective
|
||||
{
|
||||
public override Identifier Identifier { get; set; } = "return".ToIdentifier();
|
||||
public Submarine ReturnTarget { get; }
|
||||
public Submarine Target { get; }
|
||||
|
||||
private AIObjectiveGoTo moveInsideObjective, moveOutsideObjective;
|
||||
private bool usingEscapeBehavior, isSteeringThroughGap;
|
||||
@@ -17,10 +17,13 @@ namespace Barotrauma
|
||||
|
||||
public AIObjectiveReturn(Character character, Character orderGiver, AIObjectiveManager objectiveManager, float priorityModifier = 1.0f) : base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
ReturnTarget = GetReturnTarget(Submarine.MainSubs) ?? GetReturnTarget(Submarine.Loaded);
|
||||
if (ReturnTarget == null)
|
||||
Target = GetReturnTarget(Submarine.MainSubs) ?? GetReturnTarget(Submarine.Loaded);
|
||||
if (Target == null)
|
||||
{
|
||||
DebugConsole.AddSafeError("Error with a Return objective: no suitable return target found");
|
||||
if (GameMain.GameSession.GameMode is not TestGameMode)
|
||||
{
|
||||
DebugConsole.AddWarning($"({character.DisplayName}) No suitable return target found. Cannot return back to the main sub.");
|
||||
}
|
||||
Abandon = true;
|
||||
}
|
||||
|
||||
@@ -54,7 +57,7 @@ namespace Barotrauma
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (ReturnTarget == null)
|
||||
if (Target == null)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
@@ -62,7 +65,7 @@ namespace Barotrauma
|
||||
bool shouldUseEscapeBehavior = false;
|
||||
if (character.CurrentHull != null || isSteeringThroughGap)
|
||||
{
|
||||
if (character.Submarine == null || !character.Submarine.IsConnectedTo(ReturnTarget))
|
||||
if (character.Submarine == null || !character.Submarine.IsConnectedTo(Target))
|
||||
{
|
||||
// Character is on another sub that is not connected to the target sub, use the escape behavior to get them out
|
||||
shouldUseEscapeBehavior = true;
|
||||
@@ -76,13 +79,13 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
}
|
||||
}
|
||||
else if (character.Submarine != ReturnTarget)
|
||||
else if (character.Submarine != Target)
|
||||
{
|
||||
// Character is on another sub that is connected to the target sub, create a Go To objective to reach the target sub
|
||||
if (moveInsideObjective == null)
|
||||
{
|
||||
Hull targetHull = null;
|
||||
foreach (var d in ReturnTarget.ConnectedDockingPorts.Values)
|
||||
foreach (var d in Target.ConnectedDockingPorts.Values)
|
||||
{
|
||||
if (!d.Docked) { continue; }
|
||||
if (d.DockingTarget == null) { continue; }
|
||||
@@ -143,7 +146,7 @@ namespace Barotrauma
|
||||
Hull targetHull = null;
|
||||
float targetDistanceSquared = float.MaxValue;
|
||||
bool targetIsAirlock = false;
|
||||
foreach (var hull in ReturnTarget.GetHulls(false))
|
||||
foreach (var hull in Target.GetHulls(false))
|
||||
{
|
||||
bool hullIsAirlock = hull.IsAirlock;
|
||||
if(hullIsAirlock || (!targetIsAirlock && hull.LeadsOutside(character)))
|
||||
@@ -178,18 +181,14 @@ namespace Barotrauma
|
||||
usingEscapeBehavior = shouldUseEscapeBehavior;
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific()
|
||||
protected override bool CheckObjectiveState()
|
||||
{
|
||||
if (IsCompleted)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (ReturnTarget == null)
|
||||
if (Target == null)
|
||||
{
|
||||
Abandon = true;
|
||||
return false;
|
||||
}
|
||||
if (character.Submarine == ReturnTarget)
|
||||
if (character.Submarine == Target)
|
||||
{
|
||||
IsCompleted = true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user