Client LuaCsForBarotrauma
CheckItemAction.cs
3 using Microsoft.Xna.Framework;
4 using System.Collections.Generic;
5 using System.Linq;
6 
7 namespace Barotrauma
8 {
14  {
15  [Serialize("", IsPropertySaveable.Yes, description: "Either the tag of the item(s) we want to check, or a character/container the items are inside.")]
16  public Identifier TargetTag { get; set; }
17 
18  [Serialize("", IsPropertySaveable.Yes, description: "The target item must have one of these identifiers.")]
19  public string ItemIdentifiers { get; set; }
20 
21  [Serialize("", IsPropertySaveable.Yes, description: "The target item must have at least one of these tags.")]
22  public string ItemTags { get; set; }
23 
24  [Serialize(1, IsPropertySaveable.Yes, description: "The minimum number of matching items for the check to succeed.")]
25  public int Amount { get; set; }
26 
27  [Serialize("", IsPropertySaveable.Yes, description: "Optional tag of a hull the target must be inside.")]
28  public Identifier HullTag { get; set; }
29 
30  [Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the first target when the check succeeds.")]
31  public Identifier ApplyTagToTarget { get; set; }
32 
33  [Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the found item(s) when the check succeeds.")]
34  public Identifier ApplyTagToItem { get; set; }
35 
36  [Serialize(false, IsPropertySaveable.Yes, description: "Does the item need to be equipped for the check to succeed?")]
37  public bool RequireEquipped { get; set; }
38 
39  [Serialize(false, IsPropertySaveable.Yes, description: "Does the item need to be worn for the check to succeed?")]
40  public bool RequireWorn { get; set; }
41 
42  [Serialize(true, IsPropertySaveable.Yes, description: "If enabled, the doesn't need to be directly inside the container/character we're checking, but can be nested inside multiple containers (e.g. in a toolbelt in a character's inventory).")]
43  public bool Recursive { get; set; }
44 
45  [Serialize(-1, IsPropertySaveable.Yes, description: "Can be used to require the item to be in a specific ItemContainer of the target container. For example, the input slots of a fabricator (the first ItemContainer of the fabricator, with an index of 0).")]
46  public int ItemContainerIndex { get; set; }
47 
48  private readonly bool checkPercentage;
49 
50  private float requiredConditionalMatchPercentage;
51 
52  [Serialize(100.0f, IsPropertySaveable.Yes, description: "What percentage of targets do the conditionals need to match for the check to succeed?")]
54  {
55  get { return requiredConditionalMatchPercentage; }
56  set { requiredConditionalMatchPercentage = MathHelper.Clamp(value, 0.0f, 100.0f); }
57  }
58 
59  [Serialize(false, IsPropertySaveable.Yes, description: "When enabled, the number of matching items is compared to the number of matching items there were at the start of the round. Only valid if RequiredConditionalMatchPercentage is set.")]
60  public bool CompareToInitialAmount { get; set; }
61 
62  private readonly IReadOnlyList<PropertyConditional> conditionals;
63 
64  private readonly Identifier[] itemIdentifierSplit;
65  private readonly Identifier[] itemTags;
66 
67  public CheckItemAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
68  {
69  itemIdentifierSplit = ItemIdentifiers.ToIdentifiers().ToArray();
70  itemTags = ItemTags.ToIdentifiers().ToArray();
71  var conditionalList = new List<PropertyConditional>();
72  foreach (ContentXElement subElement in element.GetChildElements("conditional"))
73  {
74  conditionalList.AddRange(PropertyConditional.FromXElement(subElement));
75  }
76  conditionals = conditionalList;
77 
78  if (itemTags.None() &&
79  ItemIdentifiers.None() &&
80  TargetTag.IsEmpty)
81  {
82  DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". {nameof(CheckItemAction)} does't define either tags or identifiers of the item to check.",
83  contentPackage: element.ContentPackage);
84  }
85  checkPercentage = element.GetAttribute(nameof(RequiredConditionalMatchPercentage)) is not null;
86  if (checkPercentage && conditionals.None())
87  {
88  DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". {nameof(CheckItemAction)} requires conditionals to be met on {requiredConditionalMatchPercentage}% of the targets, but there are no conditionals defined.");
89  }
90  if (Amount != 1 && checkPercentage)
91  {
92  DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". Cannot define both '{Amount}' and '{RequiredConditionalMatchPercentage}' in {nameof(CheckItemAction)}.",
93  contentPackage: element.ContentPackage);
94  }
95  }
96 
97  private bool EnoughTargets(int totalTargets, int targetsWithConditionalsMatched)
98  {
99  if (checkPercentage)
100  {
102  {
104  }
105  return MathUtils.Percentage(targetsWithConditionalsMatched, totalTargets) >= RequiredConditionalMatchPercentage;
106  }
107  else
108  {
109  return targetsWithConditionalsMatched >= Amount;
110  }
111  }
112 
113  private readonly List<Item> tempTargetItems = new List<Item>();
114  protected override bool? DetermineSuccess()
115  {
116  var targets = ParentEvent.GetTargets(TargetTag);
117 
118  if (!HullTag.IsEmpty)
119  {
120  var hulls = ParentEvent.GetTargets(HullTag).OfType<Hull>();
121  targets = targets.Where(t =>
122  (t is Item it && hulls.Contains(it.CurrentHull)) ||
123  (t is Character c && hulls.Contains(c.CurrentHull)));
124  }
125 
126  if (!targets.Any())
127  {
128  if (conditionals.Any())
129  {
130  //conditionals can't be met if there's no targets
131  return false;
132  }
133  return null;
134  }
135 
136  //check if the target(s) are the items we're looking for (instead of characters/containers the items are inside)
137  int targetCount = targets.Count();
138  if (targetCount >= Amount)
139  {
140  tempTargetItems.Clear();
141  foreach (var target in targets)
142  {
143  if (target is not Item item) { continue; }
144  if (itemTags.Any(item.HasTag) || itemIdentifierSplit.Contains(item.Prefab.Identifier) ||
145  (itemTags.None() && itemIdentifierSplit.None() && conditionals.Any()))
146  {
147  if (ConditionalsMatch(item, character: null))
148  {
149  tempTargetItems.Add(item);
150  }
151  }
152  }
153  if (EnoughTargets(targetCount, tempTargetItems.Count))
154  {
155  TryApplyTagToItems(tempTargetItems);
156  return true;
157  }
158  }
159 
160  foreach (var target in targets)
161  {
162  if (target is Character character)
163  {
164  Inventory inventory = character.Inventory;
165  if (CheckInventory(character.Inventory, character))
166  {
167  if (!ApplyTagToTarget.IsEmpty)
168  {
170  }
171  return true;
172  }
173  }
174  else if (target is Item item)
175  {
176  int i = 0;
177  foreach (var itemContainer in item.GetComponents<ItemContainer>())
178  {
179  if (ItemContainerIndex == -1 || i == ItemContainerIndex)
180  {
181  if (CheckInventory(itemContainer.Inventory, character: null))
182  {
183  if (!ApplyTagToTarget.IsEmpty)
184  {
186  }
187  return true;
188  }
189  }
190  i++;
191  }
192  }
193  }
194  return false;
195  }
196 
197  private bool CheckInventory(Inventory inventory, Character character)
198  {
199  if (inventory == null) { return false; }
200  int targetCount = 0;
201  HashSet<Item> eventTargets = new HashSet<Item>();
202  tempTargetItems.Clear();
203  foreach (Identifier tag in itemTags)
204  {
205  foreach (var target in ParentEvent.GetTargets(tag))
206  {
207  if (target is Item item)
208  {
209  eventTargets.Add(item);
210  }
211  }
212  }
213  foreach (Item item in inventory.FindAllItems(it =>
214  itemTags.Any(it.HasTag) ||
215  itemIdentifierSplit.Contains(it.Prefab.Identifier) ||
216  eventTargets.Contains(it),
217  recursive: Recursive))
218  {
219  targetCount++;
220  if (ConditionalsMatch(item, character))
221  {
222  tempTargetItems.Add(item);
223  }
224  }
225 
226  if (EnoughTargets(targetCount, tempTargetItems.Count))
227  {
228  TryApplyTagToItems(tempTargetItems);
229  return true;
230  }
231  return false;
232 
233  }
234 
235  private void TryApplyTagToItems(IEnumerable<Item> items)
236  {
237  if (!ApplyTagToItem.IsEmpty)
238  {
239  foreach (var targetItem in items)
240  {
241  ParentEvent.AddTarget(ApplyTagToItem, targetItem);
242  }
243  }
244  }
245 
246  private bool ConditionalsMatch(Item item, Character character = null)
247  {
248  if (item == null) { return false; }
249  foreach (PropertyConditional conditional in conditionals)
250  {
251  if (!item.ConditionalMatches(conditional))
252  {
253  return false;
254  }
255  }
256  if (RequireEquipped)
257  {
258  if (character == null) { return false; }
259  return character.HasEquippedItem(item);
260  }
261  if (RequireWorn)
262  {
263  if (character == null) { return false; }
264  foreach (var wearable in item.GetComponents<Wearable>())
265  {
266  foreach (var allowedSlot in wearable.AllowedSlots)
267  {
268  if (allowedSlot == InvSlotType.Any) { continue; }
269  if (character.HasEquippedItem(item, allowedSlot)) { return true; }
270  }
271  }
272  return false;
273  }
274  return true;
275  }
276 
277  public override string ToDebugString()
278  {
279  return $"{ToolBox.GetDebugSymbol(HasBeenDetermined())} {nameof(CheckItemAction)} -> (TargetTag: {TargetTag.ColorizeObject()}, " +
280  (ItemTags.Any() ? $"ItemTags: {ItemTags.ColorizeObject()}, " : $"ItemIdentifiers: {ItemIdentifiers.ColorizeObject()}, ") +
281  $"Succeeded: {succeeded.ColorizeObject()})";
282  }
283  }
284 }
bool HasEquippedItem(Item item, InvSlotType? slotType=null, Func< InvSlotType, bool > predicate=null)
Can be used to do various kinds of checks on items: whether a specific kind of item exists,...
CheckItemAction(ScriptedEvent parentEvent, ContentXElement element)
override? bool DetermineSuccess()
override string ToDebugString()
Rich test to display in debugdraw
IEnumerable< ContentXElement > GetChildElements(string name)
readonly ScriptedEvent ParentEvent
Definition: EventAction.cs:106
Inventory(Entity owner, int capacity, int slotsPerRow=5)
List< Item > FindAllItems(Func< Item, bool > predicate=null, bool recursive=false, List< Item > list=null)
Conditionals are used by some in-game mechanics to require one or more conditions to be met for those...
static IEnumerable< PropertyConditional > FromXElement(ContentXElement element, Predicate< XAttribute >? predicate=null)
int GetInitialTargetCount(Identifier tag)
void AddTarget(Identifier tag, Entity target)
IEnumerable< Entity > GetTargets(Identifier tag)