Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs
2026-04-30 21:59:54 +08:00

896 lines
40 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
namespace Barotrauma
{
public enum OrderCategory
{
Emergency,
Movement,
Power,
Maintenance,
Operate
}
class OrderCategoryIcon : Prefab
{
public readonly static PrefabCollection<OrderCategoryIcon> OrderCategoryIcons = new PrefabCollection<OrderCategoryIcon>();
public OrderCategoryIcon(ContentXElement element, OrdersFile file) : base(file, element.GetAttributeIdentifier("category", ""))
{
Category = Enum.Parse<OrderCategory>(Identifier.Value, true);
var spriteElement = element.GetChildElement("sprite");
Sprite = new Sprite(spriteElement, lazyLoad: true);
Color = element.GetAttributeColor("color", Color.White);
}
public readonly OrderCategory Category;
public readonly Sprite Sprite;
public readonly Color Color;
public override void Dispose() { Sprite?.Remove(); }
}
class OrderPrefab : PrefabWithUintIdentifier
{
public readonly static PrefabCollection<OrderPrefab> Prefabs = new PrefabCollection<OrderPrefab>();
public readonly static Identifier DismissalIdentifier = "dismissed".ToIdentifier();
public static OrderPrefab Dismissal => Prefabs[DismissalIdentifier];
public readonly OrderCategory? Category;
public readonly Identifier CategoryIdentifier;
public readonly LocalizedString Name;
/// <summary>
/// Name that can be used with the contextual version of the order
/// </summary>
public readonly LocalizedString ContextualName;
public readonly Sprite SymbolSprite;
public readonly Type ItemComponentType;
public readonly bool CanTypeBeSubclass;
public readonly ImmutableArray<Identifier> TargetItems;
public readonly ImmutableArray<Identifier> RequireItems;
private readonly ImmutableDictionary<Identifier, ImmutableArray<Identifier>> OptionTargetItems;
public bool HasOptionSpecificTargetItems => OptionTargetItems != null && OptionTargetItems.Any();
private readonly Color? color;
public Color Color
{
get
{
if (color.HasValue)
{
return color.Value;
}
else if (OrderCategoryIcon.OrderCategoryIcons.ContainsKey(CategoryIdentifier))
{
return OrderCategoryIcon.OrderCategoryIcons[Category.ToIdentifier()].Color;
}
else
{
return Color.White;
}
}
}
//if true, the order is issued to all available characters
public readonly bool TargetAllCharacters;
public bool IsReport => TargetAllCharacters && !MustSetTarget;
public bool IsVisibleAsReportButton =>
IsReport && !Hidden && SymbolSprite != null &&
(!TraitorModeOnly || GameMain.GameSession is { TraitorsEnabled: true });
public bool TraitorModeOnly;
public bool IsDismissal => Identifier == DismissalIdentifier;
public readonly float FadeOutTime;
public readonly bool UseController;
public readonly ImmutableArray<Identifier> ControllerTags;
/// <summary>
/// If defined, the order can only be quick-assigned to characters with these jobs. Or if it's a report, the icon will only be displayed to characters with these jobs.
/// </summary>
public readonly ImmutableArray<Identifier> AppropriateJobs;
public readonly ImmutableArray<Identifier> Options;
public readonly ImmutableArray<Identifier> HiddenOptions;
public readonly ImmutableArray<Identifier> AllOptions;
public readonly ListDictionary<Identifier, LocalizedString> OptionNames;
public readonly ImmutableDictionary<Identifier, Sprite> OptionSprites;
public readonly bool MustSetTarget;
/// <summary>
/// Can the order be turned into a non-entity-targeting one if it was originally created with a target entity.
/// Note: if MustSetTarget is true, CanBeGeneralized will always be false.
/// </summary>
public readonly bool CanBeGeneralized;
public readonly Identifier AppropriateSkill;
public readonly bool Hidden;
public readonly bool IgnoreAtOutpost;
public bool HasOptions => Options.Length > 1;
public readonly bool MustManuallyAssign;
/// <summary>
/// If enabled and this is an Operate order, it will remove Operate orders of the same item from other characters.
/// If this is a Movement order, removes other Movement orders from the character who receives the order.
/// </summary>
public readonly bool AutoDismiss;
/// <summary>
/// If defined, the order will be quick-assigned to characters with these jobs before characters with other jobs.
/// </summary>
public readonly ImmutableArray<Identifier> PreferredJobs;
public enum OrderTargetType
{
Entity,
Position,
WallSection
}
public OrderTargetType TargetType { get; }
public int? WallSectionIndex { get; }
public bool IsIgnoreOrder => Identifier == Tags.IgnoreThis || Identifier == Tags.UnignoreThis;
public bool IsDeconstructOrder => Identifier == Tags.DeconstructThis || Identifier == Tags.DontDeconstructThis;
/// <summary>
/// Should the order icon be drawn when the order target is inside a container
/// </summary>
public bool DrawIconWhenContained { get; }
/// <summary>
/// Affects how high on the order list the order will be placed (i.e. the manual priority order when it's given) when it's first given.
/// Manually rearranging orders will override this priority.
/// </summary>
public int AssignmentPriority { get; }
public bool ColoredWhenControllingGiver { get; }
public bool DisplayGiverInTooltip { get; }
public OrderPrefab(ContentXElement orderElement, OrdersFile file) : base(file, orderElement.GetAttributeIdentifier("identifier", ""))
{
Name = TextManager.Get($"OrderName.{Identifier}");
ContextualName = TextManager.Get($"OrderNameContextual.{Identifier}").Fallback(Name);
string targetItemType = orderElement.GetAttributeString("targetitemtype", "");
if (!string.IsNullOrWhiteSpace(targetItemType))
{
try
{
ItemComponentType = Type.GetType("Barotrauma.Items.Components." + targetItemType, true, true);
}
catch (Exception e)
{
DebugConsole.ThrowError("Error in the order definitions: item component type " + targetItemType + " not found", e);
}
}
CanTypeBeSubclass = orderElement.GetAttributeBool("cantypebesubclass", false);
color = orderElement.GetAttributeColor("color");
FadeOutTime = orderElement.GetAttributeFloat("fadeouttime", 0.0f);
UseController = orderElement.GetAttributeBool("usecontroller", false);
ControllerTags = orderElement.GetAttributeIdentifierArray("controllertags", Array.Empty<Identifier>()).ToImmutableArray();
TargetAllCharacters = orderElement.GetAttributeBool("targetallcharacters", false);
AppropriateJobs = orderElement.GetAttributeIdentifierArray("appropriatejobs", Array.Empty<Identifier>()).ToImmutableArray();
TraitorModeOnly = orderElement.GetAttributeBool("TraitorModeOnly", false);
PreferredJobs = orderElement.GetAttributeIdentifierArray("preferredjobs", Array.Empty<Identifier>()).ToImmutableArray();
Options = orderElement.GetAttributeIdentifierArray("options", Array.Empty<Identifier>()).ToImmutableArray();
HiddenOptions = orderElement.GetAttributeIdentifierArray("hiddenoptions", Array.Empty<Identifier>()).ToImmutableArray();
AllOptions = Options.Concat(HiddenOptions).ToImmutableArray();
var optionTargetItems = new Dictionary<Identifier, ImmutableArray<Identifier>>();
if (orderElement.GetAttributeString("targetitems", "") is string targetItems && targetItems.Contains(';'))
{
string[] splitTargetItems = targetItems.Split(';');
#if DEBUG
if (splitTargetItems.Length != AllOptions.Length)
{
DebugConsole.ThrowError($"Order \"{Identifier}\" has option-specific target items, but the option count doesn't match the target item count");
}
#endif
var allTargetItems = new List<Identifier>();
for (int i = 0; i < AllOptions.Length; i++)
{
Identifier[] optionTargetItemsSplit = i < splitTargetItems.Length ? splitTargetItems[i].ToIdentifiers().ToArray() : Array.Empty<Identifier>();
allTargetItems.AddRange(optionTargetItemsSplit);
optionTargetItems.Add(AllOptions[i], optionTargetItemsSplit.ToImmutableArray());
}
TargetItems = allTargetItems.ToImmutableArray();
}
else
{
TargetItems = orderElement.GetAttributeIdentifierArray("targetitems", Array.Empty<Identifier>(), trim: true).ToImmutableArray();
}
RequireItems = orderElement.GetAttributeIdentifierArray("requireitems", Array.Empty<Identifier>(), trim: true).ToImmutableArray();
OptionTargetItems = optionTargetItems.ToImmutableDictionary();
var category = orderElement.GetAttributeString("category", null);
this.Category = !string.IsNullOrWhiteSpace(category) ? Enum.Parse<OrderCategory>(category, true) : (OrderCategory?)null;
this.CategoryIdentifier = (this.Category?.ToString() ?? string.Empty).ToIdentifier();
MustSetTarget = orderElement.GetAttributeBool("mustsettarget", false);
CanBeGeneralized = !MustSetTarget && orderElement.GetAttributeBool("canbegeneralized", true);
AppropriateSkill = orderElement.GetAttributeIdentifier("appropriateskill", Identifier.Empty);
Hidden = orderElement.GetAttributeBool("hidden", false);
IgnoreAtOutpost = orderElement.GetAttributeBool("ignoreatoutpost", false);
OptionNames =
new ListDictionary<Identifier, LocalizedString>(
TextManager.Get("OrderOptions." + Identifier).Split(',', ''), Options.Length, i => Options[i]);
var spriteElement = orderElement.GetChildElement("sprite");
if (spriteElement != null)
{
SymbolSprite = new Sprite(spriteElement, lazyLoad: true);
}
var optionSprites = new Dictionary<Identifier, Sprite>();
if (Options != null && Options.Length > 0)
{
var optionSpriteElements = orderElement.GetChildElement("optionsprites")?.GetChildElements("sprite");
if (optionSpriteElements != null && optionSpriteElements.Any())
{
for (int i = 0; i < Options.Length; i++)
{
if (i >= optionSpriteElements.Count()) { break; };
var sprite = new Sprite(optionSpriteElements.ElementAt(i), lazyLoad: true);
optionSprites.Add(Options[i], sprite);
}
}
}
OptionSprites = optionSprites.ToImmutableDictionary();
MustManuallyAssign = orderElement.GetAttributeBool("mustmanuallyassign", false);
DrawIconWhenContained = orderElement.GetAttributeBool("displayiconwhencontained", false);
AutoDismiss = orderElement.GetAttributeBool("autodismiss", Category == OrderCategory.Operate || Category == OrderCategory.Movement);
AssignmentPriority = Math.Clamp(orderElement.GetAttributeInt("assignmentpriority", 100), 0, 100);
ColoredWhenControllingGiver = orderElement.GetAttributeBool("coloredwhencontrollinggiver", false);
DisplayGiverInTooltip = orderElement.GetAttributeBool("displaygiverintooltip", false);
}
private bool HasSpecifiedJob(Character character, IReadOnlyList<Identifier> jobs)
{
if (jobs == null || jobs.Count == 0) { return false; }
Identifier jobIdentifier = character?.Info?.Job?.Prefab?.Identifier ?? Identifier.Empty;
if (jobIdentifier.IsEmpty) { return false; }
for (int i = 0; i < jobs.Count; i++)
{
if (jobIdentifier == jobs[i]) { return true; }
}
return false;
}
public bool HasAppropriateJob(Character character) => HasSpecifiedJob(character, AppropriateJobs);
public bool HasPreferredJob(Character character) => HasSpecifiedJob(character, PreferredJobs);
public string GetChatMessage(string targetCharacterName, string targetRoomName, Entity targetEntity, bool givingOrderToSelf, Identifier orderOption = default, bool isNewOrder = true)
{
if (!TargetAllCharacters && !isNewOrder && Identifier != "dismissed")
{
// Use special dialogue when we're rearranging character orders
if (!givingOrderToSelf)
{
return TextManager.GetWithVariable("rearrangedorders", "[name]", targetCharacterName ?? string.Empty).Value;
}
else
{
// Say nothing when rearranging the orders of the character you're controlling
return string.Empty;
}
}
string messageTag = $"{(givingOrderToSelf && !TargetAllCharacters ? "OrderDialogSelf" : "OrderDialog")}.{Identifier}";
if (!orderOption.IsEmpty)
{
if (Identifier != "dismissed")
{
messageTag += $".{orderOption}";
}
else
{
string[] splitOption = orderOption.Value.Split('.');
if (splitOption.Length > 0)
{
messageTag += $".{splitOption[0]}";
}
}
}
LocalizedString targetEntityName = string.Empty;
switch (targetEntity)
{
case Item item:
targetEntityName = item.Name;
break;
case Hull hull:
targetEntityName = hull.DisplayName;
break;
case Structure structure:
targetEntityName = structure.Name;
break;
case Character character:
targetEntityName = character.DisplayName;
break;
}
return TextManager.GetWithVariables(messageTag,
("[name]", targetCharacterName ?? string.Empty, FormatCapitals.No),
("[target]", targetEntityName, FormatCapitals.No),
("[roomname]", targetRoomName ?? string.Empty, FormatCapitals.Yes)).Fallback("").Value;
}
/// <summary>
/// Get the target item component based on the target item type
/// </summary>
public ItemComponent GetTargetItemComponent(Item item)
{
if (item?.Components == null || ItemComponentType == null) { return null; }
foreach (ItemComponent component in item.Components)
{
if (component?.GetType() is Type componentType)
{
// Items used via a controller (i.e. turrets) are not selectable but should still be valid targets.
if (!UseController && !component.CanBeSelected) { continue; }
if (componentType == ItemComponentType) { return component; }
if (CanTypeBeSubclass && componentType.IsSubclassOf(ItemComponentType)) { return component; }
}
}
return null;
}
public bool TryGetTargetItemComponent(Item item, out ItemComponent firstMatchingComponent)
{
firstMatchingComponent = GetTargetItemComponent(item);
return firstMatchingComponent != null;
}
/// <param name="interactableFor">Only returns items which are interactable for this character</param>
public List<Item> GetMatchingItems(Submarine submarine, bool mustBelongToPlayerSub, CharacterTeamType? requiredTeam = null, Character interactableFor = null, Identifier orderOption = default)
{
List<Item> matchingItems = new List<Item>();
if (submarine == null) { return matchingItems; }
if (ItemComponentType != null || TargetItems.Any() || RequireItems.Any())
{
foreach (var item in Item.ItemList)
{
if (RequireItems.Any() && !TargetItemsMatchItem(RequireItems, item)) { continue; }
if (TargetItems.Any() && !TargetItemsMatchItem(item, orderOption)) { continue; }
if (RequireItems.None() && TargetItems.None() && !TryGetTargetItemComponent(item, out _)) { continue; }
if (mustBelongToPlayerSub && item.Submarine?.Info != null && item.Submarine.Info.Type != SubmarineType.Player) { continue; }
if (item.Submarine != submarine && !submarine.DockedTo.Contains(item.Submarine)) { continue; }
if (requiredTeam.HasValue && (item.Submarine == null || item.Submarine.TeamID != requiredTeam.Value)) { continue; }
if (item.NonInteractable) { continue; }
if (ItemComponentType != null && item.Components.None(c => c.GetType() == ItemComponentType)) { continue; }
Controller controller = null;
if (UseController && !item.TryFindController(out controller, tags: ControllerTags)) { continue; }
if (interactableFor != null && (!item.IsInteractable(interactableFor) || (UseController && !controller.Item.IsInteractable(interactableFor)))) { continue; }
matchingItems.Add(item);
}
}
return matchingItems;
}
/// <param name="interactableFor">Only returns items which are interactable for this character</param>
public List<Item> GetMatchingItems(bool mustBelongToPlayerSub, Character interactableFor = null, Identifier orderOption = default)
{
Submarine submarine = Character.Controlled != null && Character.Controlled.TeamID == CharacterTeamType.Team2 && Submarine.MainSubs.Length > 1 ?
Submarine.MainSubs[1] :
Submarine.MainSub;
return GetMatchingItems(submarine, mustBelongToPlayerSub, interactableFor: interactableFor, orderOption: orderOption);
}
public LocalizedString GetOptionName(string id)
{
return GetOptionName(id.ToIdentifier());
}
public LocalizedString GetOptionName(Identifier id)
{
if (OptionNames.ContainsKey(id)) { return OptionNames[id]; }
return string.Empty;
}
public LocalizedString GetOptionName(int index)
{
if (index < 0 || index >= Options.Length) { return null; }
return OptionNames[Options[index]];
}
/// <summary>
/// Used to create the order option for the Dismiss order to know which order it targets
/// </summary>
/// <param name="order">The order to target with the dismiss order</param>
public static Identifier GetDismissOrderOption(Order order)
{
Identifier option = order.Identifier;
if (order.Option != Identifier.Empty)
{
option = $"{option}.{order.Option}".ToIdentifier();
}
return option;
}
public ImmutableArray<Identifier> GetTargetItems(Identifier option = default)
{
if (option.IsEmpty || !OptionTargetItems.TryGetValue(option, out ImmutableArray<Identifier> optionTargetItems))
{
return TargetItems;
}
else
{
return optionTargetItems;
}
}
public bool TargetItemsMatchItem(Item item, Identifier option = default)
{
if (item == null) { return false; }
if (Identifier == Tags.DeconstructThis && item.AllowDeconstruct)
{
if (item.AllowDeconstruct && !Item.IsMarkedForDeconstruction(item) &&
//only allow deconstructing if there are no deconstruction recipes (= deconstructing yields nothing), or deconstruction recipes that
(item.Prefab.DeconstructItems.None() ||
item.Prefab.DeconstructItems.Any(deconstructItem =>
//1. don't require any additional items (bots can't handle that)
deconstructItem.RequiredOtherItem.None() &&
//2. don't require a research station (bots don't know how to use those)
(deconstructItem.RequiredDeconstructor.Length == 0 || deconstructItem.RequiredDeconstructor.Any(d => d != Tags.GeneticResearchStation)))))
{
return true;
}
}
else if (Identifier == Tags.DontDeconstructThis)
{
if (Item.IsMarkedForDeconstruction(item)) { return true; }
}
ImmutableArray<Identifier> targetItems = GetTargetItems(option);
return TargetItemsMatchItem(targetItems, item);
}
public static bool TargetItemsMatchItem(ImmutableArray<Identifier> targetItems, Item item)
{
return item != null && targetItems != null && targetItems.Length > 0 && (targetItems.Contains(item.Prefab.Identifier) || item.HasTag(targetItems));
}
public override void Dispose() { }
/// <summary>
/// Create an Order instance with a null target
/// </summary>
public Order CreateInstance(OrderTargetType targetType, Character orderGiver = null, bool isAutonomous = false)
{
try
{
return targetType switch
{
OrderTargetType.Entity => new Order(this, targetEntity: null, targetItem: null, orderGiver, isAutonomous),
OrderTargetType.Position => new Order(this, target: null, orderGiver),
OrderTargetType.WallSection => new Order(this, wall: null, sectionIndex: null, orderGiver),
_ => throw new NotImplementedException()
};
}
catch (NotImplementedException e)
{
DebugConsole.LogError($"Error creating a new Order instance: unexpected target type \"{targetType}\".\n{e.StackTrace.CleanupStackTrace()}",
contentPackage: ContentPackage);
return null;
}
}
}
class Order
{
public readonly OrderPrefab Prefab;
public readonly Identifier Option;
public readonly int ManualPriority;
public readonly OrderType Type;
public readonly AIObjective Objective;
public bool IsCurrentOrder => Type == OrderType.Current;
public bool IsDismissal => Prefab.IsDismissal;
public enum OrderType
{
Current,
Previous
}
public readonly Entity TargetEntity;
public readonly ItemComponent TargetItemComponent;
public readonly Controller ConnectedController;
public readonly Character OrderGiver;
public readonly OrderTarget TargetPosition;
private ISpatialEntity targetSpatialEntity;
/// <summary>
/// Note this property doesn't return the follow target of the Follow objective, as expected!
/// </summary>
public ISpatialEntity TargetSpatialEntity
{
get
{
if (targetSpatialEntity == null)
{
if (TargetType == OrderTargetType.WallSection && WallSectionIndex.HasValue)
{
targetSpatialEntity = (TargetEntity as Structure)?.Sections[WallSectionIndex.Value];
}
else
{
targetSpatialEntity = TargetEntity ?? TargetPosition as ISpatialEntity;
}
}
return targetSpatialEntity;
}
}
public Hull TargetHull => TargetEntity as Hull;
public enum OrderTargetType
{
Entity,
Position,
WallSection
}
public readonly OrderTargetType TargetType;
public readonly int? WallSectionIndex;
public LocalizedString Name => Prefab.Name;
public LocalizedString ContextualName => Prefab.ContextualName;
public Identifier Identifier => Prefab.Identifier;
public Type ItemComponentType => Prefab.ItemComponentType;
public bool CanTypeBeSubclass => Prefab.CanTypeBeSubclass;
public ref readonly ImmutableArray<Identifier> ControllerTags => ref Prefab.ControllerTags;
public ref readonly ImmutableArray<Identifier> TargetItems => ref Prefab.TargetItems;
public ref readonly ImmutableArray<Identifier> RequireItems => ref Prefab.RequireItems;
public ref readonly ImmutableArray<Identifier> Options => ref Prefab.Options;
public ref readonly ImmutableArray<Identifier> HiddenOptions => ref Prefab.HiddenOptions;
public ref readonly ImmutableArray<Identifier> AllOptions => ref Prefab.AllOptions;
public Sprite SymbolSprite => Prefab.SymbolSprite;
public Color Color => Prefab.Color;
public bool TargetAllCharacters => Prefab.TargetAllCharacters;
public ref readonly ImmutableArray<Identifier> AppropriateJobs => ref Prefab.AppropriateJobs;
public float FadeOutTime => Prefab.FadeOutTime;
public bool MustSetTarget => Prefab.MustSetTarget;
public Identifier AppropriateSkill => Prefab.AppropriateSkill;
public OrderCategory? Category => Prefab.Category;
public bool MustManuallyAssign => Prefab.MustManuallyAssign;
public bool IsIgnoreOrder => Prefab.IsIgnoreOrder;
public bool IsDeconstructOrder => Prefab.IsDeconstructOrder;
public bool DrawIconWhenContained => Prefab.DrawIconWhenContained;
public bool Hidden => Prefab.Hidden;
public bool IgnoreAtOutpost => Prefab.IgnoreAtOutpost;
public bool IsReport => Prefab.IsReport;
public bool AutoDismiss => Prefab.AutoDismiss;
public int AssignmentPriority => Prefab.AssignmentPriority;
public bool ColoredWhenControllingGiver => Prefab.ColoredWhenControllingGiver;
public bool DisplayGiverInTooltip => Prefab.DisplayGiverInTooltip;
public readonly bool UseController;
/// <summary>
/// Constructor for orders with the target type OrderTargetType.Entity
/// </summary>
public Order(OrderPrefab prefab, Entity targetEntity, ItemComponent targetItem, Character orderGiver = null, bool isAutonomous = false)
: this(prefab, Identifier.Empty, 0, OrderType.Current, null, targetEntity, targetItem, orderGiver, isAutonomous) { }
/// <summary>
/// Constructor for orders with the target type OrderTargetType.Entity
/// </summary>
public Order(OrderPrefab prefab, Identifier option, Entity targetEntity, ItemComponent targetItem, Character orderGiver = null, bool isAutonomous = false)
: this(prefab, option, 0, OrderType.Current, null, targetEntity, targetItem, orderGiver, isAutonomous) { }
/// <summary>
/// Constructor for orders with the target type OrderTargetType.Position
/// </summary>
public Order(OrderPrefab prefab, OrderTarget target, Character orderGiver = null)
: this(prefab, prefab.Options.FirstOrDefault(), 0, OrderType.Current, null, target, orderGiver) { }
/// <summary>
/// Constructor for orders with the target type OrderTargetType.Position
/// </summary>
public Order(OrderPrefab prefab, Identifier option, OrderTarget target, Character orderGiver = null)
: this(prefab, option, 0, OrderType.Current, null, target, orderGiver) { }
/// <summary>
/// Constructor for orders with the target type OrderTargetType.WallSection
/// </summary>
public Order(OrderPrefab prefab, Structure wall, int? sectionIndex, Character orderGiver = null)
: this(prefab, Identifier.Empty, 0, OrderType.Current, null, wall, sectionIndex, orderGiver) { }
/// <summary>
/// Constructor for orders with the target type OrderTargetType.WallSection
/// </summary>
public Order(OrderPrefab prefab, Identifier option, Structure wall, int? sectionIndex, Character orderGiver = null)
: this(prefab, option, 0, OrderType.Current, null, wall, sectionIndex, orderGiver) { }
/// <summary>
/// Constructor for orders with the target type OrderTargetType.Entity
/// </summary>
private Order(OrderPrefab prefab, Identifier option, int manualPriority, OrderType orderType, AIObjective aiObjective, Entity targetEntity, ItemComponent targetItem, Character orderGiver = null, bool isAutonomous = false)
{
Prefab = prefab;
Option = option;
ManualPriority = manualPriority;
Type = orderType;
Objective = aiObjective;
UseController = Prefab.UseController;
OrderGiver = orderGiver;
TargetEntity = targetEntity;
if (targetItem != null)
{
if (UseController)
{
ConnectedController = targetItem.Item?.FindController(tags: ControllerTags);
if (ConnectedController == null)
{
DebugConsole.AddWarning("AI: Tried to use a controller for operating an item, but couldn't find any.");
UseController = false;
}
}
TargetEntity = targetItem.Item;
TargetItemComponent = targetItem;
}
TargetType = OrderTargetType.Entity;
}
/// <summary>
/// Constructor for orders with the target type OrderTargetType.Position
/// </summary>
private Order(OrderPrefab prefab, Identifier option, int manualPriority, OrderType orderType, AIObjective aiObjective, OrderTarget target, Character orderGiver = null)
: this(prefab, option, manualPriority, orderType, aiObjective, targetEntity: null, targetItem: null, orderGiver)
{
TargetPosition = target;
TargetType = OrderTargetType.Position;
}
/// <summary>
/// Constructor for orders with the target type OrderTargetType.WallSection
/// </summary>
private Order(OrderPrefab prefab, Identifier option, int manualPriority, OrderType orderType, AIObjective aiObjective, Structure wall, int? sectionIndex, Character orderGiver = null)
: this(prefab, option, manualPriority, orderType, aiObjective, targetEntity: wall, null, orderGiver: orderGiver)
{
WallSectionIndex = sectionIndex;
TargetType = OrderTargetType.WallSection;
}
private Order(
Order other,
OrderPrefab prefab = null,
Identifier option = default,
int? manualPriority = null,
OrderType? type = null,
AIObjective objective = null,
Entity targetEntity = null,
ItemComponent targetItemComponent = null,
Controller connectedController = null,
Character orderGiver = null,
OrderTarget targetPosition = null,
OrderTargetType? targetType = null,
int? wallSectionIndex = null,
bool? useController = null)
{
Prefab = prefab ?? other.Prefab;
Option = option.IfEmpty(other.Option);
ManualPriority = manualPriority ?? other.ManualPriority;
Type = type ?? other.Type;
Objective = objective ?? other.Objective;
TargetEntity = targetEntity ?? other.TargetEntity;
TargetItemComponent = targetItemComponent ?? other.TargetItemComponent;
ConnectedController = connectedController ?? other.ConnectedController;
OrderGiver = orderGiver ?? other.OrderGiver;
TargetPosition = targetPosition ?? other.TargetPosition;
TargetType = targetType ?? other.TargetType;
WallSectionIndex = wallSectionIndex ?? other.WallSectionIndex;
UseController = useController ?? other.UseController;
#if DEBUG
if (UseController && ConnectedController == null)
{
DebugConsole.ThrowError($"AI: Created an Order {Identifier} that's set to use a Controller, but a Controller was not specified.\n{Environment.StackTrace.CleanupStackTrace()}");
}
#endif
}
public Order WithOption(Identifier option)
{
return new Order(this, option: option);
}
public Order WithManualPriority(int newPriority)
{
return new Order(this, manualPriority: newPriority);
}
public Order WithOrderGiver(Character orderGiver)
{
return new Order(this, orderGiver: orderGiver);
}
public Order WithObjective(AIObjective objective)
{
return new Order(this, objective: objective);
}
public Order WithTargetEntity(Entity entity)
{
return new Order(this, targetEntity: entity, targetType: OrderTargetType.Entity);
}
public Order WithTargetSpatialEntity(ISpatialEntity spatialEntity)
{
if (spatialEntity is WallSection wallSection)
{
Structure wall = wallSection.Wall;
int sectionIndex = wall.Sections.IndexOf(wallSection);
return WithWallSection(wall, sectionIndex);
}
else if (spatialEntity is Entity entity)
{
return WithTargetEntity(entity);
}
else if (spatialEntity is OrderTarget orderTarget)
{
return WithTargetPosition(orderTarget);
}
throw new InvalidOperationException($"Unexpected input type: {spatialEntity.GetType().Name}");
}
public Order WithItemComponent(Item item, ItemComponent component = null)
{
Controller controller = null;
if (UseController)
{
controller = item?.FindController(tags: ControllerTags);
}
return new Order(this, targetEntity: item, targetItemComponent: component ?? GetTargetItemComponent(item), connectedController: controller);
}
public Order WithWallSection(Structure wall, int? sectionIndex)
{
return new Order(this, targetEntity: wall, wallSectionIndex: sectionIndex, targetType: OrderTargetType.WallSection);
}
public Order WithType(OrderType type)
{
return new Order(this, type: type);
}
public Order WithTargetPosition(OrderTarget targetPosition)
{
return new Order(this, targetPosition: targetPosition, targetType: OrderTargetType.Position);
}
public Order Clone()
{
return new Order(this);
}
public Order GetDismissal()
{
if (IsDismissal) { throw new InvalidOperationException("Attempted to dismiss a dismissal order"); }
return new Order(this, prefab: OrderPrefab.Prefabs["dismissed"], option: GetDismissOrderOption(this));
}
public bool HasAppropriateJob(Character character)
=> Prefab.HasAppropriateJob(character);
public bool HasPreferredJob(Character character)
=> Prefab.HasPreferredJob(character);
public string GetChatMessage(
string targetCharacterName, string targetRoomName, bool givingOrderToSelf, Identifier orderOption = default, bool isNewOrder = true)
=> Prefab.GetChatMessage(targetCharacterName, targetRoomName, TargetEntity, givingOrderToSelf, orderOption, isNewOrder);
/// <summary>
/// Get the target item component based on the target item type
/// </summary>
public ItemComponent GetTargetItemComponent(Item item)
{
return Prefab.GetTargetItemComponent(item);
}
public bool TryGetTargetItemComponent(Item item, out ItemComponent firstMatchingComponent)
{
return Prefab.TryGetTargetItemComponent(item, out firstMatchingComponent);
}
/// <param name="interactableFor">Only returns items which are interactable for this character</param>
public List<Item> GetMatchingItems(Submarine submarine, bool mustBelongToPlayerSub, CharacterTeamType? requiredTeam = null, Character interactableFor = null)
{
return Prefab.GetMatchingItems(submarine, mustBelongToPlayerSub, requiredTeam, interactableFor);
}
/// <param name="interactableFor">Only returns items which are interactable for this character</param>
public List<Item> GetMatchingItems(bool mustBelongToPlayerSub, Character interactableFor = null)
{
return Prefab.GetMatchingItems(mustBelongToPlayerSub, interactableFor);
}
public LocalizedString GetOptionName(string id)
{
return Prefab.GetOptionName(id);
}
public LocalizedString GetOptionName(Identifier id)
{
return Prefab.GetOptionName(id);
}
public LocalizedString GetOptionName(int index)
{
return Prefab.GetOptionName(index);
}
/// <summary>
/// Used to create the order option for the Dismiss order to know which order it targets
/// </summary>
/// <param name="orderInfo">The order to target with the dismiss order</param>
public static Identifier GetDismissOrderOption(Order order)
{
return OrderPrefab.GetDismissOrderOption(order);
}
public bool MatchesOrder(Identifier orderIdentifier, Identifier orderOption) =>
orderIdentifier == Identifier && orderOption == Option;
/*public bool MatchesOrder(Order order, Identifier option) =>
order != null && MatchesOrder(order.Identifier, option);*/
public bool MatchesOrder(Order order) =>
order != null && MatchesOrder(order.Identifier, order.Option);
public bool MatchesDismissedOrder(Identifier dismissOrderOption)
{
Identifier[] dismissedOrder = dismissOrderOption.Value.Split('.').Select(s => s.ToIdentifier()).ToArray();
if (dismissedOrder != null && dismissedOrder.Length > 0)
{
Identifier dismissedOrderIdentifier = dismissedOrder.Length > 0 ? dismissedOrder[0] : Identifier.Empty;
if (dismissedOrderIdentifier == Identifier.Empty || dismissedOrderIdentifier != Identifier) { return false; }
Identifier dismissedOrderOption = dismissedOrder.Length > 1 ? dismissedOrder[1] : Identifier.Empty;
if (dismissedOrderOption == Identifier.Empty && Option == Identifier.Empty) { return true; }
return dismissedOrderOption == Option;
}
else
{
return false;
}
}
public ImmutableArray<Identifier> GetTargetItems(Identifier option = default)
=> Prefab.GetTargetItems(option);
public override string ToString()
{
return $"Order ({Name})";
}
}
}