v0.13.0.11

This commit is contained in:
Joonas Rikkonen
2021-04-22 17:33:08 +03:00
parent 0697d7fc64
commit 8bb31f2893
391 changed files with 17271 additions and 5949 deletions
@@ -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);
}
}
}