v0.13.0.11
This commit is contained in:
+232
-92
@@ -1,6 +1,6 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Networking; // used by the server
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -10,8 +10,8 @@ namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveManager
|
||||
{
|
||||
// TODO: expose
|
||||
public const float OrderPriority = 70;
|
||||
public const float HighestOrderPriority = 70;
|
||||
public const float LowestOrderPriority = 60;
|
||||
public const float RunPriority = 50;
|
||||
// Constantly increases the priority of the selected objective, unless overridden
|
||||
public const float baseDevotion = 5;
|
||||
@@ -25,7 +25,6 @@ namespace Barotrauma
|
||||
|
||||
public HumanAIController HumanAIController => character.AIController as HumanAIController;
|
||||
|
||||
|
||||
private float _waitTimer;
|
||||
/// <summary>
|
||||
/// When set above zero, the character will stand still doing nothing until the timer runs out. Does not affect orders, find safety or combat.
|
||||
@@ -39,26 +38,25 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public AIObjective CurrentOrder { get; private set; }
|
||||
public List<OrderInfo> CurrentOrders { get; } = new List<OrderInfo>();
|
||||
/// <summary>
|
||||
/// The AIObjective in <see cref="CurrentOrders"/> with the highest <see cref="AIObjective.Priority"/>
|
||||
/// </summary>
|
||||
public AIObjective CurrentOrder
|
||||
{
|
||||
get
|
||||
{
|
||||
return ForcedOrder ?? currentOrder;
|
||||
}
|
||||
private set
|
||||
{
|
||||
currentOrder = value;
|
||||
}
|
||||
}
|
||||
private AIObjective currentOrder;
|
||||
public AIObjective ForcedOrder { get; private set; }
|
||||
public AIObjective CurrentObjective { get; private set; }
|
||||
|
||||
public bool IsCurrentOrder<T>() where T : AIObjective => CurrentOrder is T;
|
||||
public bool IsCurrentObjective<T>() where T : AIObjective => CurrentObjective is T;
|
||||
public bool IsActiveObjective<T>() where T : AIObjective => GetActiveObjective() is T;
|
||||
|
||||
public AIObjective GetActiveObjective() => CurrentObjective?.GetActiveObjective();
|
||||
/// <summary>
|
||||
/// Returns the last active objective of the specific type.
|
||||
/// </summary>
|
||||
public T GetActiveObjective<T>() where T : AIObjective => CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).LastOrDefault(so => so is T) as T;
|
||||
|
||||
/// <summary>
|
||||
/// Returns all active objectives of the specific type. Creates a new collection -> don't use too frequently.
|
||||
/// </summary>
|
||||
public IEnumerable<T> GetActiveObjectives<T>() where T : AIObjective => CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).Where(so => so is T).Select(so => so as T);
|
||||
|
||||
public bool HasActiveObjective<T>() where T : AIObjective => CurrentObjective is T || CurrentObjective != null && CurrentObjective.GetSubObjectivesRecursive().Any(so => so is T);
|
||||
|
||||
public AIObjectiveManager(Character character)
|
||||
{
|
||||
this.character = character;
|
||||
@@ -134,7 +132,13 @@ namespace Barotrauma
|
||||
}
|
||||
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 != CharacterTeamType.FriendlyNPC) { continue; }
|
||||
if (autonomousObjective.ignoreAtOutpost && Level.IsLoadedOutpost && character.TeamID != CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
if (Submarine.MainSub != null && Submarine.MainSub.DockedTo.None(s => s.TeamID != CharacterTeamType.FriendlyNPC && s.TeamID != character.TeamID))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
var objective = CreateObjective(order, autonomousObjective.option, character, isAutonomous: true, autonomousObjective.priorityModifier);
|
||||
if (objective != null && objective.CanBeCompleted)
|
||||
{
|
||||
@@ -162,7 +166,11 @@ namespace Barotrauma
|
||||
coroutine = CoroutineManager.InvokeAfter(() =>
|
||||
{
|
||||
//round ended before the coroutine finished
|
||||
#if CLIENT
|
||||
if (GameMain.GameSession == null || Level.Loaded == null && !(GameMain.GameSession.GameMode is TestGameMode)) { return; }
|
||||
#else
|
||||
if (GameMain.GameSession == null || Level.Loaded == null) { return; }
|
||||
#endif
|
||||
DelayedObjectives.Remove(objective);
|
||||
AddObjective(objective);
|
||||
callback?.Invoke();
|
||||
@@ -200,21 +208,34 @@ namespace Barotrauma
|
||||
|
||||
public void UpdateObjectives(float deltaTime)
|
||||
{
|
||||
if (CurrentOrder != null)
|
||||
UpdateOrderObjective(ForcedOrder);
|
||||
|
||||
if (CurrentOrders.Any())
|
||||
{
|
||||
foreach(var order in CurrentOrders)
|
||||
{
|
||||
var orderObjective = order.Objective;
|
||||
UpdateOrderObjective(orderObjective);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateOrderObjective(AIObjective orderObjective)
|
||||
{
|
||||
if (orderObjective == null) { return; }
|
||||
#if DEBUG
|
||||
// Note: don't automatically remove orders here. Removing orders needs to be done via dismissing.
|
||||
if (CurrentOrder.IsCompleted)
|
||||
if (orderObjective.IsCompleted)
|
||||
{
|
||||
DebugConsole.NewMessage($"{character.Name}: ORDER {CurrentOrder.DebugTag} IS COMPLETED. CURRENTLY ALL ORDERS SHOULD BE LOOPING.", Color.Red);
|
||||
DebugConsole.NewMessage($"{character.Name}: ORDER {orderObjective.DebugTag} IS COMPLETED. CURRENTLY ALL ORDERS SHOULD BE LOOPING.", Color.Red);
|
||||
}
|
||||
else if (!CurrentOrder.CanBeCompleted)
|
||||
else if (!orderObjective.CanBeCompleted)
|
||||
{
|
||||
DebugConsole.NewMessage($"{character.Name}: ORDER {CurrentOrder.DebugTag}, CANNOT BE COMPLETED.", Color.Red);
|
||||
DebugConsole.NewMessage($"{character.Name}: ORDER {orderObjective.DebugTag}, CANNOT BE COMPLETED.", Color.Red);
|
||||
}
|
||||
#endif
|
||||
CurrentOrder.Update(deltaTime);
|
||||
orderObjective.Update(deltaTime);
|
||||
}
|
||||
|
||||
if (WaitTimer > 0)
|
||||
{
|
||||
WaitTimer -= deltaTime;
|
||||
@@ -248,7 +269,29 @@ namespace Barotrauma
|
||||
|
||||
public void SortObjectives()
|
||||
{
|
||||
CurrentOrder?.GetPriority();
|
||||
ForcedOrder?.GetPriority();
|
||||
|
||||
AIObjective orderWithHighestPriority = null;
|
||||
float highestPriority = 0;
|
||||
foreach (var currentOrder in CurrentOrders)
|
||||
{
|
||||
var orderObjective = currentOrder.Objective;
|
||||
if (orderObjective == null) { continue; }
|
||||
orderObjective.GetPriority();
|
||||
if (orderWithHighestPriority == null || orderObjective.Priority > highestPriority)
|
||||
{
|
||||
orderWithHighestPriority = orderObjective;
|
||||
highestPriority = orderObjective.Priority;
|
||||
}
|
||||
}
|
||||
#if SERVER
|
||||
if (orderWithHighestPriority != null && orderWithHighestPriority != currentOrder)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(character, new object[] { NetEntityEvent.Type.ObjectiveManagerOrderState });
|
||||
}
|
||||
#endif
|
||||
CurrentOrder = orderWithHighestPriority;
|
||||
|
||||
for (int i = Objectives.Count - 1; i >= 0; i--)
|
||||
{
|
||||
Objectives[i].GetPriority();
|
||||
@@ -257,6 +300,7 @@ namespace Barotrauma
|
||||
{
|
||||
Objectives.Sort((x, y) => y.Priority.CompareTo(x.Priority));
|
||||
}
|
||||
|
||||
GetCurrentObjective()?.SortSubObjectives();
|
||||
}
|
||||
|
||||
@@ -272,13 +316,19 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void SetOrder(AIObjective objective)
|
||||
public void SetForcedOrder(AIObjective objective)
|
||||
{
|
||||
CurrentOrder = objective;
|
||||
ForcedOrder = objective;
|
||||
}
|
||||
|
||||
public void ClearForcedOrder()
|
||||
{
|
||||
ForcedOrder = null;
|
||||
SortObjectives();
|
||||
}
|
||||
|
||||
private CoroutineHandle speakRoutine;
|
||||
public void SetOrder(Order order, string option, Character orderGiver, bool speak)
|
||||
public void SetOrder(Order order, string option, int priority, Character orderGiver, bool speak)
|
||||
{
|
||||
if (character.IsDead)
|
||||
{
|
||||
@@ -289,8 +339,53 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
ClearIgnored();
|
||||
CurrentOrder = CreateObjective(order, option, orderGiver, isAutonomous: false);
|
||||
if (CurrentOrder == null)
|
||||
|
||||
if (order == null || order.Identifier == "dismissed")
|
||||
{
|
||||
if (!string.IsNullOrEmpty(option))
|
||||
{
|
||||
if (CurrentOrders.Any(o => o.MatchesDismissedOrder(option)))
|
||||
{
|
||||
var dismissedOrderInfo = CurrentOrders.First(o => o.MatchesDismissedOrder(option));
|
||||
CurrentOrders.Remove(dismissedOrderInfo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentOrders.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the order priorities reflect those set by the player
|
||||
for (int i = CurrentOrders.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var currentOrder = CurrentOrders[i];
|
||||
if (currentOrder.Objective == null || currentOrder.MatchesOrder(order, option))
|
||||
{
|
||||
CurrentOrders.RemoveAt(i);
|
||||
continue;
|
||||
}
|
||||
var currentOrderInfo = character.GetCurrentOrder(currentOrder.Order, currentOrder.OrderOption);
|
||||
if (currentOrderInfo.HasValue)
|
||||
{
|
||||
int currentPriority = currentOrderInfo.Value.ManualPriority;
|
||||
if (currentOrder.ManualPriority != currentPriority)
|
||||
{
|
||||
CurrentOrders[i] = new OrderInfo(currentOrder, currentPriority);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentOrders.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
var newCurrentOrder = CreateObjective(order, option, orderGiver, isAutonomous: false);
|
||||
if (newCurrentOrder != null)
|
||||
{
|
||||
CurrentOrders.Add(new OrderInfo(order, option, priority, newCurrentOrder));
|
||||
}
|
||||
if (!HasOrders())
|
||||
{
|
||||
// Recreate objectives, because some of them may be removed, if impossible to complete (e.g. due to path finding)
|
||||
CreateAutonomousObjectives();
|
||||
@@ -298,56 +393,57 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
// This should be redundant, because all the objectives are reset when they are selected as active.
|
||||
CurrentOrder.Reset();
|
||||
if (speak)
|
||||
newCurrentOrder?.Reset();
|
||||
|
||||
if (speak && character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogAffirmative"), null, 1.0f);
|
||||
if (speakRoutine != null)
|
||||
{
|
||||
CoroutineManager.StopCoroutines(speakRoutine);
|
||||
}
|
||||
speakRoutine = CoroutineManager.InvokeAfter(() =>
|
||||
{
|
||||
if (GameMain.GameSession == null || Level.Loaded == null) { return; }
|
||||
if (CurrentOrder != null && character.SpeechImpediment < 100.0f)
|
||||
{
|
||||
if (CurrentOrder is AIObjectiveRepairItems repairItems && repairItems.Targets.None())
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNoRepairTargets"), null, 3.0f, "norepairtargets");
|
||||
}
|
||||
else if (CurrentOrder is AIObjectiveChargeBatteries chargeBatteries && chargeBatteries.Targets.None())
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNoBatteries"), null, 3.0f, "nobatteries");
|
||||
}
|
||||
else if (CurrentOrder is AIObjectiveExtinguishFires extinguishFires && extinguishFires.Targets.None())
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNoFire"), null, 3.0f, "nofire");
|
||||
}
|
||||
else if (CurrentOrder is AIObjectiveFixLeaks fixLeaks && fixLeaks.Targets.None())
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNoLeaks"), null, 3.0f, "noleaks");
|
||||
}
|
||||
else if (CurrentOrder is AIObjectiveFightIntruders fightIntruders && fightIntruders.Targets.None())
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNoEnemies"), null, 3.0f, "noenemies");
|
||||
}
|
||||
else if (CurrentOrder is AIObjectiveRescueAll rescueAll && rescueAll.Targets.None())
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNoRescueTargets"), null, 3.0f, "norescuetargets");
|
||||
}
|
||||
else if (CurrentOrder is AIObjectivePumpWater pumpWater && pumpWater.Targets.None())
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNoPumps"), null, 3.0f, "nopumps");
|
||||
}
|
||||
}
|
||||
}, 3);
|
||||
//if (speakRoutine != null)
|
||||
//{
|
||||
// CoroutineManager.StopCoroutines(speakRoutine);
|
||||
//}
|
||||
//speakRoutine = CoroutineManager.InvokeAfter(() =>
|
||||
//{
|
||||
// if (GameMain.GameSession == null || Level.Loaded == null) { return; }
|
||||
// if (newCurrentOrder != null && character.SpeechImpediment < 100.0f)
|
||||
// {
|
||||
// if (newCurrentOrder is AIObjectiveRepairItems repairItems && repairItems.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoRepairTargets"), null, 3.0f, "norepairtargets");
|
||||
// }
|
||||
// else if (newCurrentOrder is AIObjectiveChargeBatteries chargeBatteries && chargeBatteries.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoBatteries"), null, 3.0f, "nobatteries");
|
||||
// }
|
||||
// else if (newCurrentOrder is AIObjectiveExtinguishFires extinguishFires && extinguishFires.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoFire"), null, 3.0f, "nofire");
|
||||
// }
|
||||
// else if (newCurrentOrder is AIObjectiveFixLeaks fixLeaks && fixLeaks.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoLeaks"), null, 3.0f, "noleaks");
|
||||
// }
|
||||
// else if (newCurrentOrder is AIObjectiveFightIntruders fightIntruders && fightIntruders.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoEnemies"), null, 3.0f, "noenemies");
|
||||
// }
|
||||
// else if (newCurrentOrder is AIObjectiveRescueAll rescueAll && rescueAll.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoRescueTargets"), null, 3.0f, "norescuetargets");
|
||||
// }
|
||||
// else if (newCurrentOrder is AIObjectivePumpWater pumpWater && pumpWater.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoPumps"), null, 3.0f, "nopumps");
|
||||
// }
|
||||
// }
|
||||
//}, 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AIObjective CreateObjective(Order order, string option, Character orderGiver, bool isAutonomous, float priorityModifier = 1)
|
||||
{
|
||||
if (order == null) { return null; }
|
||||
if (order == null || order.Identifier == "dismissed") { return null; }
|
||||
AIObjective newObjective;
|
||||
switch (order.Identifier.ToLowerInvariant())
|
||||
{
|
||||
@@ -360,7 +456,7 @@ namespace Barotrauma
|
||||
extraDistanceWhileSwimming = 100,
|
||||
AllowGoingOutside = true,
|
||||
IgnoreIfTargetDead = true,
|
||||
followControlledCharacter = orderGiver == character,
|
||||
followControlledCharacter = true,
|
||||
mimic = true,
|
||||
DialogueIdentifier = "dialogcannotreachplace"
|
||||
};
|
||||
@@ -430,7 +526,7 @@ namespace Barotrauma
|
||||
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, false, priorityModifier: priorityModifier)
|
||||
{
|
||||
IsLoop = false,
|
||||
Override = character.CurrentOrder != null,
|
||||
Override = !character.IsDismissed,
|
||||
completionCondition = () =>
|
||||
{
|
||||
if (float.TryParse(option, out float pct))
|
||||
@@ -483,21 +579,9 @@ namespace Barotrauma
|
||||
return newObjective;
|
||||
}
|
||||
|
||||
private void DismissSelf()
|
||||
{
|
||||
#if CLIENT
|
||||
if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.IsSinglePlayer)
|
||||
{
|
||||
GameMain.GameSession?.CrewManager?.SetCharacterOrder(character, Order.GetPrefab("dismissed"), null, character);
|
||||
}
|
||||
#else
|
||||
GameMain.Server?.SendOrderChatMessage(new OrderChatMessage(Order.GetPrefab("dismissed"), null, null, character, character));
|
||||
#endif
|
||||
}
|
||||
|
||||
private bool IsAllowedToWait()
|
||||
{
|
||||
if (CurrentOrder != null) { return false; }
|
||||
if (HasOrders()) { return false; }
|
||||
if (CurrentObjective is AIObjectiveCombat || CurrentObjective is AIObjectiveFindSafety) { return false; }
|
||||
if (character.AnimController.InWater) { return false; }
|
||||
if (character.IsClimbing) { return false; }
|
||||
@@ -508,5 +592,61 @@ namespace Barotrauma
|
||||
if (AIObjectiveIdle.IsForbidden(character.CurrentHull)) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsCurrentOrder<T>() where T : AIObjective => CurrentOrder is T;
|
||||
public bool IsCurrentObjective<T>() where T : AIObjective => CurrentObjective is T;
|
||||
public bool IsActiveObjective<T>() where T : AIObjective => GetActiveObjective() is T;
|
||||
|
||||
public AIObjective GetActiveObjective() => CurrentObjective?.GetActiveObjective();
|
||||
/// <summary>
|
||||
/// Returns the last active objective of the specific type.
|
||||
/// </summary>
|
||||
public T GetActiveObjective<T>() where T : AIObjective => CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).LastOrDefault(so => so is T) as T;
|
||||
|
||||
/// <summary>
|
||||
/// Returns all active objectives of the specific type. Creates a new collection -> don't use too frequently.
|
||||
/// </summary>
|
||||
public IEnumerable<T> GetActiveObjectives<T>() where T : AIObjective => CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).Where(so => so is T).Select(so => so as T);
|
||||
|
||||
public bool HasActiveObjective<T>() where T : AIObjective => CurrentObjective is T || CurrentObjective != null && CurrentObjective.GetSubObjectivesRecursive().Any(so => so is T);
|
||||
|
||||
public bool IsOrder(AIObjective objective)
|
||||
{
|
||||
return objective == ForcedOrder || CurrentOrders.Any(o => o.Objective == objective);
|
||||
}
|
||||
|
||||
public bool HasOrders()
|
||||
{
|
||||
return ForcedOrder != null || CurrentOrders.Any();
|
||||
}
|
||||
|
||||
public bool HasOrder<T>() where T : AIObjective
|
||||
{
|
||||
return ForcedOrder is T || CurrentOrders.Any(o => o.Objective is T);
|
||||
}
|
||||
|
||||
public float GetOrderPriority(AIObjective objective)
|
||||
{
|
||||
if (objective == ForcedOrder) { return HighestOrderPriority; }
|
||||
var currentOrder = CurrentOrders.FirstOrDefault(o => o.Objective == objective);
|
||||
if (currentOrder.Objective == null)
|
||||
{
|
||||
return HighestOrderPriority;
|
||||
}
|
||||
else if (currentOrder.ManualPriority > 0)
|
||||
{
|
||||
return MathHelper.Lerp(LowestOrderPriority, HighestOrderPriority, MathUtils.InverseLerp(1, CharacterInfo.HighestManualOrderPriority, currentOrder.ManualPriority));
|
||||
}
|
||||
#if DEBUG
|
||||
DebugConsole.AddWarning("Error in order priority: shouldn't return 0!");
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
public OrderInfo? GetCurrentOrderInfo()
|
||||
{
|
||||
if (currentOrder == null) { return null; }
|
||||
return CurrentOrders.FirstOrDefault(o => o.Objective == CurrentOrder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user