3 using Microsoft.Xna.Framework;
5 using System.Collections.Generic;
6 using System.Collections.Immutable;
61 private readonly ImmutableDictionary<Identifier, ImmutableArray<Identifier>> OptionTargetItems;
64 private readonly Color? color;
106 public readonly ImmutableArray<Identifier>
Options;
165 Name = TextManager.Get($
"OrderName.{Identifier}");
169 if (!
string.IsNullOrWhiteSpace(targetItemType))
173 ItemComponentType = Type.GetType(
"Barotrauma.Items.Components." + targetItemType,
true,
true);
178 DebugConsole.ThrowError(
"Error in the order definitions: item component type " + targetItemType +
" not found", e);
194 var optionTargetItems =
new Dictionary<Identifier, ImmutableArray<Identifier>>();
195 if (orderElement.
GetAttributeString(
"targetitems",
"") is
string targetItems && targetItems.Contains(
';'))
197 string[] splitTargetItems = targetItems.Split(
';');
199 if (splitTargetItems.Length !=
AllOptions.Length)
201 DebugConsole.ThrowError($
"Order \"{Identifier}\" has option-specific target items, but the option count doesn't match the target item count");
204 var allTargetItems =
new List<Identifier>();
207 Identifier[] optionTargetItemsSplit = i < splitTargetItems.Length ? splitTargetItems[i].ToIdentifiers().ToArray() : Array.Empty<
Identifier>();
208 allTargetItems.AddRange(optionTargetItemsSplit);
209 optionTargetItems.Add(
AllOptions[i], optionTargetItemsSplit.ToImmutableArray());
218 OptionTargetItems = optionTargetItems.ToImmutableDictionary();
222 this.CategoryIdentifier = (this.Category?.ToString() ??
string.Empty).ToIdentifier();
234 if (spriteElement !=
null)
239 var optionSprites =
new Dictionary<Identifier, Sprite>();
243 if (optionSpriteElements !=
null && optionSpriteElements.Any())
245 for (
int i = 0; i <
Options.Length; i++)
247 if (i >= optionSpriteElements.Count()) {
break; };
248 var sprite =
new Sprite(optionSpriteElements.ElementAt(i), lazyLoad:
true);
249 optionSprites.Add(
Options[i], sprite);
263 private bool HasSpecifiedJob(
Character character, IReadOnlyList<Identifier> jobs)
265 if (jobs ==
null || jobs.Count == 0) {
return false; }
267 if (jobIdentifier.IsEmpty) {
return false; }
268 for (
int i = 0; i < jobs.Count; i++)
270 if (jobIdentifier == jobs[i]) {
return true; }
279 public string GetChatMessage(
string targetCharacterName,
string targetRoomName,
Entity targetEntity,
bool givingOrderToSelf,
Identifier orderOption =
default,
bool isNewOrder =
true)
284 if (!givingOrderToSelf)
286 return TextManager.GetWithVariable(
"rearrangedorders",
"[name]", targetCharacterName ??
string.Empty).Value;
294 string messageTag = $
"{(givingOrderToSelf && !TargetAllCharacters ? "OrderDialogSelf
" : "OrderDialog
")}.{Identifier}";
295 if (!orderOption.IsEmpty)
299 messageTag += $
".{orderOption}";
303 string[] splitOption = orderOption.Value.Split(
'.');
304 if (splitOption.Length > 0)
306 messageTag += $
".{splitOption[0]}";
312 switch (targetEntity)
315 targetEntityName = item.Name;
318 targetEntityName = hull.DisplayName;
321 targetEntityName = structure.Name;
328 return TextManager.GetWithVariables(messageTag,
329 (
"[name]", targetCharacterName ??
string.Empty,
FormatCapitals.No),
331 (
"[roomname]", targetRoomName ??
string.Empty,
FormatCapitals.Yes)).Fallback(
"").Value;
342 if (component?.GetType() is Type componentType)
354 return firstMatchingComponent !=
null;
360 List<Item> matchingItems =
new List<Item>();
361 if (submarine ==
null) {
return matchingItems; }
369 if (mustBelongToPlayerSub && item.Submarine?.Info !=
null && item.Submarine.Info.Type !=
SubmarineType.Player) {
continue; }
370 if (item.Submarine != submarine && !submarine.
DockedTo.Contains(item.Submarine)) {
continue; }
371 if (requiredTeam.HasValue && (item.Submarine ==
null || item.Submarine.TeamID != requiredTeam.Value)) {
continue; }
372 if (item.NonInteractable) {
continue; }
376 if (interactableFor !=
null && (!item.IsInteractable(interactableFor) || (
UseController && !controller.
Item.
IsInteractable(interactableFor)))) {
continue; }
377 matchingItems.Add(item);
380 return matchingItems;
389 return GetMatchingItems(submarine, mustBelongToPlayerSub, interactableFor: interactableFor, orderOption: orderOption);
405 if (index < 0 || index >=
Options.Length) {
return null; }
418 option = $
"{option}.{order.Option}".ToIdentifier();
425 if (option.IsEmpty || !OptionTargetItems.TryGetValue(option, out ImmutableArray<Identifier> optionTargetItems))
431 return optionTargetItems;
437 if (item ==
null) {
return false; }
446 return item !=
null && targetItems !=
null && targetItems.Length > 0 && (targetItems.Contains(item.
Prefab.
Identifier) || item.
HasTag(targetItems));
458 return targetType
switch
460 OrderTargetType.Entity =>
new Order(
this, targetEntity:
null, targetItem:
null, orderGiver, isAutonomous),
463 _ =>
throw new NotImplementedException()
466 catch (NotImplementedException e)
468 DebugConsole.LogError($
"Error creating a new Order instance: unexpected target type \"{targetType}\".\n{e.StackTrace.CleanupStackTrace()}",
508 if (targetSpatialEntity ==
null)
519 return targetSpatialEntity;
542 public ref readonly ImmutableArray<Identifier>
Options => ref
Prefab.Options;
572 : this(prefab,
Identifier.Empty, 0,
OrderType.Current, null, targetEntity, targetItem, orderGiver, isAutonomous) { }
578 : this(prefab, option, 0,
OrderType.Current, null, targetEntity, targetItem, orderGiver, isAutonomous) { }
584 : this(prefab, prefab.
Options.FirstOrDefault(), 0,
OrderType.Current, null, target, orderGiver) { }
590 : this(prefab, option, 0,
OrderType.Current, null, target, orderGiver) { }
596 : this(prefab,
Identifier.Empty, 0,
OrderType.Current, null, wall, sectionIndex, orderGiver) { }
602 : this(prefab, option, 0,
OrderType.Current, null, wall, sectionIndex, orderGiver) { }
619 if (targetItem !=
null)
626 DebugConsole.AddWarning(
"AI: Tried to use a controller for operating an item, but couldn't find any.");
640 private Order(OrderPrefab prefab,
Identifier option,
int manualPriority,
OrderType orderType, AIObjective aiObjective, OrderTarget target, Character orderGiver =
null)
641 : this(prefab, option, manualPriority, orderType, aiObjective, targetEntity: null, targetItem: null, orderGiver)
650 private Order(OrderPrefab prefab,
Identifier option,
int manualPriority,
OrderType orderType, AIObjective aiObjective, Structure wall,
int? sectionIndex, Character orderGiver =
null)
651 : this(prefab, option, manualPriority, orderType, aiObjective, targetEntity: wall, null, orderGiver: orderGiver)
659 OrderPrefab prefab =
null,
661 int? manualPriority =
null,
663 AIObjective objective =
null,
664 Entity targetEntity =
null,
667 Character orderGiver =
null,
668 OrderTarget targetPosition =
null,
670 int? wallSectionIndex =
null,
671 bool? useController =
null)
674 Option = option.IfEmpty(other.Option);
676 Type = type ?? other.Type;
677 Objective = objective ?? other.Objective;
695 DebugConsole.ThrowError($
"AI: Created an Order {Identifier} that's set to use a Controller, but a Controller was not specified.\n{Environment.StackTrace.CleanupStackTrace()}");
702 return new Order(
this, option: option);
707 return new Order(
this, manualPriority: newPriority);
712 return new Order(
this, orderGiver: orderGiver);
717 return new Order(
this, objective: objective);
730 int sectionIndex = wall.
Sections.IndexOf(wallSection);
733 else if (spatialEntity is
Entity entity)
742 throw new InvalidOperationException($
"Unexpected input type: {spatialEntity.GetType().Name}");
752 return new Order(
this, targetEntity: item, targetItemComponent: component ??
GetTargetItemComponent(item), connectedController: controller);
757 return new Order(
this, targetEntity: wall, wallSectionIndex: sectionIndex, targetType:
OrderTargetType.WallSection);
762 return new Order(
this, type: type);
772 return new Order(
this);
777 if (
IsDismissal) {
throw new InvalidOperationException(
"Attempted to dismiss a dismissal order"); }
782 =>
Prefab.HasAppropriateJob(character);
785 =>
Prefab.HasPreferredJob(character);
788 string targetCharacterName,
string targetRoomName,
bool givingOrderToSelf,
Identifier orderOption =
default,
bool isNewOrder =
true)
789 =>
Prefab.GetChatMessage(targetCharacterName, targetRoomName,
TargetEntity, givingOrderToSelf, orderOption, isNewOrder);
796 return Prefab.GetTargetItemComponent(item);
801 return Prefab.TryGetTargetItemComponent(item, out firstMatchingComponent);
807 return Prefab.GetMatchingItems(submarine, mustBelongToPlayerSub, requiredTeam, interactableFor);
814 return Prefab.GetMatchingItems(mustBelongToPlayerSub, interactableFor);
819 return Prefab.GetOptionName(
id);
824 return Prefab.GetOptionName(
id);
829 return Prefab.GetOptionName(index);
848 order !=
null &&
MatchesOrder(order.Identifier, order.Option);
852 Identifier[] dismissedOrder = dismissOrderOption.Value.Split(
'.').Select(s => s.ToIdentifier()).ToArray();
853 if (dismissedOrder !=
null && dismissedOrder.Length > 0)
855 Identifier dismissedOrderIdentifier = dismissedOrder.Length > 0 ? dismissedOrder[0] :
Identifier.Empty;
856 if (dismissedOrderIdentifier ==
Identifier.Empty || dismissedOrderIdentifier !=
Identifier) {
return false; }
857 Identifier dismissedOrderOption = dismissedOrder.Length > 1 ? dismissedOrder[1] :
Identifier.Empty;
859 return dismissedOrderOption ==
Option;
868 =>
Prefab.GetTargetItems(option);
872 return $
"Order ({Name})";
static Character? Controlled
string? GetAttributeString(string key, string? def)
Identifier[] GetAttributeIdentifierArray(Identifier[] def, params string[] keys)
Color GetAttributeColor(string key, in Color def)
float GetAttributeFloat(string key, float def)
IEnumerable< ContentXElement > GetChildElements(string name)
ContentXElement? GetChildElement(string name)
bool GetAttributeBool(string key, bool def)
int GetAttributeInt(string key, int def)
Identifier GetAttributeIdentifier(string key, string def)
Entity(Submarine submarine, ushort id)
static GameSession?? GameSession
bool IsInteractable(Character character)
Returns interactibility based on whether the character is on a player team
bool HasTag(Identifier tag)
static readonly List< Item > ItemList
static HashSet< Item > DeconstructItems
Items that have been marked for deconstruction
Controller FindController(ImmutableArray< Identifier >? tags=null)
The base class for components holding the different functionalities of the item
LocalizedString Fallback(LocalizedString fallback, bool useDefaultLanguageIfFound=true)
Use this text instead if the original text cannot be found.
static readonly PrefabCollection< OrderCategoryIcon > OrderCategoryIcons
OrderCategoryIcon(ContentXElement element, OrdersFile file)
readonly OrderCategory Category
Order WithOption(Identifier option)
readonly Entity TargetEntity
Order WithTargetPosition(OrderTarget targetPosition)
Order(OrderPrefab prefab, Identifier option, OrderTarget target, Character orderGiver=null)
Constructor for orders with the target type OrderTargetType.Position
bool MatchesOrder(Order order)
ref readonly ImmutableArray< Identifier > AppropriateJobs
ItemComponent GetTargetItemComponent(Item item)
Get the target item component based on the target item type
LocalizedString ContextualName
bool MatchesDismissedOrder(Identifier dismissOrderOption)
string GetChatMessage(string targetCharacterName, string targetRoomName, bool givingOrderToSelf, Identifier orderOption=default, bool isNewOrder=true)
Order(OrderPrefab prefab, Identifier option, Entity targetEntity, ItemComponent targetItem, Character orderGiver=null, bool isAutonomous=false)
Constructor for orders with the target type OrderTargetType.Entity
Order(OrderPrefab prefab, Entity targetEntity, ItemComponent targetItem, Character orderGiver=null, bool isAutonomous=false)
Constructor for orders with the target type OrderTargetType.Entity
override string ToString()
ref readonly ImmutableArray< Identifier > HiddenOptions
readonly Character OrderGiver
ref readonly ImmutableArray< Identifier > TargetItems
bool TryGetTargetItemComponent(Item item, out ItemComponent firstMatchingComponent)
static Identifier GetDismissOrderOption(Order order)
Used to create the order option for the Dismiss order to know which order it targets
Order WithType(OrderType type)
ISpatialEntity??? TargetSpatialEntity
Note this property doesn't return the follow target of the Follow objective, as expected!
LocalizedString GetOptionName(string id)
ref readonly ImmutableArray< Identifier > Options
readonly? int WallSectionIndex
readonly Identifier Option
LocalizedString GetOptionName(Identifier id)
bool HasPreferredJob(Character character)
Order WithTargetEntity(Entity entity)
readonly int ManualPriority
readonly OrderTarget TargetPosition
readonly bool UseController
Identifier AppropriateSkill
LocalizedString GetOptionName(int index)
bool DisplayGiverInTooltip
Order WithManualPriority(int newPriority)
Order(OrderPrefab prefab, OrderTarget target, Character orderGiver=null)
Constructor for orders with the target type OrderTargetType.Position
ref readonly ImmutableArray< Identifier > AllOptions
List< Item > GetMatchingItems(bool mustBelongToPlayerSub, Character interactableFor=null)
Only returns items which are interactable for this character
readonly Controller ConnectedController
Order(OrderPrefab prefab, Identifier option, Structure wall, int? sectionIndex, Character orderGiver=null)
Constructor for orders with the target type OrderTargetType.WallSection
readonly AIObjective Objective
Order WithItemComponent(Item item, ItemComponent component=null)
ref readonly ImmutableArray< Identifier > RequireItems
Order WithWallSection(Structure wall, int? sectionIndex)
readonly OrderPrefab Prefab
List< Item > GetMatchingItems(Submarine submarine, bool mustBelongToPlayerSub, CharacterTeamType? requiredTeam=null, Character interactableFor=null)
Only returns items which are interactable for this character
Order WithTargetSpatialEntity(ISpatialEntity spatialEntity)
Order(OrderPrefab prefab, Structure wall, int? sectionIndex, Character orderGiver=null)
Constructor for orders with the target type OrderTargetType.WallSection
bool MatchesOrder(Identifier orderIdentifier, Identifier orderOption)
ref readonly ImmutableArray< Identifier > ControllerTags
ImmutableArray< Identifier > GetTargetItems(Identifier option=default)
bool DrawIconWhenContained
readonly OrderTargetType TargetType
Order WithObjective(AIObjective objective)
bool HasAppropriateJob(Character character)
readonly ItemComponent TargetItemComponent
Order WithOrderGiver(Character orderGiver)
bool ColoredWhenControllingGiver
readonly ImmutableArray< Identifier > RequireItems
readonly ImmutableArray< Identifier > HiddenOptions
OrderPrefab(ContentXElement orderElement, OrdersFile file)
readonly bool UseController
readonly LocalizedString Name
bool HasOptionSpecificTargetItems
bool TryGetTargetItemComponent(Item item, out ItemComponent firstMatchingComponent)
List< Item > GetMatchingItems(bool mustBelongToPlayerSub, Character interactableFor=null, Identifier orderOption=default)
Only returns items which are interactable for this character
readonly ImmutableArray< Identifier > TargetItems
readonly bool AutoDismiss
If enabled and this is an Operate order, it will remove Operate orders of the same item from other ch...
readonly? OrderCategory Category
readonly Identifier CategoryIdentifier
readonly bool MustSetTarget
readonly bool CanTypeBeSubclass
readonly Sprite SymbolSprite
bool ColoredWhenControllingGiver
LocalizedString GetOptionName(int index)
static bool TargetItemsMatchItem(ImmutableArray< Identifier > targetItems, Item item)
bool TargetItemsMatchItem(Item item, Identifier option=default)
readonly bool TargetAllCharacters
readonly bool MustManuallyAssign
readonly ImmutableArray< Identifier > AllOptions
readonly ImmutableDictionary< Identifier, Sprite > OptionSprites
bool HasPreferredJob(Character character)
readonly Identifier AppropriateSkill
int AssignmentPriority
Affects how high on the order list the order will be placed (i.e. the manual priority order when it's...
readonly float FadeOutTime
readonly ImmutableArray< Identifier > AppropriateJobs
If defined, the order can only be quick-assigned to characters with these jobs. Or if it's a report,...
static readonly PrefabCollection< OrderPrefab > Prefabs
readonly ImmutableArray< Identifier > PreferredJobs
If defined, the order will be quick-assigned to characters with these jobs before characters with oth...
Order CreateInstance(OrderTargetType targetType, Character orderGiver=null, bool isAutonomous=false)
Create an Order instance with a null target
readonly bool IgnoreAtOutpost
static OrderPrefab Dismissal
ImmutableArray< Identifier > GetTargetItems(Identifier option=default)
readonly bool CanBeGeneralized
Can the order be turned into a non-entity-targeting one if it was originally created with a target en...
bool HasAppropriateJob(Character character)
bool DrawIconWhenContained
Should the order icon be drawn when the order target is inside a container
LocalizedString GetOptionName(string id)
List< Item > GetMatchingItems(Submarine submarine, bool mustBelongToPlayerSub, CharacterTeamType? requiredTeam=null, Character interactableFor=null, Identifier orderOption=default)
Only returns items which are interactable for this character
ItemComponent GetTargetItemComponent(Item item)
Get the target item component based on the target item type
bool IsVisibleAsReportButton
string GetChatMessage(string targetCharacterName, string targetRoomName, Entity targetEntity, bool givingOrderToSelf, Identifier orderOption=default, bool isNewOrder=true)
readonly Type ItemComponentType
static readonly Identifier DismissalIdentifier
readonly ListDictionary< Identifier, LocalizedString > OptionNames
readonly ImmutableArray< Identifier > Options
readonly ImmutableArray< Identifier > ControllerTags
readonly LocalizedString ContextualName
Name that can be used with the contextual version of the order
OrderTargetType TargetType
static Identifier GetDismissOrderOption(Order order)
Used to create the order option for the Dismiss order to know which order it targets
LocalizedString GetOptionName(Identifier id)
bool DisplayGiverInTooltip
Prefab(ContentFile file, Identifier identifier)
readonly Identifier Identifier
Prefab that has a property serves as a deterministic hash of a prefab's identifier....
static readonly Submarine[] MainSubs
IEnumerable< Submarine > DockedTo
static Submarine MainSub
Note that this can be null in some situations, e.g. editors and missions that don't load a submarine.