(6eeea9b7c) v0.9.10.0.0

This commit is contained in:
Joonas Rikkonen
2020-06-04 16:41:07 +03:00
parent ce4ccd99ac
commit eeac247a8e
366 changed files with 7772 additions and 3692 deletions
@@ -118,7 +118,6 @@ namespace Barotrauma
public void TryComplete(float deltaTime)
{
if (isCompleted) { return; }
//if (Abandon && !IsLoop && subObjectives.None()) { return; }
if (CheckState()) { return; }
// Not ready -> act (can't do foreach because it's possible that the collection is modified in event callbacks.
for (int i = 0; i < subObjectives.Count; i++)
@@ -201,7 +200,7 @@ namespace Barotrauma
}
else
{
Priority = CumulatedDevotion * PriorityModifier;
Priority = CumulatedDevotion;
}
return Priority;
}
@@ -211,7 +210,7 @@ namespace Barotrauma
var currentObjective = objectiveManager.CurrentObjective;
if (currentObjective != null && (currentObjective == this || currentObjective.subObjectives.Any(so => so == this)))
{
CumulatedDevotion += Devotion * PriorityModifier * deltaTime;
CumulatedDevotion += Devotion * deltaTime;
}
}
@@ -20,6 +20,7 @@ namespace Barotrauma
{
if (battery == null) { return false; }
var item = battery.Item;
if (item.NonInteractable) { return false; }
if (item.Submarine == null) { return false; }
if (item.CurrentHull == null) { return false; }
if (item.Submarine.TeamID != character.TeamID) { return false; }
@@ -121,6 +121,12 @@ namespace Barotrauma
protected override bool Check()
{
if (initialMode == CombatMode.Offensive && Mode != CombatMode.Offensive)
{
Abandon = true;
SteeringManager.Reset();
return false;
}
bool completed = (Enemy != null && (Enemy.Removed || Enemy.IsDead)) || (initialMode != CombatMode.Offensive && coolDownTimer <= 0);
if (completed)
{
@@ -465,7 +471,6 @@ namespace Barotrauma
SteeringManager.Reset();
return;
}
retreatTarget = null;
RemoveSubObjective(ref retreatObjective);
RemoveSubObjective(ref seekAmmunition);
@@ -482,9 +487,8 @@ namespace Barotrauma
},
onAbandon: () =>
{
Mode = CombatMode.Defensive;
Abandon = true;
SteeringManager.Reset();
RemoveSubObjective(ref followTargetObjective);
});
if (followTargetObjective != null)
{
@@ -593,10 +597,7 @@ namespace Barotrauma
private void Attack(float deltaTime)
{
float squaredDistance = Vector2.DistanceSquared(character.Position, Enemy.Position);
character.CursorPosition = Enemy.Position;
float engageDistance = 500;
if (character.CurrentHull != Enemy.CurrentHull && squaredDistance > engageDistance * engageDistance) { return; }
if (!character.CanSeeCharacter(Enemy)) { return; }
if (Weapon.RequireAimToUse)
{
@@ -604,7 +605,7 @@ namespace Barotrauma
if (SteeringManager == PathSteering)
{
var door = PathSteering.CurrentPath?.CurrentNode?.ConnectedDoor;
if (door != null && !door.IsOpen)
if (door != null && !door.IsOpen && !door.IsBroken)
{
isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents<Controller>(true).Any();
}
@@ -626,7 +627,7 @@ namespace Barotrauma
}
if (WeaponComponent is MeleeWeapon meleeWeapon)
{
if (squaredDistance <= meleeWeapon.Range * meleeWeapon.Range)
if (Vector2.DistanceSquared(character.Position, Enemy.Position) <= meleeWeapon.Range * meleeWeapon.Range)
{
character.SetInput(InputType.Shoot, false, true);
Weapon.Use(deltaTime, character);
@@ -636,7 +637,7 @@ namespace Barotrauma
{
if (WeaponComponent is RepairTool repairTool)
{
if (squaredDistance > repairTool.Range * repairTool.Range) { return; }
if (Vector2.DistanceSquared(character.Position, Enemy.Position) > repairTool.Range * repairTool.Range) { return; }
}
if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - Weapon.Position) < MathHelper.PiOver4)
{
@@ -158,7 +158,7 @@ namespace Barotrauma
Abandon = true;
}, onCompleted: () =>
{
if (getItemObjective.TargetItem != null)
if (getItemObjective?.TargetItem != null)
{
containedItems.Add(getItemObjective.TargetItem);
}
@@ -106,7 +106,7 @@ namespace Barotrauma
if (SteeringManager == PathSteering)
{
var door = PathSteering.CurrentPath?.CurrentNode?.ConnectedDoor;
if (door != null && !door.IsOpen)
if (door != null && !door.IsOpen && !door.IsBroken)
{
isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents<Controller>(true).Any();
}
@@ -1,9 +1,4 @@
using Barotrauma.Items.Components;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Barotrauma.Extensions;
using System.Collections.Generic;
namespace Barotrauma
{
@@ -43,7 +43,7 @@ namespace Barotrauma
}
if (character.CurrentHull == null)
{
Priority = objectiveManager.CurrentOrder is AIObjectiveGoTo ? 0 : 100;
Priority = objectiveManager.CurrentOrder is AIObjectiveGoTo && HumanAIController.HasDivingSuit(character) ? 0 : 100;
}
else
{
@@ -83,8 +83,9 @@ namespace Barotrauma
else
{
float dangerFactor = (100 - currenthullSafety) / 100;
Priority = Math.Min(Priority + dangerFactor * priorityIncrease * deltaTime, 100);
Priority += dangerFactor * priorityIncrease * deltaTime;
}
Priority = MathHelper.Clamp(Priority, 0, 100);
}
}
@@ -93,34 +94,39 @@ namespace Barotrauma
protected override void Act(float deltaTime)
{
var currentHull = character.CurrentHull;
bool needsDivingGear = HumanAIController.NeedsDivingGear(character, currentHull, out bool needsDivingSuit);
bool needsEquipment = false;
if (needsDivingSuit)
bool dangerousPressure = currentHull == null || currentHull.LethalPressure > 0;
if (!dangerousPressure)
{
needsEquipment = !HumanAIController.HasDivingSuit(character, AIObjectiveFindDivingGear.lowOxygenThreshold);
}
else if (needsDivingGear)
{
needsEquipment = !HumanAIController.HasDivingGear(character, AIObjectiveFindDivingGear.lowOxygenThreshold);
}
if (needsEquipment && divingGearObjective == null && !character.LockHands)
{
RemoveSubObjective(ref goToObjective);
TryAddSubObjective(ref divingGearObjective,
constructor: () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager),
onAbandon: () =>
{
searchHullTimer = Math.Min(1, searchHullTimer);
// Don't try to seek diving gear if the pressure is dangerous. Just get out.
bool needsDivingGear = HumanAIController.NeedsDivingGear(character, currentHull, out bool needsDivingSuit);
bool needsEquipment = false;
if (needsDivingSuit)
{
needsEquipment = !HumanAIController.HasDivingSuit(character, AIObjectiveFindDivingGear.lowOxygenThreshold);
}
else if (needsDivingGear)
{
needsEquipment = !HumanAIController.HasDivingGear(character, AIObjectiveFindDivingGear.lowOxygenThreshold);
}
if (needsEquipment && divingGearObjective == null && !character.LockHands)
{
RemoveSubObjective(ref goToObjective);
TryAddSubObjective(ref divingGearObjective,
constructor: () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager),
onAbandon: () =>
{
searchHullTimer = Math.Min(1, searchHullTimer);
// Don't reset the diving gear objective, because it's possible that there is no diving gear -> seek a safe hull and then reset so that we can check again.
},
onCompleted: () =>
{
resetPriority = true;
searchHullTimer = Math.Min(1, searchHullTimer);
RemoveSubObjective(ref divingGearObjective);
});
onCompleted: () =>
{
resetPriority = true;
searchHullTimer = Math.Min(1, searchHullTimer);
RemoveSubObjective(ref divingGearObjective);
});
}
}
else if (divingGearObjective == null || !divingGearObjective.CanBeCompleted)
if (divingGearObjective == null || !divingGearObjective.CanBeCompleted)
{
if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD)
{
@@ -37,7 +37,7 @@ namespace Barotrauma
protected override float TargetEvaluation()
{
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveFixLeaks>(), onlyBots: true);
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveFixLeaks>() && !c.Character.IsIncapacitated, onlyBots: true);
int totalLeaks = Targets.Count();
if (totalLeaks == 0) { return 0; }
int secondaryLeaks = Targets.Count(l => l.IsRoomToRoom);
@@ -3,6 +3,7 @@ using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -20,7 +21,9 @@ namespace Barotrauma
//can be either tags or identifiers
private string[] itemIdentifiers;
public IEnumerable<string> Identifiers => itemIdentifiers;
private Item targetItem, moveToTarget, rootContainer;
private Item targetItem;
private ISpatialEntity moveToTarget;
private bool isDoneSeeking;
public Item TargetItem => targetItem;
private int currSearchIndex;
@@ -29,6 +32,8 @@ namespace Barotrauma
private float currItemPriority;
private bool checkInventory;
public static float DefaultReach = 100;
public bool AllowToFindDivingGear { get; set; } = true;
public AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip = true, float priorityModifier = 1)
@@ -37,6 +42,7 @@ namespace Barotrauma
currSearchIndex = -1;
this.equip = equip;
this.targetItem = targetItem;
moveToTarget = targetItem?.GetRootInventoryOwner();
}
public AIObjectiveGetItem(Character character, string itemIdentifier, AIObjectiveManager objectiveManager, bool equip = true, bool checkInventory = true, float priorityModifier = 1)
@@ -62,8 +68,7 @@ namespace Barotrauma
if (item != null)
{
targetItem = item;
rootContainer = item.GetRootContainer();
moveToTarget = rootContainer ?? item;
moveToTarget = item.GetRootInventoryOwner();
}
return item != null;
}
@@ -86,6 +91,15 @@ namespace Barotrauma
}
if (!isDoneSeeking)
{
bool dangerousPressure = character.CurrentHull == null || character.CurrentHull.LethalPressure > 0;
if (dangerousPressure)
{
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Seeking item aborted, because the pressure is dangerous.", Color.Yellow);
#endif
Abandon = true;
return;
}
FindTargetItem();
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
return;
@@ -108,7 +122,26 @@ namespace Barotrauma
Reset();
return;
}
if (character.CanInteractWith(targetItem, out _, checkLinked: false))
bool canInteract = false;
if (moveToTarget is Character c)
{
if (character == c)
{
canInteract = true;
moveToTarget = null;
}
else
{
character.SelectCharacter(c);
canInteract = character.CanInteractWith(c, maxDist: DefaultReach);
character.DeselectCharacter();
}
}
else if (moveToTarget is Item parentItem)
{
canInteract = character.CanInteractWith(parentItem, out _, checkLinked: false);
}
if (canInteract)
{
var pickable = targetItem.GetComponent<Pickable>();
if (pickable == null)
@@ -173,17 +206,17 @@ namespace Barotrauma
}
}
}
else
else if (moveToTarget != null)
{
TryAddSubObjective(ref goToObjective,
constructor: () =>
{
return new AIObjectiveGoTo(moveToTarget, character, objectiveManager, repeat: false, getDivingGearIfNeeded: AllowToFindDivingGear)
return new AIObjectiveGoTo(moveToTarget, character, objectiveManager, repeat: false, getDivingGearIfNeeded: AllowToFindDivingGear, closeEnough: DefaultReach)
{
// If the root container changes, the item is no longer where it was (taken by someone -> need to find another item)
abortCondition = () => targetItem == null || targetItem.GetRootContainer() != rootContainer,
abortCondition = () => targetItem == null || targetItem.GetRootInventoryOwner() != moveToTarget,
DialogueIdentifier = "dialogcannotreachtarget",
TargetName = moveToTarget.Name
TargetName = (moveToTarget as MapEntity)?.Name ?? (moveToTarget as Character)?.Name ?? moveToTarget.ToString()
};
},
onAbandon: () =>
@@ -212,9 +245,9 @@ namespace Barotrauma
{
currSearchIndex++;
var item = Item.ItemList[currSearchIndex];
if (item.Submarine == null) { continue; }
if (item.CurrentHull == null) { continue; }
if (item.Submarine.TeamID != character.TeamID) { continue; }
Submarine itemSub = item.Submarine ?? item.ParentInventory?.Owner?.Submarine;
if (itemSub == null) { continue; }
if (itemSub.TeamID != character.TeamID) { continue; }
if (!CheckItem(item)) { continue; }
if (ignoredContainerIdentifiers != null && item.Container != null)
{
@@ -222,8 +255,8 @@ namespace Barotrauma
}
if (character.Submarine != null)
{
if (item.Submarine.Info.Type != character.Submarine.Info.Type) { continue; }
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; }
if (itemSub.Info.Type != character.Submarine.Info.Type) { continue; }
if (character.Submarine.GetConnectedSubs().None(s => s == itemSub && itemSub.TeamID == character.TeamID && itemSub.Info.Type == character.Submarine.Info.Type)) { continue; }
}
if (character.IsItemTakenBySomeoneElse(item)) { continue; }
float itemPriority = 1;
@@ -231,8 +264,8 @@ namespace Barotrauma
{
itemPriority = GetItemPriority(item);
}
Item rootContainer = item.GetRootContainer();
Vector2 itemPos = (rootContainer ?? item).WorldPosition;
Entity rootInventoryOwner = item.GetRootInventoryOwner();
Vector2 itemPos = (rootInventoryOwner ?? item).WorldPosition;
float yDist = Math.Abs(character.WorldPosition.Y - itemPos.Y);
yDist = yDist > 100 ? yDist * 5 : 0;
float dist = Math.Abs(character.WorldPosition.X - itemPos.X) + yDist;
@@ -243,8 +276,7 @@ namespace Barotrauma
if (itemPriority < currItemPriority) { continue; }
currItemPriority = itemPriority;
targetItem = item;
moveToTarget = rootContainer ?? item;
this.rootContainer = rootContainer;
moveToTarget = rootInventoryOwner ?? item;
}
if (currSearchIndex >= Item.ItemList.Count - 1)
{
@@ -293,7 +325,6 @@ namespace Barotrauma
RemoveSubObjective(ref goToObjective);
targetItem = null;
moveToTarget = null;
rootContainer = null;
isDoneSeeking = false;
currSearchIndex = 0;
}
@@ -80,21 +80,28 @@ namespace Barotrauma
this.repeat = repeat;
waitUntilPathUnreachable = 3.0f;
this.getDivingGearIfNeeded = getDivingGearIfNeeded;
CloseEnough = closeEnough;
if (Target is Item i)
{
CloseEnough = Math.Max(CloseEnough, i.InteractDistance + Math.Max(i.Rect.Width, i.Rect.Height) / 2);
}
else if (Target is Character)
{
CloseEnough = Math.Max(closeEnough, AIObjectiveGetItem.DefaultReach);
}
else
{
CloseEnough = closeEnough;
}
}
private void SpeakCannotReach()
{
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {Target.ToString()}", Color.Yellow);
DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {Target}", Color.Yellow);
#endif
if (objectiveManager.CurrentOrder != null && DialogueIdentifier != null)
{
string msg = TargetName == null ? TextManager.Get(DialogueIdentifier, true) : TextManager.GetWithVariable(DialogueIdentifier, "[name]", TargetName, true);
string msg = TargetName == null ? TextManager.Get(DialogueIdentifier, true) : TextManager.GetWithVariable(DialogueIdentifier, "[name]", TargetName, formatCapitals: !(Target is Character));
if (msg != null)
{
character.Speak(msg, identifier: DialogueIdentifier, minDurationBetweenSimilar: 20.0f);
@@ -213,10 +220,19 @@ namespace Barotrauma
return;
}
}
if (repeat && IsCloseEnough)
if (repeat)
{
OnCompleted();
return;
if (IsCloseEnough)
{
if (requiredCondition == null || requiredCondition())
{
if (character.CanSeeTarget(Target))
{
OnCompleted();
return;
}
}
}
}
if (SteeringManager == PathSteering)
{
@@ -244,7 +260,7 @@ namespace Barotrauma
}
}
private Hull GetTargetHull()
public Hull GetTargetHull()
{
if (Target is Hull h)
{
@@ -284,13 +300,7 @@ namespace Barotrauma
//otherwise characters can let go of the ladders too soon once they're close enough to the target
if (PathSteering.CurrentPath.NextNode != null) { return false; }
}
bool closeEnough = Vector2.DistanceSquared(Target.WorldPosition, character.WorldPosition) < CloseEnough * CloseEnough;
if (closeEnough)
{
closeEnough = !(Target is Character) || Target is Character c && c.CurrentHull == character.CurrentHull;
}
return closeEnough;
return Vector2.DistanceSquared(Target.WorldPosition, character.WorldPosition) < CloseEnough * CloseEnough;
}
}
@@ -326,7 +336,9 @@ namespace Barotrauma
}
else if (Target is Character targetCharacter)
{
if (character.CanInteractWith(targetCharacter, CloseEnough)) { IsCompleted = true; }
character.SelectCharacter(targetCharacter);
if (character.CanInteractWith(targetCharacter, skipDistanceCheck: true)) { IsCompleted = true; }
character.DeselectCharacter();
}
else
{
@@ -338,6 +350,16 @@ namespace Barotrauma
return IsCompleted;
}
protected override void OnAbandon()
{
StopMovement();
if (SteeringManager == PathSteering)
{
PathSteering.ResetPath();
}
base.OnAbandon();
}
private void StopMovement()
{
character.AIController.SteeringManager.Reset();
@@ -3,7 +3,6 @@ using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -35,41 +34,43 @@ namespace Barotrauma
{
standStillTimer = Rand.Range(-10.0f, 10.0f);
walkDuration = Rand.Range(0.0f, 10.0f);
CalculatePriority();
}
protected override bool Check() => false;
public override bool CanBeCompleted => true;
public override bool IsLoop { get => true; set => throw new System.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: " + Environment.StackTrace); }
private float randomTimer;
private float randomUpdateInterval = 5;
public float Random { get; private set; }
public void CalculatePriority()
public void CalculatePriority(float max = 0)
{
Random = Rand.Range(0.5f, 1.5f);
randomTimer = randomUpdateInterval;
float max = Math.Min(Math.Min(AIObjectiveManager.RunPriority, AIObjectiveManager.OrderPriority) - 1, 100);
float initiative = character.GetSkillLevel("initiative");
Priority = MathHelper.Lerp(1, max, MathUtils.InverseLerp(100, 0, initiative * Random));
//Random = Rand.Range(0.5f, 1.5f);
//randomTimer = randomUpdateInterval;
//max = max > 0 ? max : Math.Min(Math.Min(AIObjectiveManager.RunPriority, AIObjectiveManager.OrderPriority) - 1, 100);
//float initiative = character.GetSkillLevel("initiative");
//Priority = MathHelper.Lerp(1, max, MathUtils.InverseLerp(100, 0, initiative * Random));
Priority = 1;
}
public override float GetPriority() => Priority;
public override void Update(float deltaTime)
{
if (objectiveManager.CurrentObjective == this)
{
if (randomTimer > 0)
{
randomTimer -= deltaTime;
}
else
{
CalculatePriority();
}
}
//if (objectiveManager.CurrentObjective == this)
//{
// if (randomTimer > 0)
// {
// randomTimer -= deltaTime;
// }
// else
// {
// CalculatePriority();
// }
//}
}
protected override void Act(float deltaTime)
@@ -129,7 +130,7 @@ namespace Barotrauma
//choose a random available hull
currentTarget = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced);
bool isCurrentHullAllowed = !IsForbidden(character.CurrentHull);
var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, nodeFilter: node =>
var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsgStr: $"AIObjectiveIdle {character.DisplayName}", nodeFilter: node =>
{
if (node.Waypoint.CurrentHull == null) { return false; }
// Check that there is no unsafe or forbidden hulls on the way to the target
@@ -47,7 +47,7 @@ namespace Barotrauma
public override bool AllowSubObjectiveSorting => true;
public virtual bool InverseTargetEvaluation => false;
public override bool IsLoop { get => true; set => throw new System.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); }
public override void Update(float deltaTime)
{
@@ -204,7 +204,7 @@ namespace Barotrauma
{
Objectives.Remove(target);
ignoreList.Add(target);
targetUpdateTimer = 0;
targetUpdateTimer = Math.Min(0.1f, targetUpdateTimer);
};
}
}
@@ -14,8 +14,11 @@ namespace Barotrauma
public const float OrderPriority = 70;
public const float RunPriority = 50;
// Constantly increases the priority of the selected objective, unless overridden
public const float baseDevotion = 3;
public const float baseDevotion = 5;
/// <summary>
/// Excluding the current order.
/// </summary>
public List<AIObjective> Objectives { get; private set; } = new List<AIObjective>();
private readonly Character character;
@@ -88,8 +91,25 @@ namespace Barotrauma
public Dictionary<AIObjective, CoroutineHandle> DelayedObjectives { get; private set; } = new Dictionary<AIObjective, CoroutineHandle>();
private void ClearIgnored()
{
if (character.AIController is HumanAIController humanAi)
{
humanAi.UnreachableHulls.Clear();
humanAi.IgnoredItems.Clear();
}
}
public void CreateAutonomousObjectives()
{
if (character.IsDead)
{
#if DEBUG
DebugConsole.ThrowError("Attempted to create autonomous orders for a dead character");
#else
return;
#endif
}
foreach (var delayedObjective in DelayedObjectives)
{
CoroutineManager.StopCoroutines(delayedObjective.Value);
@@ -99,15 +119,15 @@ namespace Barotrauma
AddObjective(new AIObjectiveFindSafety(character, this));
AddObjective(new AIObjectiveIdle(character, this));
int objectiveCount = Objectives.Count;
foreach (var automaticOrder in character.Info.Job.Prefab.AutomaticOrders)
foreach (var autonomousObjective in character.Info.Job.Prefab.AutonomousObjective)
{
var orderPrefab = Order.GetPrefab(automaticOrder.identifier);
if (orderPrefab == null) { throw new Exception($"Could not find a matching prefab by the identifier: '{automaticOrder.identifier}'"); }
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, false)?.GetRandom() : null;
var order = new Order(orderPrefab, item ?? character.CurrentHull as Entity,
item?.Components.FirstOrDefault(ic => ic.GetType() == orderPrefab.ItemComponentType), orderGiver: character);
if (order == null) { continue; }
var objective = CreateObjective(order, automaticOrder.option, character, automaticOrder.priorityModifier);
var objective = CreateObjective(order, autonomousObjective.option, character, isAutonomous: true, autonomousObjective.priorityModifier);
if (objective != null && objective.CanBeCompleted)
{
AddObjective(objective, delay: Rand.Value() / 2);
@@ -160,7 +180,7 @@ namespace Barotrauma
{
previousObjective?.OnDeselected();
CurrentObjective?.OnSelected();
GetObjective<AIObjectiveIdle>().CalculatePriority();
GetObjective<AIObjectiveIdle>().CalculatePriority(Math.Max(CurrentObjective.Priority - 10, 0));
}
return CurrentObjective;
}
@@ -172,7 +192,21 @@ namespace Barotrauma
public void UpdateObjectives(float deltaTime)
{
CurrentOrder?.Update(deltaTime);
if (CurrentOrder != null)
{
#if DEBUG
// Note: don't automatically remove orders here. Removing orders needs to be done via dismissing.
if (CurrentOrder.IsCompleted)
{
DebugConsole.NewMessage($"{character.Name}: ORDER {CurrentOrder.DebugTag} IS COMPLETED. CURRENTLY ALL ORDERS SHOULD BE LOOPING.", Color.Red);
}
else if (!CurrentOrder.CanBeCompleted)
{
DebugConsole.NewMessage($"{character.Name}: ORDER {CurrentOrder.DebugTag}, CANNOT BE COMPLETED.", Color.Red);
}
#endif
CurrentOrder.Update(deltaTime);
}
if (WaitTimer > 0)
{
WaitTimer -= deltaTime;
@@ -195,7 +229,7 @@ namespace Barotrauma
#endif
Objectives.Remove(objective);
}
else if (objective != CurrentOrder)
else
{
objective.Update(deltaTime);
}
@@ -233,7 +267,16 @@ namespace Barotrauma
public void SetOrder(Order order, string option, Character orderGiver)
{
CurrentOrder = CreateObjective(order, option, orderGiver);
if (character.IsDead)
{
#if DEBUG
DebugConsole.ThrowError("Attempted to set an order for a dead character");
#else
return;
#endif
}
ClearIgnored();
CurrentOrder = CreateObjective(order, option, orderGiver, isAutonomous: false);
if (CurrentOrder == null)
{
// Recreate objectives, because some of them may be removed, if impossible to complete (e.g. due to path finding)
@@ -245,7 +288,7 @@ namespace Barotrauma
}
}
public AIObjective CreateObjective(Order order, string option, Character orderGiver, float priorityModifier = 1)
public AIObjective CreateObjective(Order order, string option, Character orderGiver, bool isAutonomous, float priorityModifier = 1)
{
if (order == null) { return null; }
AIObjective newObjective;
@@ -284,14 +327,21 @@ namespace Barotrauma
newObjective = new AIObjectiveRepairItems(character, this, priorityModifier: priorityModifier, prioritizedItem: order.TargetEntity as Item)
{
RelevantSkill = order.AppropriateSkill,
RequireAdequateSkills = option == "jobspecific"
RequireAdequateSkills = isAutonomous
};
break;
case "pumpwater":
if (order.TargetItemComponent is Pump targetPump)
{
newObjective = new AIObjectiveOperateItem(targetPump, character, this, option, false, priorityModifier: priorityModifier);
// newObjective.Completed += DismissSelf;
if (order.TargetItemComponent.Item.NonInteractable) { return null; }
newObjective = new AIObjectiveOperateItem(targetPump, character, this, option, false, priorityModifier: priorityModifier)
{
IsLoop = true,
Override = orderGiver != null && orderGiver.IsPlayer
};
// ItemComponent.AIOperate() returns false by default -> We'd have to set IsLoop = false and implement a custom override of AIOperate for the Pump.cs,
// if we want that the bot just switches the pump on/off and continues doing something else.
// If we want that the bot does the objective and then forgets about it, I think we could do the same plus dismiss when the bot is done.
}
else
{
@@ -306,9 +356,11 @@ namespace Barotrauma
break;
case "steer":
var steering = (order?.TargetEntity as Item)?.GetComponent<Steering>();
if (steering != null) steering.PosToMaintain = steering.Item.Submarine?.WorldPosition;
if (steering != null) { steering.PosToMaintain = steering.Item.Submarine?.WorldPosition; }
if (order.TargetItemComponent == null) { return null; }
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, requireEquip: false, useController: order.UseController, priorityModifier: priorityModifier)
if (order.TargetItemComponent.Item.NonInteractable) { return null; }
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option,
requireEquip: false, useController: order.UseController, controller: order.ConnectedController, priorityModifier: priorityModifier)
{
IsLoop = true,
// Don't override unless it's an order by a player
@@ -317,12 +369,15 @@ namespace Barotrauma
break;
default:
if (order.TargetItemComponent == null) { return null; }
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, requireEquip: false, useController: order.UseController, priorityModifier: priorityModifier)
if (order.TargetItemComponent.Item.NonInteractable) { return null; }
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option,
requireEquip: false, useController: order.UseController, controller: order.ConnectedController, priorityModifier: priorityModifier)
{
IsLoop = true,
// Don't override unless it's an order by a player
Override = orderGiver != null && orderGiver.IsPlayer
};
if (newObjective.Abandon) { return null; }
break;
}
return newObjective;
@@ -3,7 +3,6 @@ using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -29,6 +28,7 @@ namespace Barotrauma
public ItemComponent GetTarget() => useController ? controller : component;
public Func<bool> completionCondition;
private bool isDoneOperating;
public override float GetPriority()
{
@@ -47,17 +47,41 @@ namespace Barotrauma
{
Priority = AIObjectiveManager.OrderPriority;
}
Item targetItem = GetTarget()?.Item;
ItemComponent target = GetTarget();
Item targetItem = target?.Item;
if (targetItem == null)
{
#if DEBUG
DebugConsole.ThrowError("Item or component of AI Objective Operate item wass null. This shouldn't happen.");
DebugConsole.ThrowError("Item or component of AI Objective Operate item was null. This shouldn't happen.");
#endif
Abandon = true;
Priority = 0;
return 0.0f;
return Priority;
}
if (targetItem.CurrentHull == null || targetItem.CurrentHull.FireSources.Any() || HumanAIController.IsItemOperatedByAnother(GetTarget(), out _))
var reactor = component?.Item.GetComponent<Reactor>();
if (reactor != null)
{
switch (Option)
{
case "shutdown":
if (!reactor.PowerOn)
{
Priority = 0;
return Priority;
}
break;
case "powerup":
// Check that we don't already have another order that is targeting the same item.
// Without this the autonomous objective will tell the bot to turn the reactor on again.
if (objectiveManager.CurrentOrder is AIObjectiveOperateItem operateOrder && operateOrder != this && operateOrder.GetTarget() == target)
{
Priority = 0;
return Priority;
}
break;
}
}
if (targetItem.CurrentHull == null || targetItem.CurrentHull.FireSources.Any() || HumanAIController.IsItemOperatedByAnother(target, out _))
{
Priority = 0;
}
@@ -68,26 +92,33 @@ namespace Barotrauma
else
{
float value = CumulatedDevotion + (AIObjectiveManager.OrderPriority * PriorityModifier);
float max = MathHelper.Min((AIObjectiveManager.OrderPriority - 1), 90);
float max = objectiveManager.CurrentOrder == this ? MathHelper.Min(AIObjectiveManager.OrderPriority, 90) : AIObjectiveManager.RunPriority - 1;
Priority = MathHelper.Clamp(value, 0, max);
}
}
return Priority;
}
public AIObjectiveOperateItem(ItemComponent item, Character character, AIObjectiveManager objectiveManager, string option, bool requireEquip, Entity operateTarget = null, bool useController = false, float priorityModifier = 1)
public AIObjectiveOperateItem(ItemComponent item, Character character, AIObjectiveManager objectiveManager, string option, bool requireEquip,
Entity operateTarget = null, bool useController = false, ItemComponent controller = null, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier, option)
{
this.component = item ?? throw new System.ArgumentNullException("item", "Attempted to create an AIObjectiveOperateItem with a null target.");
component = item ?? throw new ArgumentNullException("item", "Attempted to create an AIObjectiveOperateItem with a null target.");
this.requireEquip = requireEquip;
this.operateTarget = operateTarget;
this.useController = useController;
if (useController)
if (useController) { this.controller = controller ?? component?.Item?.FindController(); }
var target = GetTarget();
if (target == null)
{
//try finding the controller with the simpler non-recursive method first
controller =
component.Item.GetConnectedComponents<Controller>().FirstOrDefault() ??
component.Item.GetConnectedComponents<Controller>(recursive: true).FirstOrDefault();
#if DEBUG
throw new Exception("target null");
#endif
Abandon = true;
}
else if (target.Item.NonInteractable)
{
Abandon = true;
}
}
@@ -122,7 +153,7 @@ namespace Barotrauma
}
if (component.AIOperate(deltaTime, character, this))
{
IsCompleted = completionCondition == null || completionCondition();
isDoneOperating = completionCondition == null || completionCondition();
}
}
else
@@ -189,12 +220,12 @@ namespace Barotrauma
}
if (component.AIOperate(deltaTime, character, this))
{
IsCompleted = completionCondition == null || completionCondition();
isDoneOperating = completionCondition == null || completionCondition();
}
}
}
}
protected override bool Check() => IsCompleted && !IsLoop;
protected override bool Check() => isDoneOperating && !IsLoop;
}
}
@@ -3,7 +3,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -27,6 +26,7 @@ namespace Barotrauma
protected override bool Filter(Pump pump)
{
if (pump == null) { return false; }
if (pump.Item.NonInteractable) { return false; }
if (pump.Item.HasTag("ballast")) { return false; }
if (pump.Item.Submarine == null) { return false; }
if (pump.Item.CurrentHull == null) { return false; }
@@ -3,7 +3,6 @@ using Microsoft.Xna.Framework;
using System;
using System.Linq;
using Barotrauma.Extensions;
using FarseerPhysics;
namespace Barotrauma
{
@@ -52,12 +51,11 @@ namespace Barotrauma
float dist = Math.Abs(character.WorldPosition.X - Item.WorldPosition.X) + yDist;
distanceFactor = MathHelper.Lerp(1, 0.25f, MathUtils.InverseLerp(0, 5000, dist));
}
float damagePriority = isPriority ? 1 : MathHelper.Lerp(1, 0, Item.Condition / Item.MaxCondition);
float successFactor = isPriority ? 1 : MathHelper.Lerp(0, 1, Item.Repairables.Average(r => r.DegreeOfSuccess(character)));
float severity = isPriority ? 1 : AIObjectiveRepairItems.GetTargetPriority(Item, character);
float isSelected = IsRepairing ? 50 : 0;
float devotion = (CumulatedDevotion + isSelected) / 100;
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (damagePriority * distanceFactor * successFactor * PriorityModifier), 0, 1));
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
}
return Priority;
}
@@ -150,7 +148,8 @@ namespace Barotrauma
{
if (character.SelectedConstruction != Item)
{
if (!Item.TryInteract(character, true, true))
if (!Item.TryInteract(character, ignoreRequiredItems: true, forceSelectKey: true) &&
!Item.TryInteract(character, ignoreRequiredItems: true, forceActionKey: true))
{
Abandon = true;
}
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Barotrauma.Items.Components;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
@@ -56,7 +57,7 @@ namespace Barotrauma
{
Objectives.Remove(item);
ignoreList.Add(item);
targetUpdateTimer = 0;
targetUpdateTimer = Math.Min(0.1f, targetUpdateTimer);
};
}
break;
@@ -75,13 +76,9 @@ namespace Barotrauma
if (item != character.SelectedConstruction)
{
float condition = item.ConditionPercentage;
if (item.Repairables.All(r => condition >= r.AIRepairThreshold)) { return false; }
if (item.Repairables.All(r => condition >= r.RepairThreshold)) { return false; }
}
}
if (RequireAdequateSkills)
{
if (item.Repairables.Any(r => !r.HasRequiredSkills(character))) { return false; }
}
if (!string.IsNullOrWhiteSpace(RelevantSkill))
{
if (item.Repairables.None(r => r.requiredSkills.Any(s => s.Identifier.Equals(RelevantSkill, StringComparison.OrdinalIgnoreCase)))) { return false; }
@@ -96,7 +93,7 @@ namespace Barotrauma
// Don't stop fixing until done
return 100;
}
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveRepairItems>(), onlyBots: true);
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveRepairItems>() && !c.Character.IsIncapacitated, onlyBots: true);
int items = Targets.Count;
bool anyFixers = otherFixers > 0;
float ratio = anyFixers ? items / (float)otherFixers : 1;
@@ -111,10 +108,24 @@ namespace Barotrauma
// Enough fixers
return 0;
}
return Targets.Sum(t => 100 - t.ConditionPercentage) * ratio;
if (RequireAdequateSkills)
{
return Targets.Sum(t => GetTargetPriority(t, character)) * ratio;
}
else
{
return Targets.Sum(t => 100 - t.ConditionPercentage) * ratio;
}
}
}
public static float GetTargetPriority(Item item, Character character)
{
float damagePriority = MathHelper.Lerp(1, 0, item.Condition / item.MaxCondition);
float successFactor = MathHelper.Lerp(0, 1, item.Repairables.Average(r => r.DegreeOfSuccess(character)));
return MathHelper.Lerp(0, 100, MathHelper.Clamp(damagePriority * successFactor, 0, 1));
}
protected override IEnumerable<Item> GetList() => Item.ItemList;
protected override AIObjective ObjectiveConstructor(Item item)
@@ -126,6 +137,7 @@ namespace Barotrauma
public static bool IsValidTarget(Item item, Character character)
{
if (item == null) { return false; }
if (item.NonInteractable) { return false; }
if (item.IsFullCondition) { return false; }
if (item.CurrentHull == null) { return false; }
if (item.Submarine == null) { return false; }
@@ -38,7 +38,19 @@ namespace Barotrauma
}
this.targetCharacter = targetCharacter;
}
protected override void OnAbandon()
{
character.SelectedCharacter = null;
base.OnAbandon();
}
protected override void OnCompleted()
{
character.SelectedCharacter = null;
base.OnCompleted();
}
protected override void Act(float deltaTime)
{
if (character.LockHands || targetCharacter == null || targetCharacter.CurrentHull == null || targetCharacter.Removed || targetCharacter.IsDead)
@@ -46,16 +58,13 @@ namespace Barotrauma
Abandon = true;
return;
}
if (targetCharacter.SelectedBy != null && targetCharacter.SelectedBy != character)
var otherRescuer = targetCharacter.SelectedBy;
if (otherRescuer != null && otherRescuer != character)
{
var otherCharacter = character.SelectedBy;
if (otherCharacter != null)
{
// Someone else is rescuing/holding the target.
Abandon = otherCharacter.IsPlayer || character.GetSkillLevel("medical") < otherCharacter.GetSkillLevel("medical");
}
// Someone else is rescuing/holding the target.
Abandon = otherRescuer.IsPlayer || character.GetSkillLevel("medical") < otherRescuer.GetSkillLevel("medical");
return;
}
if (targetCharacter != character)
{
// Incapacitated target is not in a safe place -> Move to a safe place first
@@ -161,13 +170,23 @@ namespace Barotrauma
private readonly List<string> suitableItemIdentifiers = new List<string>();
private readonly List<string> itemNameList = new List<string>();
private Dictionary<string, float> currentTreatmentSuitabilities = new Dictionary<string, float>();
private readonly Dictionary<string, float> currentTreatmentSuitabilities = new Dictionary<string, float>();
private void GiveTreatment(float deltaTime)
{
if (targetCharacter == null)
{
string errorMsg = $"{character.Name}: Attempted to update a Rescue objective with no target!";
DebugConsole.ThrowError(errorMsg);
Abandon = true;
return;
}
SteeringManager?.Reset();
if (!targetCharacter.IsPlayer)
{
// If the target is a bot, don't let it move
targetCharacter.AIController?.SteeringManager.Reset();
targetCharacter.AIController?.SteeringManager?.Reset();
}
if (treatmentTimer > 0.0f)
{
@@ -182,6 +201,8 @@ namespace Barotrauma
//check if we already have a suitable treatment for any of the afflictions
foreach (Affliction affliction in GetSortedAfflictions(targetCharacter))
{
if (affliction == null) { throw new Exception("Affliction was null"); }
if (affliction.Prefab == null) { throw new Exception("Affliction prefab was null"); }
foreach (KeyValuePair<string, float> treatmentSuitability in affliction.Prefab.TreatmentSuitability)
{
if (currentTreatmentSuitabilities.ContainsKey(treatmentSuitability.Key) && currentTreatmentSuitabilities[treatmentSuitability.Key] > 0.0f)
@@ -258,7 +279,7 @@ namespace Barotrauma
ic.PlaySound(ActionType.OnUse, character);
#endif
ic.WasUsed = true;
ic.ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb);
ic.ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb, user: character);
if (ic.DeleteOnUse)
{
remove = true;
@@ -33,46 +33,31 @@ namespace Barotrauma
protected override float TargetEvaluation()
{
int otherRescuers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveRescueAll>(), onlyBots: true);
int targetCount = Targets.Count;
bool anyRescuers = otherRescuers > 0;
float ratio = anyRescuers ? targetCount / (float)otherRescuers : 1;
if (objectiveManager.CurrentOrder == this)
if (objectiveManager.CurrentOrder != this)
{
return Targets.Min(t => GetVitalityFactor(t)) / ratio;
}
else
{
float multiplier = 1;
if (anyRescuers)
if (!character.IsMedic && HumanAIController.IsTrueForAnyCrewMember(c => c != HumanAIController && c.Character.IsMedic && !c.Character.IsUnconscious))
{
float mySkill = character.GetSkillLevel("medical");
int betterRescuers = HumanAIController.CountCrew(c => c != HumanAIController && c.Character.Info.Job.GetSkillLevel("medical") >= mySkill, onlyBots: true);
if (targetCount / (float)betterRescuers <= 1)
{
// Enough rescuers
return 100;
}
else
{
bool foundOtherMedics = HumanAIController.IsTrueForAnyCrewMember(c => c != HumanAIController && c.Character.Info.Job.Prefab.Identifier == "medicaldoctor");
if (foundOtherMedics)
{
if (character.Info.Job.Prefab.Identifier != "medicaldoctor")
{
// Double the vitality factor -> less likely to take action
multiplier = 2;
}
}
}
// Don't do anything if there's a medic on board and we are not a medic
return 100;
}
return Targets.Min(t => GetVitalityFactor(t)) / ratio * multiplier;
}
float worstCondition = Targets.Min(t => GetVitalityFactor(t));
if (Targets.Contains(character))
{
if (character.Bleeding > 10)
{
// Enforce the highest priority when bleeding out.
worstCondition = 0;
}
// Boost the priority when wounded.
worstCondition /= 2;
}
return worstCondition;
}
public static float GetVitalityFactor(Character character)
{
float vitality = character.HealthPercentage - character.Bleeding - character.Bloodloss + Math.Min(character.Oxygen, 0);
float vitality = character.HealthPercentage - (character.Bleeding * 2) - character.Bloodloss + Math.Min(character.Oxygen, 0);
vitality -= character.CharacterHealth.GetAfflictionStrength("paralysis");
return Math.Clamp(vitality, 0, 100);
}
@@ -92,6 +77,11 @@ namespace Barotrauma
if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target)) { return false; }
if (!humanAI.ObjectiveManager.IsCurrentOrder<AIObjectiveRescueAll>())
{
if (!character.IsMedic && target != character)
{
// Don't allow to treat others autonomously
return false;
}
// Ignore unsafe hulls, unless ordered
if (humanAI.UnsafeHulls.Contains(target.CurrentHull))
{
@@ -111,10 +101,10 @@ namespace Barotrauma
if (target.Submarine.Info.Type != character.Submarine.Info.Type) { return false; }
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, true)) { return false; }
}
if (!target.IsPlayer && HumanAIController.IsActive(target) && target.AIController is HumanAIController targetAI)
if (target != character &&!target.IsPlayer && HumanAIController.IsActive(target) && target.AIController is HumanAIController targetAI)
{
// Ignore all concious targets that are currently fighting, fleeing or treating characters
if (targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveCombat>() ||
if (targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveCombat>() ||
targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveFindSafety>() ||
targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveRescue>())
{