Client LuaCsForBarotrauma
TagAction.cs
2 using System;
3 using System.Collections.Generic;
4 using System.Collections.Immutable;
5 using System.Linq;
6 
7 namespace Barotrauma
8 {
13  {
14  public enum SubType { Any = 0, Player = 1, Outpost = 2, Wreck = 4, BeaconStation = 8 }
15 
16  [Serialize("", IsPropertySaveable.Yes, description: "What criteria to use to select the entities to target. Valid values are players, player, traitor, nontraitor, nontraitorplayer, bot, crew, humanprefabidentifier:[id], jobidentifier:[id], structureidentifier:[id], structurespecialtag:[tag], itemidentifier:[id], itemtag:[tag], hull, hullname:[name], submarine:[type], eventtag:[tag].")]
17  public string Criteria { get; set; }
18 
19  [Serialize("", IsPropertySaveable.Yes, description: "The tag to apply to the target.")]
20  public Identifier Tag { get; set; }
21 
22  [Serialize(SubType.Any, IsPropertySaveable.Yes, description: "The type of submarine the target needs to be in.")]
23  public SubType SubmarineType { get; set; }
24 
25  [Serialize("", IsPropertySaveable.Yes, "If set, the target must be in an outpost module that has this tag.")]
26  public Identifier RequiredModuleTag { get; set; }
27 
28  [Serialize(true, IsPropertySaveable.Yes, description: "Should incapacitated (e.g. dead, paralyzed, unconscious) characters be ignored, i.e. not considered valid targets?")]
29  public bool IgnoreIncapacitatedCharacters { get; set; }
30 
31  [Serialize(false, IsPropertySaveable.Yes, description: "Can items that have been set to be hidden in-game be tagged?")]
32  public bool AllowHiddenItems { get; set; }
33 
34  [Serialize(false, IsPropertySaveable.Yes, description: "If there are multiple matching targets, should all of them be tagged or one chosen randomly?")]
35  public bool ChooseRandom { get; set; }
36 
37  [Serialize(false, IsPropertySaveable.Yes, description: "Should the event continue if the TagAction can't find any valid targets?")]
38  public bool ContinueIfNoTargetsFound { get; set; }
39 
40  [Serialize(0.0f, IsPropertySaveable.Yes, description: "If larger than 0, the specified percentage of the matching targets are tagged. Between 0-100.")]
41  public float ChoosePercentage { get; set; }
42 
43  private bool isFinished = false;
44 
49  private bool cantFindTargets = false;
50 
55  private bool mustRecheckTargets = false;
56 
57  private bool taggingDone = false;
58 
59  public TagAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
60  {
61  Taggers = new (string k, Action<Identifier> v)[]
62  {
63  ("players", v => TagPlayers()),
64  ("player", v => TagPlayers()),
65  ("traitor", v => TagTraitors()),
66  ("nontraitor", v => TagNonTraitors()),
67  ("nontraitorplayer", v => TagNonTraitorPlayers()),
68  ("bot", v => TagBots(playerCrewOnly: false)),
69  ("crew", v => TagCrew()),
70  ("humanprefabidentifier", TagHumansByIdentifier),
71  ("humanprefabtag", TagHumansByTag),
72  ("jobidentifier", TagHumansByJobIdentifier),
73  ("structureidentifier", TagStructuresByIdentifier),
74  ("structurespecialtag", TagStructuresBySpecialTag),
75  ("itemidentifier", TagItemsByIdentifier),
76  ("itemtag", TagItemsByTag),
77  ("hull", v => TagHulls()),
78  ("hullname", TagHullsByName),
79  ("submarine", TagSubmarinesByType),
80  ("eventtag", TagByEventTag),
81  }.Select(t => (t.k.ToIdentifier(), t.v)).ToImmutableDictionary();
82  }
83 
84  public override bool IsFinished(ref string goTo)
85  {
86  return isFinished;
87  }
88  public override void Reset()
89  {
90  isFinished = false;
91  }
92 
93  private void TagByEventTag(Identifier eventTag)
94  {
95  AddTarget(Tag, ParentEvent.GetTargets(eventTag).Where(t => MatchesRequirements(t)));
96  }
97 
98  private void TagPlayers()
99  {
100  AddTargetPredicate(
101  Tag,
102  ScriptedEvent.TargetPredicate.EntityType.Character,
103  e => e is Character c && c.IsPlayer && (!c.IsIncapacitated || !IgnoreIncapacitatedCharacters));
104  }
105 
106  private void TagTraitors()
107  {
108  AddTargetPredicate(
109  Tags.Traitor,
110  ScriptedEvent.TargetPredicate.EntityType.Character,
111  e => e is Character c && (c.IsPlayer || c.IsBot) && c.IsTraitor && !c.IsIncapacitated);
112  }
113 
114  private void TagNonTraitors()
115  {
116  AddTargetPredicate(
117  Tags.NonTraitor,
118  ScriptedEvent.TargetPredicate.EntityType.Character,
119  e => e is Character c && (c.IsPlayer || c.IsBot) && !c.IsTraitor && c.IsOnPlayerTeam && !c.IsIncapacitated);
120  }
121 
122  private void TagNonTraitorPlayers()
123  {
124  AddTargetPredicate(
125  Tags.NonTraitorPlayer,
126  ScriptedEvent.TargetPredicate.EntityType.Character,
127  e => e is Character c && c.IsPlayer && !c.IsTraitor && c.IsOnPlayerTeam && !c.IsIncapacitated);
128  }
129 
130  private void TagBots(bool playerCrewOnly)
131  {
132  AddTargetPredicate(
133  Tag,
134  ScriptedEvent.TargetPredicate.EntityType.Character,
135  e =>
136  e is Character c &&
137  c.IsBot &&
138  (!c.IsIncapacitated || !IgnoreIncapacitatedCharacters) &&
139  (!playerCrewOnly || c.TeamID == CharacterTeamType.Team1));
140  }
141 
142  private void TagCrew()
143  {
144 #if CLIENT
145  AddTarget(Tag, GameMain.GameSession.CrewManager.GetCharacters());
146 #else
147  TagPlayers();
148  TagBots(playerCrewOnly: true);
149 #endif
150  }
151 
152  private void TagHumansByIdentifier(Identifier identifier)
153  {
154  AddTarget(Tag, Character.CharacterList.Where(c => c.HumanPrefab?.Identifier == identifier));
155  }
156 
157  private void TagHumansByTag(Identifier tag)
158  {
159  AddTarget(Tag, Character.CharacterList.Where(c => c.HumanPrefab != null && c.HumanPrefab.GetTags().Contains(tag)));
160  }
161 
162  private void TagHumansByJobIdentifier(Identifier jobIdentifier)
163  {
164  AddTarget(Tag, Character.CharacterList.Where(c => c.HasJob(jobIdentifier)));
165  }
166 
167  private void TagStructuresByIdentifier(Identifier identifier)
168  {
169  AddTargetPredicate(
170  Tag,
171  ScriptedEvent.TargetPredicate.EntityType.Structure,
172  e => e is Structure s && MatchesRequirements(s) && s.Prefab.Identifier == identifier);
173  }
174 
175  private void TagStructuresBySpecialTag(Identifier tag)
176  {
177  AddTargetPredicate(
178  Tag,
179  ScriptedEvent.TargetPredicate.EntityType.Structure,
180  e => e is Structure s && MatchesRequirements(s) && s.SpecialTag.ToIdentifier() == tag);
181  }
182 
183  private void TagItemsByIdentifier(Identifier identifier)
184  {
185  AddTargetPredicate(
186  Tag,
187  ScriptedEvent.TargetPredicate.EntityType.Item,
188  e => e is Item it && it.Prefab.Identifier == identifier && IsValidItem(it));
189  }
190 
191  private void TagItemsByTag(Identifier tag)
192  {
193  AddTargetPredicate(
194  Tag,
195  ScriptedEvent.TargetPredicate.EntityType.Item,
196  e => e is Item it && it.HasTag(tag) && IsValidItem(it));
197  }
198 
199  private void TagHulls()
200  {
201  AddTargetPredicate(
202  Tag,
203  ScriptedEvent.TargetPredicate.EntityType.Hull,
204  e => e is Hull h && MatchesRequirements(h));
205  }
206 
207  private void TagHullsByName(Identifier name)
208  {
209  AddTargetPredicate(
210  Tag,
211  ScriptedEvent.TargetPredicate.EntityType.Hull,
212  e => e is Hull h && MatchesRequirements(h) && h.RoomName.Contains(name.Value, StringComparison.OrdinalIgnoreCase));
213  }
214 
215  private void TagSubmarinesByType(Identifier type)
216  {
217  AddTargetPredicate(
218  Tag,
219  ScriptedEvent.TargetPredicate.EntityType.Submarine,
220  e => e is Submarine s && MatchesRequirements(s) && (type.IsEmpty || type == s.Info?.Type.ToIdentifier()));
221  }
222 
223  private bool IsValidItem(Item it)
224  {
225  return
226  !it.IsLayerHidden && /*items in hidden layers are treated as if they didn't exist, regardless if hidden items should be allowed*/
227  (!it.HiddenInGame || AllowHiddenItems) &&
228  ModuleTagMatches(it) &&
229  //if the item has just spawned, it may be in a hull but not moved into the coordinate space of the hull yet
230  //= it.Submarine still null
231  SubmarineTypeMatches(it.Submarine ?? it.CurrentHull?.Submarine ?? it.ParentInventory?.Owner?.Submarine);
232  }
233 
234  private bool MatchesRequirements(Entity e)
235  {
236  return ModuleTagMatches(e) && SubmarineTypeMatches(e.Submarine);
237  }
238 
239  private bool ModuleTagMatches(Entity e)
240  {
241  if (RequiredModuleTag.IsEmpty) { return true; }
242  if (e?.Submarine == null) { return false; }
243 
244  Hull hull;
245  if (e is Character character)
246  {
247  hull = character.CurrentHull;
248  }
249  else if (e is Item item)
250  {
251  hull = item.CurrentHull;
252  }
253  else if (e is WayPoint wp)
254  {
255  hull = wp.CurrentHull;
256  }
257  else if (e is Hull h)
258  {
259  hull = h;
260  }
261  else
262  {
263  DebugConsole.AddWarning($"Potential error in event \"{ParentEvent.Prefab.Identifier}\": {nameof(TagAction)} cannot check the module tags of an entity of the type {e.GetType()}.");
264  return false;
265  }
266 
267  return hull != null && hull.OutpostModuleTags.Contains(RequiredModuleTag);
268  }
269 
270 
271  private bool SubmarineTypeMatches(Submarine sub)
272  {
273  return SubmarineTypeMatches(sub, SubmarineType);
274  }
275 
276  public static bool SubmarineTypeMatches(Submarine sub, SubType submarineType)
277  {
278  if (submarineType == SubType.Any) { return true; }
279  if (sub == null) { return false; }
280  switch (sub.Info.Type)
281  {
282  case Barotrauma.SubmarineType.Player:
283  return submarineType.HasFlag(SubType.Player) && sub != GameMain.NetworkMember?.RespawnManager?.RespawnShuttle;
284  case Barotrauma.SubmarineType.Outpost:
285  case Barotrauma.SubmarineType.OutpostModule:
286  return submarineType.HasFlag(SubType.Outpost);
287  case Barotrauma.SubmarineType.Wreck:
288  return submarineType.HasFlag(SubType.Wreck);
289  case Barotrauma.SubmarineType.BeaconStation:
290  return submarineType.HasFlag(SubType.BeaconStation);
291  default:
292  return false;
293  }
294  }
295 
296  private void AddTargetPredicate(Identifier tag, ScriptedEvent.TargetPredicate.EntityType entityType, Predicate<Entity> predicate)
297  {
298  if (ChoosePercentage > 0.0f)
299  {
300  TagPercentage(tag, Entity.GetEntities().Where(e => predicate(e)));
301  }
302  else if (ChooseRandom)
303  {
304  TagRandom(tag, Entity.GetEntities().Where(e => predicate(e)));
305  }
306  else
307  {
308  ParentEvent.AddTargetPredicate(tag, entityType, predicate);
309  mustRecheckTargets = true;
310  }
311  }
312 
313  private void AddTarget(Identifier tag, IEnumerable<Entity> entities)
314  {
315  if (entities.None())
316  {
317  cantFindTargets = true;
318  return;
319  }
320  if (ChoosePercentage > 0.0f)
321  {
322  TagPercentage(tag, entities);
323  }
324  else if (ChooseRandom)
325  {
326  TagRandom(tag, entities);
327  }
328  else
329  {
330  foreach (var entity in entities)
331  {
332  ParentEvent.AddTarget(tag, entity);
333  }
334  }
335  }
336 
337  private List<Entity> tempEntities;
338  private void TagPercentage(Identifier tag, IEnumerable<Entity> entities)
339  {
340  if (entities.None())
341  {
342  cantFindTargets = true;
343  return;
344  }
345 
346  int amountToChoose = (int)Math.Ceiling(entities.Count() * (ChoosePercentage / 100.0f));
347 
348  tempEntities ??= new List<Entity>();
349  tempEntities.Clear();
350  for (int i = 0; i < amountToChoose; i++)
351  {
352  var entity = entities.GetRandomUnsynced();
353  tempEntities.Remove(entity);
354  ParentEvent.AddTarget(tag, entity);
355  }
356  }
357 
358  private void TagRandom(Identifier tag, IEnumerable<Entity> entities)
359  {
360  if (entities.None())
361  {
362  cantFindTargets = true;
363  return;
364  }
365  ParentEvent.AddTarget(tag, entities.GetRandomUnsynced());
366  }
367 
368  private readonly ImmutableDictionary<Identifier, Action<Identifier>> Taggers;
369 
370  public override void Update(float deltaTime)
371  {
372  if (isFinished || cantFindTargets) { return; }
373 
374  if (!taggingDone)
375  {
376  cantFindTargets = false;
377  string[] criteriaSplit = Criteria.Split(';');
378  foreach (string entry in criteriaSplit)
379  {
380  string[] kvp = entry.Split(':');
381  Identifier key = kvp[0].Trim().ToIdentifier();
382  Identifier value = kvp.Length > 1 ? kvp[1].Trim().ToIdentifier() : Identifier.Empty;
383  if (Taggers.TryGetValue(key, out Action<Identifier> tagger))
384  {
385  tagger(value);
386  }
387  else
388  {
389  string errorMessage = $"Error in TagAction (event \"{ParentEvent.Prefab.Identifier}\") - unrecognized target criteria \"{key}\".";
390  DebugConsole.ThrowError(errorMessage,
391  contentPackage: ParentEvent.Prefab?.ContentPackage);
392  GameAnalyticsManager.AddErrorEventOnce($"TagAction.Update:InvalidCriteria_{ParentEvent.Prefab.Identifier}_{key}", GameAnalyticsManager.ErrorSeverity.Error, errorMessage);
393  }
394  }
395  taggingDone = true;
396  }
397 
399  {
400  isFinished = true;
401  }
402  else
403  {
404  if (mustRecheckTargets)
405  {
406  isFinished = ParentEvent.GetTargets(Tag).Any();
407  }
408  else
409  {
410  isFinished = !cantFindTargets;
411  }
412  }
413  }
414 
415  public override string ToDebugString()
416  {
417  return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(TagAction)} -> (Criteria: {Criteria.ColorizeObject()}, Tag: {Tag.ColorizeObject()}, Sub: {SubmarineType.ColorizeObject()})";
418  }
419  }
420 }
static IReadOnlyCollection< Entity > GetEntities()
Definition: Entity.cs:24
readonly ScriptedEvent ParentEvent
Definition: EventAction.cs:102
EventPrefab Prefab
Definition: Event.cs:16
static NetworkMember NetworkMember
Definition: GameMain.cs:190
ContentPackage? ContentPackage
Definition: Prefab.cs:37
sealed record TargetPredicate(TargetPredicate.EntityType Type, Predicate< Entity > Predicate)
void AddTargetPredicate(Identifier tag, TargetPredicate.EntityType entityType, Predicate< Entity > predicate)
void AddTarget(Identifier tag, Entity target)
IEnumerable< Entity > GetTargets(Identifier tag)
Tags a specific entity. Tags are used by other actions to refer to specific entities....
Definition: TagAction.cs:13
bool IgnoreIncapacitatedCharacters
Definition: TagAction.cs:29
static bool SubmarineTypeMatches(Submarine sub, SubType submarineType)
Definition: TagAction.cs:276
override bool IsFinished(ref string goTo)
Has the action finished.
Definition: TagAction.cs:84
TagAction(ScriptedEvent parentEvent, ContentXElement element)
Definition: TagAction.cs:59
bool ContinueIfNoTargetsFound
Definition: TagAction.cs:38
Identifier RequiredModuleTag
Definition: TagAction.cs:26
Identifier Tag
Definition: TagAction.cs:20
override string ToDebugString()
Rich test to display in debugdraw
Definition: TagAction.cs:415
override void Reset()
Definition: TagAction.cs:88
SubType SubmarineType
Definition: TagAction.cs:23
override void Update(float deltaTime)
Definition: TagAction.cs:370