Unstable v0.10.600.0

This commit is contained in:
Juan Pablo Arce
2020-10-01 12:19:24 -03:00
parent 20a69375ca
commit ebe1ce1427
217 changed files with 4284 additions and 1547 deletions
@@ -15,6 +15,7 @@ namespace Barotrauma
public virtual bool IgnoreUnsafeHulls => false;
public virtual bool AbandonWhenCannotCompleteSubjectives => true;
public virtual bool AllowSubObjectiveSorting => false;
public virtual bool ForceOrderPriority => true;
/// <summary>
/// Can there be multiple objective instaces of the same type?
@@ -34,6 +35,7 @@ namespace Barotrauma
public virtual bool AllowAutomaticItemUnequipping => false;
public virtual bool AllowOutsideSubmarine => false;
public virtual bool AllowInFriendlySubs => false;
public virtual bool AllowInAnySub => false;
protected readonly List<AIObjective> subObjectives = new List<AIObjective>();
private float _cumulatedDevotion;
@@ -198,12 +200,10 @@ namespace Barotrauma
{
get
{
if (AllowOutsideSubmarine) { return true; }
if (character.Submarine == null) { return false; }
return
character.Submarine.TeamID == character.TeamID ||
(AllowInFriendlySubs && character.Submarine.TeamID == Character.TeamType.FriendlyNPC) ||
character.Submarine.DockedTo.Any(sub => sub.TeamID == character.TeamID);
if (!AllowOutsideSubmarine && character.Submarine == null) { return false; }
if (AllowInAnySub) { return true; }
if (AllowInFriendlySubs && character.Submarine.TeamID == Character.TeamType.FriendlyNPC) { return true; }
return character.Submarine.TeamID == character.TeamID || character.Submarine.DockedTo.Any(sub => sub.TeamID == character.TeamID);
}
}
@@ -212,12 +212,14 @@ namespace Barotrauma
/// </summary>
public virtual float GetPriority()
{
bool isOrder = objectiveManager.CurrentOrder == this;
if (!IsAllowed)
{
Priority = 0;
Abandon = !isOrder;
return Priority;
}
if (objectiveManager.CurrentOrder == this)
if (isOrder)
{
Priority = AIObjectiveManager.OrderPriority;
}
@@ -306,7 +308,7 @@ namespace Barotrauma
return true;
}
#if DEBUG
DebugConsole.ThrowError("Attempted to add a duplicate subobjective!\n" + Environment.StackTrace);
DebugConsole.ThrowError("Attempted to add a duplicate subobjective!\n" + Environment.StackTrace.CleanupStackTrace());
#endif
return false;
}
@@ -0,0 +1,124 @@
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
class AIObjectiveCleanupItem : AIObjective
{
public override string DebugTag => "cleanup item";
public override bool KeepDivingGearOn => true;
public override bool AllowAutomaticItemUnequipping => false;
public readonly Item item;
public bool IsPriority { get; set; }
private readonly List<Item> ignoredContainers = new List<Item>();
private AIObjectiveDecontainItem decontainObjective;
private int itemIndex = 0;
public AIObjectiveCleanupItem(Item item, Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier)
{
this.item = item;
}
public override float GetPriority()
{
if (!IsAllowed)
{
Priority = 0;
Abandon = true;
return Priority;
}
else
{
float distanceFactor = 0.9f;
if (!IsPriority && item.CurrentHull != character.CurrentHull)
{
float yDist = Math.Abs(character.WorldPosition.Y - item.WorldPosition.Y);
yDist = yDist > 100 ? yDist * 5 : 0;
float dist = Math.Abs(character.WorldPosition.X - item.WorldPosition.X) + yDist;
distanceFactor = MathHelper.Lerp(0.9f, 0, MathUtils.InverseLerp(0, 5000, dist));
}
bool isSelected = character.HasItem(item);
float selectedBonus = isSelected ? 100 - MaxDevotion : 0;
float devotion = (CumulatedDevotion + selectedBonus) / 100;
float reduction = IsPriority ? 1 : isSelected ? 2 : 3;
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - reduction, 90);
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (distanceFactor * PriorityModifier), 0, 1));
}
return Priority;
}
protected override void Act(float deltaTime)
{
// Only continue when the get item sub objectives have been completed.
if (subObjectives.Any()) { return; }
if (HumanAIController.FindSuitableContainer(character, item, ignoredContainers, ref itemIndex, out Item suitableContainer))
{
itemIndex = 0;
if (suitableContainer != null)
{
bool equip = item.HasTag(AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR) || (
item.GetComponent<Wearable>() == null &&
item.AllowedSlots.None(s =>
s == InvSlotType.Card ||
s == InvSlotType.Head ||
s == InvSlotType.Headset ||
s == InvSlotType.InnerClothes ||
s == InvSlotType.OuterClothes));
TryAddSubObjective(ref decontainObjective, () => new AIObjectiveDecontainItem(character, item, objectiveManager, targetContainer: suitableContainer.GetComponent<ItemContainer>())
{
Equip = equip,
DropIfFails = true
},
onCompleted: () =>
{
if (equip)
{
HumanAIController.ReequipUnequipped();
}
IsCompleted = true;
},
onAbandon: () =>
{
if (equip)
{
HumanAIController.ReequipUnequipped();
}
if (decontainObjective != null && decontainObjective.ContainObjective != null && decontainObjective.ContainObjective.CanBeCompleted)
{
ignoredContainers.Add(suitableContainer);
}
else
{
Abandon = true;
}
});
}
else
{
Abandon = true;
}
}
else
{
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
}
}
protected override bool Check() => IsCompleted;
public override void Reset()
{
base.Reset();
ignoredContainers.Clear();
itemIndex = 0;
decontainObjective = null;
}
}
}
@@ -0,0 +1,85 @@
using Barotrauma.Items.Components;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
class AIObjectiveCleanupItems : AIObjectiveLoop<Item>
{
public override string DebugTag => "cleanup items";
public override bool KeepDivingGearOn => true;
public override bool AllowAutomaticItemUnequipping => false;
public override bool ForceOrderPriority => false;
public readonly Item prioritizedItem;
public AIObjectiveCleanupItems(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1, Item prioritizedItem = null)
: base(character, objectiveManager, priorityModifier)
{
this.prioritizedItem = prioritizedItem;
}
protected override float TargetEvaluation() => Targets.Any() ? AIObjectiveManager.RunPriority - 1 : 0;
protected override bool Filter(Item target)
{
// 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)) { return Objectives.ContainsKey(target) && IsItemInsideValidSubmarine(target, character); }
if (target.CurrentHull.FireSources.Count > 0) { return false; }
// Don't repair items in rooms that have enemies inside.
if (Character.CharacterList.Any(c => c.CurrentHull == target.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return false; }
return true;
}
protected override IEnumerable<Item> GetList() => Item.ItemList;
protected override AIObjective ObjectiveConstructor(Item item)
=> new AIObjectiveCleanupItem(item, character, objectiveManager, priorityModifier: PriorityModifier)
{
IsPriority = prioritizedItem == item
};
protected override void OnObjectiveCompleted(AIObjective objective, Item target)
=> HumanAIController.RemoveTargets<AIObjectiveCleanupItems, Item>(character, target);
private static bool IsItemInsideValidSubmarine(Item item, Character character)
{
if (item.CurrentHull == null) { return false; }
if (item.Submarine == null) { return false; }
if (item.Submarine.TeamID != character.TeamID) { return false; }
if (character.Submarine != null)
{
if (!character.Submarine.IsConnectedTo(item.Submarine)) { return false; }
}
return true;
}
public static bool IsValidTarget(Item item, Character character)
{
if (item == null) { return false; }
if (item.NonInteractable) { return false; }
if (item.ParentInventory != null) { return false; }
if (character != null && !IsItemInsideValidSubmarine(item, character)) { return false; }
//var rootContainer = item.GetRootContainer();
//// Only target items lying on the ground (= not inside a container) (do we need this check?)
//if (rootContainer != null) { return false; }
var pickable = item.GetComponent<Pickable>();
if (pickable == null) { return false; }
var wire = item.GetComponent<Wire>();
if (wire != null)
{
if (wire.Connections.Any()) { return false; }
}
else
{
var connectionPanel = item.GetComponent<ConnectionPanel>();
if (connectionPanel != null && connectionPanel.Connections.Any(c => c.Wires.Any(w => w != null)))
{
return false;
}
}
return item.Prefab.PreferredContainers.Any();
}
}
}
@@ -15,6 +15,7 @@ namespace Barotrauma
public override bool KeepDivingGearOn => true;
public override bool IgnoreUnsafeHulls => true;
public override bool AllowOutsideSubmarine => true;
public override bool AllowInAnySub => true;
private readonly CombatMode initialMode;
@@ -54,8 +55,8 @@ namespace Barotrauma
if (_weaponComponent == null)
{
_weaponComponent =
Weapon.GetComponent<RangedWeapon>() as ItemComponent ??
Weapon.GetComponent<MeleeWeapon>() as ItemComponent ??
Weapon.GetComponent<RangedWeapon>() ??
Weapon.GetComponent<MeleeWeapon>() ??
Weapon.GetComponent<RepairTool>() as ItemComponent;
}
return _weaponComponent;
@@ -144,6 +145,7 @@ namespace Barotrauma
if (Enemy.Submarine == null || (Enemy.Submarine.TeamID != character.TeamID && Enemy.Submarine != character.Submarine))
{
Priority = 0;
Abandon = true;
return Priority;
}
}
@@ -760,11 +762,7 @@ namespace Barotrauma
if (HumanAIController.HasItem(character, "handlocker", out IEnumerable<Item> matchingItems) && Enemy.Stun > 0 && character.CanInteractWith(Enemy))
{
var handCuffs = matchingItems.First();
if (HumanAIController.TryToMoveItem(handCuffs, Enemy.Inventory))
{
handCuffs.Equip(Enemy);
}
else
if (!HumanAIController.TakeItem(handCuffs, Enemy.Inventory, equip: true))
{
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Failed to handcuff the target.", Color.Red);
@@ -777,7 +775,7 @@ namespace Barotrauma
if (item.StolenDuringRound)
{
item.Drop(character);
character.Inventory.TryPutItem(item, character, new List<InvSlotType>() { InvSlotType.Any });
character.Inventory.TryPutItem(item, character, CharacterInventory.anySlot);
}
}
character.Speak(TextManager.Get("DialogTargetArrested"), null, 3.0f, "targetarrested", 30.0f);
@@ -885,7 +883,11 @@ namespace Barotrauma
private void Attack(float deltaTime)
{
character.CursorPosition = Enemy.Position;
character.CursorPosition = Enemy.WorldPosition;
if (character.Submarine != null)
{
character.CursorPosition -= character.Submarine.Position;
}
visibilityCheckTimer -= deltaTime;
if (visibilityCheckTimer <= 0.0f)
{
@@ -142,6 +142,7 @@ namespace Barotrauma
}
else
{
// TODO: should we just use GetItem?
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(container.Item, character, objectiveManager, getDivingGearIfNeeded: AllowToFindDivingGear)
{
DialogueIdentifier = "dialogcannotreachtarget",
@@ -153,27 +154,34 @@ namespace Barotrauma
}
else
{
// No matching items in the inventory, try to get an item
TryAddSubObjective(ref getItemObjective, () =>
new AIObjectiveGetItem(character, itemIdentifiers, objectiveManager, equip: Equip, checkInventory: checkInventory, spawnItemIfNotFound: spawnItemIfNotFound)
{
GetItemPriority = GetItemPriority,
ignoredContainerIdentifiers = ignoredContainerIdentifiers,
ignoredItems = containedItems,
AllowToFindDivingGear = AllowToFindDivingGear,
AllowDangerousPressure = AllowDangerousPressure,
TargetCondition = ConditionLevel
}, onAbandon: () =>
{
Abandon = true;
}, onCompleted: () =>
{
if (getItemObjective?.TargetItem != null)
if (character.Submarine == null)
{
Abandon = true;
}
else
{
// No matching items in the inventory, try to get an item
TryAddSubObjective(ref getItemObjective, () =>
new AIObjectiveGetItem(character, itemIdentifiers, objectiveManager, equip: Equip, checkInventory: checkInventory, spawnItemIfNotFound: spawnItemIfNotFound)
{
containedItems.Add(getItemObjective.TargetItem);
}
RemoveSubObjective(ref getItemObjective);
});
GetItemPriority = GetItemPriority,
ignoredContainerIdentifiers = ignoredContainerIdentifiers,
ignoredItems = containedItems,
AllowToFindDivingGear = AllowToFindDivingGear,
AllowDangerousPressure = AllowDangerousPressure,
TargetCondition = ConditionLevel
}, onAbandon: () =>
{
Abandon = true;
}, onCompleted: () =>
{
if (getItemObjective?.TargetItem != null)
{
containedItems.Add(getItemObjective.TargetItem);
}
RemoveSubObjective(ref getItemObjective);
});
}
}
}
@@ -16,9 +16,12 @@ namespace Barotrauma
private ItemContainer targetContainer;
private readonly Item targetItem;
private AIObjectiveGoTo goToObjective;
private AIObjectiveGetItem getItemObjective;
private AIObjectiveContainItem containObjective;
public AIObjectiveGetItem GetItemObjective => getItemObjective;
public AIObjectiveContainItem ContainObjective => containObjective;
public bool Equip { get; set; }
/// <summary>
@@ -26,7 +29,7 @@ namespace Barotrauma
/// In both cases abandons the objective.
/// Note that has no effect if the target container was not defined (always drops) -> completes when the item is dropped.
/// </summary>
public bool DropIfFailsToContain { get; set; } = true;
public bool DropIfFails { get; set; } = true;
public AIObjectiveDecontainItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, ItemContainer sourceContainer = null, ItemContainer targetContainer = null, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier)
@@ -74,54 +77,30 @@ namespace Barotrauma
return;
}
}
else
else if (targetContainer.Inventory.Items.Contains(itemToDecontain))
{
if (targetContainer.Inventory.Items.Contains(itemToDecontain))
{
IsCompleted = true;
return;
}
IsCompleted = true;
return;
}
if (goToObjective == null && !itemToDecontain.IsOwnedBy(character))
if (getItemObjective == null && !itemToDecontain.IsOwnedBy(character))
{
if (sourceContainer == null)
{
Abandon = true;
return;
}
if (!character.CanInteractWith(sourceContainer.Item, out _, checkLinked: false))
{
TryAddSubObjective(ref goToObjective,
constructor: () => new AIObjectiveGoTo(sourceContainer.Item, character, objectiveManager)
{
// If the container changes, the item is no longer where it was
abortCondition = () => itemToDecontain.Container != sourceContainer.Item,
DialogueIdentifier = "dialogcannotreachtarget",
TargetName = sourceContainer.Item.Name
},
onAbandon: () => Abandon = true);
return;
}
TryAddSubObjective(ref getItemObjective,
constructor: () => new AIObjectiveGetItem(character, targetItem, objectiveManager, Equip),
onAbandon: () => Abandon = true);
return;
}
if (targetContainer != null)
{
TryAddSubObjective(ref containObjective,
constructor: () => new AIObjectiveContainItem(character, itemToDecontain, targetContainer, objectiveManager)
{
Equip = this.Equip,
Equip = Equip,
RemoveEmpty = false,
GetItemPriority = this.GetItemPriority,
GetItemPriority = GetItemPriority,
ignoredContainerIdentifiers = sourceContainer != null ? new string[] { sourceContainer.Item.Prefab.Identifier } : null
},
onCompleted: () => IsCompleted = true,
onAbandon: () =>
{
if (DropIfFailsToContain)
{
itemToDecontain.Drop(character);
}
Abandon = true;
});
onAbandon: () => Abandon = true);
}
else
{
@@ -133,8 +112,17 @@ namespace Barotrauma
public override void Reset()
{
base.Reset();
goToObjective = null;
getItemObjective = null;
containObjective = null;
}
protected override void OnAbandon()
{
base.OnAbandon();
if (DropIfFails && targetItem != null && targetItem.IsOwnedBy(character))
{
targetItem.Drop(character);
}
}
}
}
@@ -13,6 +13,8 @@ namespace Barotrauma
public override bool ConcurrentObjectives => true;
public override bool KeepDivingGearOn => true;
public override bool AllowInAnySub => true;
private readonly Hull targetHull;
private AIObjectiveGetItem getExtinguisherObjective;
@@ -30,12 +32,15 @@ namespace Barotrauma
if (!IsAllowed)
{
Priority = 0;
Abandon = true;
return Priority;
}
if (!objectiveManager.IsCurrentOrder<AIObjectiveExtinguishFires>()
&& Character.CharacterList.Any(c => c.CurrentHull == targetHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c)))
bool isOrder = objectiveManager.IsCurrentOrder<AIObjectiveExtinguishFires>();
if (!isOrder && Character.CharacterList.Any(c => c.CurrentHull == targetHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c)))
{
// Don't go into rooms with any enemies, unless it's an order
Priority = 0;
Abandon = true;
}
else
{
@@ -43,14 +48,22 @@ namespace Barotrauma
yDist = yDist > 100 ? yDist * 3 : 0;
float dist = Math.Abs(character.WorldPosition.X - targetHull.WorldPosition.X) + yDist;
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, dist));
if (targetHull == character.CurrentHull)
if (targetHull == character.CurrentHull || HumanAIController.VisibleHulls.Contains(targetHull))
{
distanceFactor = 1;
}
float severity = AIObjectiveExtinguishFires.GetFireSeverity(targetHull);
float severityFactor = MathHelper.Lerp(0, 1, severity / 100);
float devotion = CumulatedDevotion / 100;
Priority = MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + (severityFactor * distanceFactor * PriorityModifier), 0, 1));
if (severity > 0.5f && !isOrder)
{
// Ignore severe fires unless ordered. (Let the fire drain all the oxygen instead).
Priority = 0;
Abandon = true;
}
else
{
float devotion = CumulatedDevotion / 100;
Priority = MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
}
}
return Priority;
}
@@ -131,13 +144,16 @@ namespace Barotrauma
if (move)
{
//go to the first firesource
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(fs, character, objectiveManager, closeEnough: extinguisher.Range / 2)
if (TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(fs, character, objectiveManager, closeEnough: extinguisher.Range / 2)
{
DialogueIdentifier = "dialogcannotreachfire",
TargetName = fs.Hull.DisplayName
},
onAbandon: () => Abandon = true,
onCompleted: () => RemoveSubObjective(ref gotoObjective)))
{
DialogueIdentifier = "dialogcannotreachfire",
TargetName = fs.Hull.DisplayName
},
onAbandon: () => Abandon = true,
onCompleted: () => RemoveSubObjective(ref gotoObjective));
gotoObjective.requiredCondition = () => HumanAIController.VisibleHulls.Contains(fs.Hull);
}
}
else
{
@@ -1,6 +1,8 @@
using System.Linq;
using System;
using System.Linq;
using System.Collections.Generic;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
@@ -8,14 +10,22 @@ namespace Barotrauma
{
public override string DebugTag => "extinguish fires";
public override bool ForceRun => true;
public override bool AllowInAnySub => true;
public AIObjectiveExtinguishFires(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
protected override bool Filter(Hull hull) => IsValidTarget(hull, character);
protected override float TargetEvaluation() => Targets.Sum(t => GetFireSeverity(t));
protected override float TargetEvaluation() =>
// If any target is visible -> 100 priority
Targets.Any(t => t == character.CurrentHull || HumanAIController.VisibleHulls.Contains(t)) ? 100 :
// Else based on the fire severity
Targets.Sum(t => GetFireSeverity(t) * 100);
public static float GetFireSeverity(Hull hull) => hull.FireSources.Sum(fs => fs.Size.X);
/// <summary>
/// 0-1 based on the horizontal size of all of the fires in the hull.
/// </summary>
public static float GetFireSeverity(Hull hull) => MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, Math.Min(hull.Rect.Width, 1000), hull.FireSources.Sum(fs => fs.Size.X)));
protected override IEnumerable<Hull> GetList() => Hull.hullList;
@@ -31,22 +41,7 @@ namespace Barotrauma
if (hull.FireSources.None()) { return false; }
if (hull.Submarine == null) { return false; }
if (character.Submarine == null) { return false; }
if (!character.Submarine.IsConnectedTo(hull.Submarine)) { return false; }
if (character.AIController is HumanAIController humanAI)
{
if (hull.Submarine.TeamID != character.TeamID)
{
if (humanAI.ObjectiveManager.IsCurrentOrder<AIObjectiveExtinguishFires>())
{
// For orders, allow targets in the current sub (for example if the bot is inside an outpost or a wreck)
if (hull.Submarine != character.Submarine) { return false; }
}
else
{
return false;
}
}
}
if (!character.Submarine.IsEntityFoundOnThisSub(hull, includingConnectedSubs: true)) { return false; }
return true;
}
}
@@ -17,7 +17,7 @@ namespace Barotrauma
private Item targetItem;
public static float MIN_OXYGEN = 10;
public static string HEAVY_DIVING_GEAR = "heavydiving";
public static string HEAVY_DIVING_GEAR = "deepdiving";
public static string LIGHT_DIVING_GEAR = "lightdiving";
public static string OXYGEN_SOURCE = "oxygensource";
@@ -14,8 +14,9 @@ namespace Barotrauma
public override bool IgnoreUnsafeHulls => true;
public override bool ConcurrentObjectives => true;
public override bool AllowOutsideSubmarine => true;
public override bool AllowInAnySub => true;
public override bool AbandonWhenCannotCompleteSubjectives => false;
public override bool IsLoop { get => true; set => throw new Exception("Trying to set the value for IsLoop from: " + Environment.StackTrace); }
public override bool IsLoop { get => true; set => throw new Exception("Trying to set the value for IsLoop from: " + Environment.StackTrace.CleanupStackTrace()); }
// TODO: expose?
const float priorityIncrease = 100;
@@ -12,6 +12,7 @@ namespace Barotrauma
public override string DebugTag => "fix leak";
public override bool ForceRun => true;
public override bool KeepDivingGearOn => true;
public override bool AllowInAnySub => true;
public Gap Leak { get; private set; }
@@ -35,11 +36,7 @@ namespace Barotrauma
if (!IsAllowed)
{
Priority = 0;
return Priority;
}
if (Leak.Removed || Leak.Open <= 0)
{
Priority = 0;
Abandon = true;
}
else if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.ObjectiveManager.GetActiveObjective<AIObjectiveFixLeak>()?.Leak == Leak))
{
@@ -116,7 +113,7 @@ namespace Barotrauma
{
HumanAIController.AnimController.Crouching = true;
}
float reach = repairTool.Range + ConvertUnits.ToDisplayUnits(((HumanoidAnimController)character.AnimController).ArmLength);
float reach = CalculateReach(repairTool, character);
bool canOperate = toLeak.LengthSquared() < reach * reach;
if (canOperate)
{
@@ -144,7 +141,7 @@ namespace Barotrauma
onAbandon: () =>
{
if (Check()) { IsCompleted = true; }
else if ((Leak.WorldPosition - character.WorldPosition).LengthSquared() > reach * reach * 2)
else if ((Leak.WorldPosition - character.WorldPosition).LengthSquared() > MathUtils.Pow(reach * 2, 2))
{
// Too far
Abandon = true;
@@ -167,5 +164,14 @@ namespace Barotrauma
gotoObjective = null;
operateObjective = null;
}
public static float CalculateReach(RepairTool repairTool, Character character)
{
float armLength = ConvertUnits.ToDisplayUnits(((HumanoidAnimController)character.AnimController).ArmLength);
// This is an approximation, because we don't know the exact reach until the pose is taken.
// And even then the actual range depends on the direction we are aiming to.
// Found out that without any multiplier the value (209) is often too short.
return repairTool.Range + armLength * 1.2f;
}
}
}
@@ -9,6 +9,7 @@ namespace Barotrauma
public override string DebugTag => "fix leaks";
public override bool ForceRun => true;
public override bool KeepDivingGearOn => true;
public override bool AllowInAnySub => true;
private Hull PrioritizedHull { get; set; }
public AIObjectiveFixLeaks(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1, Hull prioritizedHull = null) : base(character, objectiveManager, priorityModifier)
@@ -71,12 +72,9 @@ namespace Barotrauma
{
if (gap == null) { return false; }
if (gap.ConnectedWall == null || gap.ConnectedDoor != null || gap.Open <= 0 || gap.linkedTo.All(l => l == null)) { return false; }
if (gap.Submarine == null) { return false; }
if (gap.Submarine.TeamID != character.TeamID) { return false; }
if (character.Submarine != null)
{
if (!character.Submarine.IsConnectedTo(gap.Submarine)) { return false; }
}
if (gap.Submarine == null || character.Submarine == null) { return false; }
// Don't allow going into another sub, unless it's connected and of the same team and type.
if (!character.Submarine.IsEntityFoundOnThisSub(gap, includingConnectedSubs: true)) { return false; }
return true;
}
}
@@ -37,6 +37,7 @@ namespace Barotrauma
public static float DefaultReach = 100;
public bool AllowToFindDivingGear { get; set; } = true;
public bool MustBeSpecificItem { get; set; }
public AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip = true, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier)
@@ -84,6 +85,11 @@ namespace Barotrauma
Abandon = true;
return;
}
if (character.Submarine == null)
{
Abandon = true;
return;
}
if (identifiersOrTags != null && !isDoneSeeking)
{
if (checkInventory)
@@ -109,7 +115,10 @@ namespace Barotrauma
}
}
FindTargetItem();
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
if (!objectiveManager.IsCurrentOrder<AIObjectiveGoTo>())
{
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
}
return;
}
}
@@ -137,7 +146,8 @@ namespace Barotrauma
if (originalTarget == null)
{
// Try again
Reset();
ignoredItems.Add(targetItem);
ResetInternal();
}
else
{
@@ -176,12 +186,8 @@ namespace Barotrauma
return;
}
if (HumanAIController.TryToMoveItem(targetItem, character.Inventory))
if (HumanAIController.TakeItem(targetItem, character.Inventory, equip, storeUnequipped: true))
{
if (equip)
{
targetItem.Equip(character);
}
IsCompleted = true;
}
else
@@ -207,8 +213,16 @@ namespace Barotrauma
},
onAbandon: () =>
{
ignoredItems.Add(targetItem);
Reset();
if (originalTarget == null)
{
// Try again
ignoredItems.Add(targetItem);
ResetInternal();
}
else
{
Abandon = true;
}
},
onCompleted: () => RemoveSubObjective(ref goToObjective));
}
@@ -235,13 +249,13 @@ namespace Barotrauma
Submarine mySub = character.Submarine;
if (itemSub == null) { continue; }
if (mySub == null) { continue; }
if (itemSub.TeamID != mySub.TeamID && itemSub.TeamID != character.TeamID) { continue; }
if (!CheckItem(item)) { continue; }
if (ignoredContainerIdentifiers != null && item.Container != null)
{
if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; }
}
if (!mySub.IsConnectedTo(itemSub)) { 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; }
float itemPriority = 1;
if (GetItemPriority != null)
@@ -272,7 +286,7 @@ namespace Barotrauma
if (!(MapEntityPrefab.List.FirstOrDefault(me => me is ItemPrefab ip && identifiersOrTags.Any(id => id == ip.Identifier || ip.Tags.Contains(id))) is ItemPrefab prefab))
{
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s): {string.Join(", ", identifiersOrTags)}, tried to spawn the item but no matching item prefabs were found.", Color.Yellow);
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}, tried to spawn the item but no matching item prefabs were found.", Color.Yellow);
#endif
Abandon = true;
}
@@ -291,7 +305,7 @@ namespace Barotrauma
else
{
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s): {string.Join(", ", identifiersOrTags)}", Color.Yellow);
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}", Color.Yellow);
#endif
Abandon = true;
}
@@ -330,11 +344,28 @@ namespace Barotrauma
public override void Reset()
{
base.Reset();
ResetInternal();
}
/// <summary>
/// Does not reset the ignored items list
/// </summary>
private void ResetInternal()
{
goToObjective = null;
targetItem = originalTarget;
moveToTarget = targetItem?.GetRootInventoryOwner();
isDoneSeeking = false;
currSearchIndex = 0;
}
protected override void OnAbandon()
{
base.OnAbandon();
if (objectiveManager.CurrentOrder != null)
{
character.Speak(TextManager.Get("DialogCannotFindItem"), null, 0.0f, "cannotfinditem", 10.0f);
}
}
}
}
@@ -1,5 +1,6 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
@@ -50,6 +51,7 @@ namespace Barotrauma
public override bool AbandonWhenCannotCompleteSubjectives => !repeat;
public override bool AllowOutsideSubmarine => AllowGoingOutside;
public override bool AllowInAnySub => true;
public string DialogueIdentifier { get; set; }
public string TargetName { get; set; }
@@ -60,17 +62,27 @@ namespace Barotrauma
public override float GetPriority()
{
bool isOrder = objectiveManager.CurrentOrder == this;
if (!IsAllowed)
{
Priority = 0;
Abandon = !isOrder;
return Priority;
}
if (followControlledCharacter && Character.Controlled == null)
{
Priority = 0;
Abandon = !isOrder;
}
if (Target is Entity e && e.Removed)
{
Priority = 0;
Abandon = !isOrder;
}
if (IgnoreIfTargetDead && Target is Character character && character.IsDead)
{
Priority = 0;
Abandon = !isOrder;
}
else
{
@@ -84,7 +96,7 @@ namespace Barotrauma
}
else
{
Priority = objectiveManager.CurrentOrder == this ? AIObjectiveManager.OrderPriority : 10;
Priority = isOrder ? AIObjectiveManager.OrderPriority : 10;
}
}
return Priority;
@@ -93,7 +105,7 @@ namespace Barotrauma
public AIObjectiveGoTo(ISpatialEntity target, Character character, AIObjectiveManager objectiveManager, bool repeat = false, bool getDivingGearIfNeeded = true, float priorityModifier = 1, float closeEnough = 0)
: base(character, objectiveManager, priorityModifier)
{
this.Target = target;
Target = target;
this.repeat = repeat;
waitUntilPathUnreachable = 3.0f;
this.getDivingGearIfNeeded = getDivingGearIfNeeded;
@@ -260,6 +272,53 @@ namespace Barotrauma
}
}
}
if (!character.AnimController.InWater)
{
useScooter = false;
checkScooterTimer = 0;
}
else if (checkScooterTimer <= 0)
{
useScooter = false;
checkScooterTimer = checkScooterTime;
string scooterTag = "scooter";
string batteryTag = "mobilebattery";
Item scooter = null;
bool isScooterEquipped = false;
float closeEnough = 250;
float squaredDistance = Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition);
bool shouldUseScooter = squaredDistance > closeEnough * closeEnough && (!mimic ||
(Target is Character targetCharacter && targetCharacter.HasEquippedItem(scooterTag, allowBroken: false)) || squaredDistance > Math.Pow(closeEnough * 2, 2));
if (HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> equippedScooters, batteryTag, requireEquipped: true))
{
scooter = equippedScooters.FirstOrDefault();
isScooterEquipped = scooter != null;
}
else if (shouldUseScooter && HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> scooters, batteryTag, requireEquipped: false))
{
scooter = scooters.FirstOrDefault();
if (scooter != null)
{
isScooterEquipped = HumanAIController.TakeItem(scooter, character.Inventory, equip: true, dropOtherIfCannotMove: false, allowSwapping: true, storeUnequipped: false);
}
}
if (scooter != null && isScooterEquipped)
{
if (shouldUseScooter)
{
useScooter = true;
}
else
{
// Unequip
character.Inventory.TryPutItem(scooter, character, CharacterInventory.anySlot);
}
}
}
else
{
checkScooterTimer -= deltaTime;
}
if (SteeringManager == PathSteering)
{
Func<PathNode, bool> nodeFilter = null;
@@ -274,47 +333,90 @@ namespace Barotrauma
}, endNodeFilter, nodeFilter, CheckVisibility);
if (!isInside && PathSteering.CurrentPath == null || PathSteering.IsPathDirty || PathSteering.CurrentPath.Unreachable)
{
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(Target.WorldPosition - character.WorldPosition));
if (character.AnimController.InWater)
if (useScooter)
{
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 15);
UseScooter(Target.WorldPosition);
}
else
{
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(Target.WorldPosition - character.WorldPosition));
if (character.AnimController.InWater)
{
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 15);
}
}
}
else if (useScooter && PathSteering.CurrentPath?.CurrentNode != null)
{
UseScooter(PathSteering.CurrentPath.CurrentNode.WorldPosition);
}
}
else
{
SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Target), 10);
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 15);
if (useScooter)
{
UseScooter(Target.WorldPosition);
}
else
{
SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Target), 10);
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 15);
}
}
}
void UseScooter(Vector2 targetWorldPos)
{
SteeringManager.Reset();
character.CursorPosition = targetWorldPos;
if (character.Submarine != null)
{
character.CursorPosition -= character.Submarine.Position;
}
Vector2 dir = Vector2.Normalize(character.CursorPosition - character.Position);
if (!MathUtils.IsValid(dir)) { dir = Vector2.UnitY; }
SteeringManager.SteeringManual(1.0f, dir);
character.SetInput(InputType.Aim, false, true);
character.SetInput(InputType.Shoot, false, true);
}
}
public Hull GetTargetHull()
private bool useScooter;
private float checkScooterTimer;
private readonly float checkScooterTime = 0.2f;
public Hull GetTargetHull() => GetTargetHull(Target);
public static Hull GetTargetHull(ISpatialEntity target)
{
if (Target is Hull h)
if (target is Hull h)
{
return h;
}
else if (Target is Item i)
else if (target is Item i)
{
return i.CurrentHull;
}
else if (Target is Character c)
else if (target is Character c)
{
return c.CurrentHull;
}
else if (Target is Gap g)
else if (target is Gap g)
{
return g.FlowTargetHull;
}
else if (Target is WayPoint wp)
else if (target is WayPoint wp)
{
return wp.CurrentHull;
}
else if (Target is FireSource fs)
else if (target is FireSource fs)
{
return fs.Hull;
}
else if (target is OrderTarget ot)
{
return ot.Hull;
}
return null;
}
@@ -361,7 +463,7 @@ namespace Barotrauma
{
if (Target is Item item)
{
if (character.CanInteractWith(item, out _, checkLinked: false)) { IsCompleted = true; }
if (!character.IsClimbing && character.CanInteractWith(item, out _, checkLinked: false)) { IsCompleted = true; }
}
else if (Target is Character targetCharacter)
{
@@ -1,4 +1,5 @@
using Barotrauma.Items.Components;
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
@@ -11,14 +12,14 @@ namespace Barotrauma
{
public override string DebugTag => "idle";
public override bool AllowAutomaticItemUnequipping => true;
public override bool AllowOutsideSubmarine => true;
public override bool AllowInAnySub => true;
private BehaviorType behavior;
public BehaviorType Behavior
{
get { return behavior; }
set
{
set
{
behavior = value;
switch (behavior)
{
@@ -27,16 +28,13 @@ namespace Barotrauma
newTargetIntervalMax = 20;
standStillMin = 2;
standStillMax = 10;
walkDurationMin = 5;
walkDurationMax = 10;
break;
case BehaviorType.Passive:
case BehaviorType.StayInHull:
newTargetIntervalMin = 60;
newTargetIntervalMax = 120;
standStillMin = 30;
standStillMax = 60;
walkDurationMin = 5;
walkDurationMax = 10;
break;
}
}
@@ -46,8 +44,8 @@ namespace Barotrauma
private float newTargetIntervalMax;
private float standStillMin;
private float standStillMax;
private float walkDurationMin;
private float walkDurationMax;
private readonly float walkDurationMin = 5;
private readonly float walkDurationMax = 10;
public enum BehaviorType
{
@@ -55,7 +53,7 @@ namespace Barotrauma
Passive,
StayInHull
}
public Hull TargetHull { get; set; }
private Hull currentTarget;
private float newTargetTimer;
@@ -86,7 +84,7 @@ namespace Barotrauma
protected override bool Check() => false;
public override bool CanBeCompleted => true;
public override bool IsLoop { get => true; set => throw new Exception("Trying to set the value for IsLoop from: " + Environment.StackTrace); }
public override bool IsLoop { get => true; set => throw new Exception("Trying to set the value for IsLoop from: " + Environment.StackTrace.CleanupStackTrace()); }
public readonly HashSet<string> PreferredOutpostModuleTypes = new HashSet<string>();
@@ -142,6 +140,8 @@ namespace Barotrauma
timerMargin = 0;
}
private bool IsSteeringFinished() => PathSteering.CurrentPath != null && (PathSteering.CurrentPath.Finished || PathSteering.CurrentPath.Unreachable);
protected override void Act(float deltaTime)
{
if (PathSteering == null) { return; }
@@ -164,12 +164,30 @@ namespace Barotrauma
character.DeselectCharacter();
}
if (behavior != BehaviorType.StayInHull)
if (!character.IsClimbing)
{
bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) ||
(PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)));
character.SelectedConstruction = null;
}
bool IsSteeringFinished() => PathSteering.CurrentPath != null && (PathSteering.CurrentPath.Finished || PathSteering.CurrentPath.Unreachable);
CleanupItems(deltaTime);
if (behavior == BehaviorType.StayInHull)
{
currentTarget = TargetHull;
bool stayInHull = character.CurrentHull == currentTarget && IsSteeringFinished() && !character.IsClimbing;
if (stayInHull)
{
Wander(deltaTime);
}
else if (currentTarget != null)
{
PathSteering.SteeringSeek(character.GetRelativeSimPosition(currentTarget), weight: 1, nodeFilter: node => node.Waypoint.CurrentHull != null);
}
}
else
{
bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) ||
(PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)));
if (currentTarget != null && !currentTargetIsInvalid)
{
@@ -226,7 +244,7 @@ namespace Barotrauma
searchingNewHull = true;
return;
}
else if (targetHulls.Count > 0)
else if (targetHulls.Any())
{
//choose a random available hull
currentTarget = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced);
@@ -242,12 +260,13 @@ namespace Barotrauma
});
if (path.Unreachable)
{
//can't go to this room, remove it from the list and try another room next frame
//can't go to this room, remove it from the list and try another room
int index = targetHulls.IndexOf(currentTarget);
targetHulls.RemoveAt(index);
hullWeights.RemoveAt(index);
PathSteering.Reset();
currentTarget = null;
SetTargetTimerLow();
return;
}
searchingNewHull = false;
@@ -263,47 +282,25 @@ namespace Barotrauma
{
character.AIController.SelectTarget(currentTarget.AiTarget);
string errorMsg = null;
#if DEBUG
#if DEBUG
bool isRoomNameFound = currentTarget.DisplayName != null;
errorMsg = "(Character " + character.Name + " idling, target " + (isRoomNameFound ? currentTarget.DisplayName : currentTarget.ToString()) + ")";
#endif
#endif
var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsgStr: errorMsg, nodeFilter: node => node.Waypoint.CurrentHull != null);
PathSteering.SetPath(path);
}
SetTargetTimerNormal();
}
}
newTargetTimer -= deltaTime;
}
//wander randomly
// - if reached the end of the path
// - if the target is unreachable
// - if the path requires going outside
if (!character.IsClimbing)
{
if (behavior == BehaviorType.StayInHull || SteeringManager != PathSteering || (PathSteering.CurrentPath != null &&
(PathSteering.CurrentPath.Finished || PathSteering.CurrentPath.Unreachable || PathSteering.CurrentPath.HasOutdoorsNodes)))
if (!character.IsClimbing && IsSteeringFinished())
{
Wander(deltaTime);
return;
}
character.SelectedConstruction = null;
}
if (currentTarget != null)
{
if (SteeringManager == PathSteering)
else if (currentTarget != null)
{
PathSteering.SteeringSeek(character.GetRelativeSimPosition(currentTarget), weight: 1, nodeFilter: node => node.Waypoint.CurrentHull != null);
}
else
{
character.AIController.SteeringManager.SteeringSeek(character.GetRelativeSimPosition(currentTarget));
}
}
else
{
Wander(deltaTime);
}
}
@@ -328,7 +325,7 @@ namespace Barotrauma
{
//if there are characters too close on both sides, don't try to steer away from them
//because it'll cause the character to spaz out trying to avoid both
if (tooCloseCharacter != null &&
if (tooCloseCharacter != null &&
Math.Sign(tooCloseCharacter.WorldPosition.X - character.WorldPosition.X) != Math.Sign(c.WorldPosition.X - character.WorldPosition.X))
{
tooCloseCharacter = null;
@@ -336,7 +333,7 @@ namespace Barotrauma
}
tooCloseCharacter = c;
}
HumanAIController.FaceTarget(c);
HumanAIController.FaceTarget(c);
}
}
@@ -344,7 +341,7 @@ namespace Barotrauma
{
Vector2 diff = character.WorldPosition - tooCloseCharacter.WorldPosition;
if (diff.LengthSquared() < 0.0001f) { diff = Rand.Vector(1.0f); }
if (Math.Abs(diff.X) > 0 &&
if (Math.Abs(diff.X) > 0 &&
(character.WorldPosition.X > currentHull.WorldRect.Right - 50 || character.WorldPosition.X < currentHull.WorldRect.Left + 50))
{
// Between a wall and a character -> move away
@@ -459,6 +456,48 @@ namespace Barotrauma
}
}
#region Cleaning
private readonly float checkItemsInterval = 1;
private float checkItemsTimer;
private readonly List<Item> itemsToClean = new List<Item>();
private readonly List<Item> ignoredItems = new List<Item>();
private void CleanupItems(float deltaTime)
{
if (checkItemsTimer <= 0)
{
checkItemsTimer = checkItemsInterval * Rand.Range(0.9f, 1.1f);
var hull = character.CurrentHull;
if (hull != null)
{
itemsToClean.Clear();
foreach (Item item in Item.ItemList)
{
if (item.CurrentHull != hull) { continue; }
if (AIObjectiveCleanupItems.IsValidTarget(item, character) && !ignoredItems.Contains(item))
{
itemsToClean.Add(item);
}
}
if (itemsToClean.Any())
{
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);
}
}
}
}
else
{
checkItemsTimer -= deltaTime;
}
}
#endregion
public static bool IsForbidden(Hull hull)
{
if (hull == null) { return true; }
@@ -475,6 +514,9 @@ namespace Barotrauma
tooCloseCharacter = null;
targetHulls.Clear();
hullWeights.Clear();
checkItemsTimer = 0;;
itemsToClean.Clear();
ignoredItems.Clear();
autonomousObjectiveRetryTimer = 10;
}
}
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
namespace Barotrauma
@@ -46,7 +45,7 @@ namespace Barotrauma
public override bool AllowSubObjectiveSorting => true;
public virtual bool InverseTargetEvaluation => false;
public override bool IsLoop { get => true; set => throw new Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace); }
public override bool IsLoop { get => true; set => throw new Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace.CleanupStackTrace()); }
public override void Update(float deltaTime)
{
@@ -79,7 +78,12 @@ namespace Barotrauma
var target = objective.Key;
if (!Targets.Contains(target))
{
subObjectives.Remove(objective.Value);
var subObjective = objective.Value;
if (CurrentSubObjective == subObjective)
{
CurrentSubObjective.Abandon = !CurrentSubObjective.IsCompleted;
}
subObjectives.Remove(subObjective);
}
}
SyncRemovedObjectives(Objectives, GetList());
@@ -137,7 +141,7 @@ namespace Barotrauma
{
if (objectiveManager.CurrentOrder == this)
{
Priority = AIObjectiveManager.OrderPriority;
Priority = ForceOrderPriority ? AIObjectiveManager.OrderPriority : targetValue;
}
else
{
@@ -70,7 +70,7 @@ namespace Barotrauma
if (objective == null)
{
#if DEBUG
DebugConsole.ThrowError("Attempted to add a null objective to AIObjectiveManager\n" + Environment.StackTrace);
DebugConsole.ThrowError("Attempted to add a null objective to AIObjectiveManager\n" + Environment.StackTrace.CleanupStackTrace());
#endif
return;
}
@@ -128,8 +128,7 @@ namespace Barotrauma
var orderPrefab = Order.GetPrefab(autonomousObjective.identifier);
if (orderPrefab == null) { throw new Exception($"Could not find a matching prefab by the identifier: '{autonomousObjective.identifier}'"); }
var item = orderPrefab.MustSetTarget ? orderPrefab.GetMatchingItems(character.Submarine, mustBelongToPlayerSub: false, requiredTeam: character.Info.TeamID)?.GetRandom() : null;
var order = new Order(orderPrefab, item ?? character.CurrentHull as Entity,
item?.Components.FirstOrDefault(ic => ic.GetType() == orderPrefab.ItemComponentType), orderGiver: character);
var order = new Order(orderPrefab, item ?? character.CurrentHull as Entity, orderPrefab.GetTargetItemComponent(item), orderGiver: character);
if (order == null) { continue; }
if (autonomousObjective.ignoreAtOutpost && Level.IsLoadedOutpost && character.TeamID != Character.TeamType.FriendlyNPC) { continue; }
var objective = CreateObjective(order, autonomousObjective.option, character, isAutonomous: true, autonomousObjective.priorityModifier);
@@ -147,7 +146,7 @@ namespace Barotrauma
if (objective == null)
{
#if DEBUG
DebugConsole.ThrowError($"{character.Name}: Attempted to add a null objective to AIObjectiveManager\n" + Environment.StackTrace);
DebugConsole.ThrowError($"{character.Name}: Attempted to add a null objective to AIObjectiveManager\n" + Environment.StackTrace.CleanupStackTrace());
#endif
return;
}
@@ -314,9 +313,10 @@ namespace Barotrauma
};
break;
case "wait":
newObjective = new AIObjectiveGoTo(order.TargetEntity ?? character, character, this, repeat: true, priorityModifier: priorityModifier)
newObjective = new AIObjectiveGoTo(order.TargetSpatialEntity ?? character, character, this, repeat: true, priorityModifier: priorityModifier)
{
AllowGoingOutside = character.CurrentHull == null
AllowGoingOutside = order.TargetSpatialEntity == null ? character.CurrentHull == null :
character.Submarine == null || character.Submarine != order.TargetSpatialEntity.Submarine
};
break;
case "fixleaks":
@@ -374,6 +374,32 @@ namespace Barotrauma
Override = orderGiver != null && orderGiver.IsPlayer
};
break;
case "setchargepct":
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, false, priorityModifier: priorityModifier)
{
IsLoop = false,
Override = character.CurrentOrder != null,
completionCondition = () =>
{
if (float.TryParse(option, out float pct))
{
var targetRatio = Math.Clamp(pct, 0f, 1f);
var currentRatio = (order.TargetItemComponent as PowerContainer).RechargeRatio;
return Math.Abs(targetRatio - currentRatio) < 0.05f;
}
return true;
}
};
break;
case "getitem":
newObjective = new AIObjectiveGetItem(character, order.TargetEntity as Item ?? order.TargetItemComponent?.Item, this, false, priorityModifier: priorityModifier)
{
MustBeSpecificItem = true
};
break;
case "cleanupitems":
newObjective = new AIObjectiveCleanupItems(character, this, priorityModifier, order.TargetEntity as Item);
break;
default:
if (order.TargetItemComponent == null) { return null; }
if (order.TargetItemComponent.Item.NonInteractable) { return null; }
@@ -11,6 +11,7 @@ namespace Barotrauma
public override string DebugTag => $"operate item {component.Name}";
public override bool AllowAutomaticItemUnequipping => true;
public override bool AllowMultipleInstances => true;
public override bool AllowInAnySub => true;
private ItemComponent component, controller;
private Entity operateTarget;
@@ -35,9 +36,11 @@ namespace Barotrauma
public override float GetPriority()
{
bool isOrder = objectiveManager.CurrentOrder == this;
if (!IsAllowed || character.LockHands)
{
Priority = 0;
Abandon = !isOrder;
return Priority;
}
if (component.Item.ConditionPercentage <= 0)
@@ -46,7 +49,6 @@ namespace Barotrauma
}
else
{
bool isOrder = objectiveManager.CurrentOrder == this;
if (isOrder)
{
Priority = AIObjectiveManager.OrderPriority;
@@ -172,7 +174,7 @@ namespace Barotrauma
}
if (target.CanBeSelected)
{
if (character.CanInteractWith(target.Item, out _, checkLinked: false))
if (!character.IsClimbing && character.CanInteractWith(target.Item, out _, checkLinked: false))
{
HumanAIController.FaceTarget(target.Item);
if (character.SelectedConstruction != target.Item)
@@ -189,7 +191,8 @@ namespace Barotrauma
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(target.Item, character, objectiveManager, closeEnough: 50)
{
DialogueIdentifier = "dialogcannotreachtarget",
TargetName = target.Item.Name
TargetName = target.Item.Name,
endNodeFilter = node => node.Waypoint.Ladders == null
},
onAbandon: () => Abandon = true,
onCompleted: () => RemoveSubObjective(ref goToObjective));
@@ -10,6 +10,8 @@ namespace Barotrauma
{
public override string DebugTag => "repair item";
public override bool AllowInAnySub => true;
public Item Item { get; private set; }
private AIObjectiveGoTo goToObjective;
@@ -34,6 +36,7 @@ namespace Barotrauma
if (!IsAllowed)
{
Priority = 0;
Abandon = true;
return Priority;
}
// TODO: priority list?
@@ -69,7 +72,7 @@ namespace Barotrauma
IsCompleted = Item.IsFullCondition;
if (IsCompleted && IsRepairing())
{
character?.Speak(TextManager.GetWithVariable("DialogItemRepaired", "[itemname]", Item.Name, true), null, 0.0f, "itemrepaired", 10.0f);
character.Speak(TextManager.GetWithVariable("DialogItemRepaired", "[itemname]", Item.Name, true), null, 0.0f, "itemrepaired", 10.0f);
}
return IsCompleted;
}
@@ -134,7 +137,7 @@ namespace Barotrauma
return;
}
}
if (character.CanInteractWith(Item, out _, checkLinked: false))
if (!character.IsClimbing && character.CanInteractWith(Item, out _, checkLinked: false))
{
HumanAIController.FaceTarget(Item);
if (repairTool != null)
@@ -235,7 +238,11 @@ namespace Barotrauma
private void OperateRepairTool(float deltaTime)
{
character.CursorPosition = Item.Position;
character.CursorPosition = Item.WorldPosition;
if (character.Submarine != null)
{
character.CursorPosition -= character.Submarine.Position;
}
if (repairTool.Item.RequireAimToUse)
{
character.SetInput(InputType.Aim, false, true);
@@ -24,11 +24,11 @@ namespace Barotrauma
private readonly Item prioritizedItem;
public override bool AllowMultipleInstances => true;
public override bool AllowInAnySub => true;
public readonly static float RequiredSuccessFactor = 0.4f;
public override bool IsDuplicate<T>(T otherObjective) =>
(otherObjective as AIObjective) is AIObjectiveRepairItems repairObjective && repairObjective.RequireAdequateSkills == RequireAdequateSkills;
public override bool IsDuplicate<T>(T otherObjective) => otherObjective is AIObjectiveRepairItems repairObjective && repairObjective.RequireAdequateSkills == RequireAdequateSkills;
public AIObjectiveRepairItems(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1, Item prioritizedItem = null)
: base(character, objectiveManager, priorityModifier)
@@ -151,13 +151,9 @@ namespace Barotrauma
if (item.NonInteractable) { return false; }
if (item.IsFullCondition) { return false; }
if (item.CurrentHull == null) { return false; }
if (item.Submarine == null) { return false; }
if (item.Submarine.TeamID != character.TeamID) { return false; }
if (item.Submarine == null || character.Submarine == null) { return false; }
if (!character.Submarine.IsEntityFoundOnThisSub(item, includingConnectedSubs: true)) { return false; }
if (item.Repairables.None()) { return false; }
if (character.Submarine != null)
{
if (!character.Submarine.IsConnectedTo(item.Submarine)) { return false; }
}
return true;
}
}
@@ -14,6 +14,7 @@ namespace Barotrauma
public override bool KeepDivingGearOn => true;
public override bool AllowOutsideSubmarine => true;
public override bool AllowInAnySub => true;
const float TreatmentDelay = 0.5f;
@@ -35,7 +36,7 @@ namespace Barotrauma
{
if (targetCharacter == null)
{
string errorMsg = $"{character.Name}: Attempted to create a Rescue objective with no target!\n" + Environment.StackTrace;
string errorMsg = $"{character.Name}: Attempted to create a Rescue objective with no target!\n" + Environment.StackTrace.CleanupStackTrace();
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("AIObjectiveRescue:ctor:targetnull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
Abandon = true;
@@ -392,11 +393,13 @@ namespace Barotrauma
if (!IsAllowed)
{
Priority = 0;
Abandon = true;
return Priority;
}
if (character.LockHands || targetCharacter == null || targetCharacter.CurrentHull == null || targetCharacter.Removed || targetCharacter.IsDead)
{
Priority = 0;
Abandon = true;
}
else
{
@@ -11,6 +11,7 @@ namespace Barotrauma
public override bool ForceRun => true;
public override bool InverseTargetEvaluation => true;
public override bool AllowOutsideSubmarine => true;
public override bool AllowInAnySub => true;
private const float vitalityThreshold = 75;
private const float vitalityThresholdForOrders = 85;