Client LuaCsForBarotrauma
TriggerAction.cs
1 using Microsoft.Xna.Framework;
2 using System.Collections.Generic;
3 using System.Linq;
4 
5 namespace Barotrauma
6 {
11  {
12  public enum TriggerType
13  {
14  Inside,
15  Outside
16  }
17 
18  [Serialize("", IsPropertySaveable.Yes, description: "Tag of the first entity that will be used for trigger checks.")]
19  public Identifier Target1Tag { get; set; }
20 
21  [Serialize("", IsPropertySaveable.Yes, description: "Tag of the second entity that will be used for trigger checks.")]
22  public Identifier Target2Tag { get; set; }
23 
24  [Serialize("", IsPropertySaveable.Yes, description: "If set, the first target has to be within an outpost module of this type.")]
25  public Identifier TargetModuleType { get; set; }
26 
27  [Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the first entity when the trigger check succeeds.")]
28  public Identifier ApplyToTarget1 { get; set; }
29 
30  [Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the second entity when the trigger check succeeds.")]
31  public Identifier ApplyToTarget2 { get; set; }
32 
33  [Serialize(TriggerType.Inside, IsPropertySaveable.Yes, description: "Determines if the targets must be inside or outside of the radius.")]
34  public TriggerType Type { get; set; }
35 
36  [Serialize(0.0f, IsPropertySaveable.Yes, description: "Range to activate the trigger.")]
37  public float Radius { get; set; }
38 
39  [Serialize(true, IsPropertySaveable.Yes, description: "If true, characters who are being targeted by some enemy cannot trigger the action.")]
40  public bool DisableInCombat { get; set; }
41 
42  [Serialize(true, IsPropertySaveable.Yes, description: "If true, dead/unconscious characters cannot trigger the action.")]
43  public bool DisableIfTargetIncapacitated { get; set; }
44 
45  [Serialize(false, IsPropertySaveable.Yes, description: "If true, one target must interact with the other to trigger the action.")]
46  public bool WaitForInteraction { get; set; }
47 
48  [Serialize(false, IsPropertySaveable.Yes, description: "If true, the action can be triggered by interacting with any matching target (not just the 1st one).")]
49  public bool AllowMultipleTargets { get; set; }
50 
51  [Serialize(false, IsPropertySaveable.Yes, description: "If true and using multiple targets, all targets must be inside/outside the radius.")]
52  public bool CheckAllTargets { get; set; }
53 
54  [Serialize(false, IsPropertySaveable.Yes, description: "If true, interacting with the target will make the character select it.")]
55  public bool SelectOnTrigger { get; set; }
56 
57  private float distance;
58 
59  public TriggerAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
60 
61  private bool isFinished = false;
62  public override bool IsFinished(ref string goTo)
63  {
64  return isFinished;
65  }
66  public override void Reset()
67  {
68  ResetTargetIcons();
69  isRunning = false;
70  isFinished = false;
71  }
72 
73  public bool isRunning = false;
74 
75  private readonly List<Either<Character, Item>> npcsOrItems = new List<Either<Character, Item>>();
76 
77  private readonly List<(Entity e1, Entity e2)> triggerers = new List<(Entity e1, Entity e2)>();
78 
79  public override void Update(float deltaTime)
80  {
81  if (isFinished) { return; }
82 
83  isRunning = true;
84 
85  var targets1 = ParentEvent.GetTargets(Target1Tag);
86  if (!targets1.Any()) { return; }
87 
88  triggerers.Clear();
89  foreach (Entity e1 in targets1)
90  {
91  if (DisableInCombat && IsInCombat(e1))
92  {
93  if (CheckAllTargets)
94  {
95  return;
96  }
97  continue;
98  }
99  if (DisableIfTargetIncapacitated && e1 is Character character1 && (character1.IsDead || character1.IsIncapacitated))
100  {
101  if (CheckAllTargets)
102  {
103  return;
104  }
105  continue;
106  }
107  if (!TargetModuleType.IsEmpty)
108  {
109  if (!CheckAllTargets && CheckDistanceToHull(e1, out Hull hull))
110  {
111  Trigger(e1, hull);
112  return;
113  }
114  else if (CheckAllTargets)
115  {
116  if (CheckDistanceToHull(e1, out hull))
117  {
118  triggerers.Add((e1, hull));
119  }
120  else
121  {
122  return;
123  }
124  }
125  continue;
126  }
127 
128  var targets2 = ParentEvent.GetTargets(Target2Tag);
129 
130  foreach (Entity e2 in targets2)
131  {
132  if (e1 == e2)
133  {
134  continue;
135  }
136  if (DisableInCombat && IsInCombat(e2))
137  {
138  if (CheckAllTargets)
139  {
140  return;
141  }
142  continue;
143  }
144  if (DisableIfTargetIncapacitated && e2 is Character character2 && (character2.IsDead || character2.IsIncapacitated))
145  {
146  if (CheckAllTargets)
147  {
148  return;
149  }
150  continue;
151  }
152 
153  if (WaitForInteraction)
154  {
155  Character player = null;
156  Character npc = null;
157  Item item = null;
158  if (e1 is Character char1)
159  {
160  if (char1.IsBot)
161  {
162  npc ??= char1;
163  }
164  else
165  {
166  player = char1;
167  }
168  }
169  else
170  {
171  item ??= e1 as Item;
172  }
173  if (e2 is Character char2)
174  {
175  if (char2.IsBot)
176  {
177  npc ??= char2;
178  }
179  else
180  {
181  player = char2;
182  }
183  }
184  else
185  {
186  item ??= e2 as Item;
187  }
188 
189  if (player != null)
190  {
191  if (npc != null)
192  {
194  {
195  //if the NPC has a conversation available, don't assign the trigger until the conversation is done
196  continue;
197  }
198  else if (npc.CampaignInteractionType != CampaignMode.InteractionType.Examine)
199  {
200  if (!npcsOrItems.Any(n => n.TryGet(out Character npc2) && npc2 == npc))
201  {
202  npcsOrItems.Add(npc);
203  }
206 #if CLIENT
207  npc.SetCustomInteract(
208  (speaker, player) => { if (e1 == speaker) { Trigger(speaker, player); } else { Trigger(player, speaker); } },
209  TextManager.GetWithVariable("CampaignInteraction.Examine", "[key]", GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Use)));
210 #else
211  npc.SetCustomInteract(
212  (speaker, player) => { if (e1 == speaker) { Trigger(speaker, player); } else { Trigger(player, speaker); } },
213  TextManager.Get("CampaignInteraction.Talk"));
215 #endif
216  }
217  if (!AllowMultipleTargets) { return; }
218  }
219  else if (item != null)
220  {
221  if (!npcsOrItems.Any(n => n.TryGet(out Item item2) && item2 == item))
222  {
223  npcsOrItems.Add(item);
224  }
226  GameMain.NetworkMember?.ConnectedClients.Where(c => c.Character != null && targets2.Contains(c.Character)));
227  if (player.SelectedItem == item ||
228  player.SelectedSecondaryItem == item ||
229  (player.Inventory != null && player.Inventory.Contains(item)) ||
230  (player.FocusedItem == item && player.IsKeyHit(InputType.Use)))
231  {
232  Trigger(e1, e2);
233  return;
234  }
235  }
236  }
237  }
238  else
239  {
240  Vector2 pos1 = e1.WorldPosition;
241  Vector2 pos2 = e2.WorldPosition;
242  distance = Vector2.Distance(pos1, pos2);
243  if ((Type == TriggerType.Inside) == IsWithinRadius())
244  {
245  if (!CheckAllTargets)
246  {
247  Trigger(e1, e2);
248  return;
249  }
250  else
251  {
252  triggerers.Add((e1, e2));
253  }
254  }
255  else if (CheckAllTargets)
256  {
257  return;
258  }
259 
260  bool IsWithinRadius() =>
261  ((e1 is MapEntity m1) && Submarine.RectContains(m1.WorldRect, pos2)) ||
262  ((e2 is MapEntity m2) && Submarine.RectContains(m2.WorldRect, pos1)) ||
263  Vector2.DistanceSquared(pos1, pos2) < Radius * Radius;
264  }
265  }
266  }
267 
268  foreach (var (e1, e2) in triggerers)
269  {
270  Trigger(e1, e2);
271  }
272  }
273 
274  private void ResetTargetIcons()
275  {
276  foreach (var npcOrItem in npcsOrItems)
277  {
278  if (npcOrItem.TryGet(out Character npc))
279  {
280  npc.CampaignInteractionType = CampaignMode.InteractionType.None;
281  npc.SetCustomInteract(null, null);
282  npc.RequireConsciousnessForCustomInteract = true;
283  #if SERVER
285  #endif
286  }
287  else if (npcOrItem.TryGet(out Item item))
288  {
289  item.AssignCampaignInteractionType(CampaignMode.InteractionType.None);
290  }
291  }
292  }
293 
294  private bool CheckDistanceToHull(Entity e, out Hull hull)
295  {
296  hull = null;
297  if (Radius <= 0)
298  {
299  if (e is Character character && character.CurrentHull != null && character.CurrentHull.OutpostModuleTags.Contains(TargetModuleType))
300  {
301  hull = character.CurrentHull;
302  return Type == TriggerType.Inside;
303  }
304  else if (e is Item item && item.CurrentHull != null && item.CurrentHull.OutpostModuleTags.Contains(TargetModuleType))
305  {
306  hull = item.CurrentHull;
307  return Type == TriggerType.Inside;
308  }
309  return Type == TriggerType.Outside;
310  }
311  else
312  {
313  foreach (Hull potentialHull in Hull.HullList)
314  {
315  if (!potentialHull.OutpostModuleTags.Contains(TargetModuleType)) { continue; }
316  Rectangle hullRect = potentialHull.WorldRect;
317  hullRect.Inflate(Radius, Radius);
318  if (Submarine.RectContains(hullRect, e.WorldPosition))
319  {
320  hull = potentialHull;
321  return Type == TriggerType.Inside;
322  }
323  }
324  return Type == TriggerType.Outside;
325  }
326  }
327 
328  private static bool IsInCombat(Entity entity)
329  {
330  if (entity is not Character character) { return false; }
331  foreach (Character c in Character.CharacterList)
332  {
333  if (c.IsDead || c.Removed || c.IsIncapacitated || !c.Enabled) { continue; }
334  if (c.IsBot && c.AIController is HumanAIController humanAi)
335  {
336  if (humanAi.ObjectiveManager.CurrentObjective is AIObjectiveCombat combatObjective &&
337  combatObjective.Enemy == character)
338  {
339  return true;
340  }
341  }
342  else if (c.AIController is EnemyAIController enemyAI && (enemyAI.State == AIState.Aggressive || enemyAI.State == AIState.Attack))
343  {
344  if (enemyAI.SelectedAiTarget?.Entity == character || c.CurrentHull == character.CurrentHull)
345  {
346  return true;
347  }
348  }
349  }
350  return false;
351  }
352 
353  private void Trigger(Entity entity1, Entity entity2)
354  {
355  ResetTargetIcons();
356  if (!ApplyToTarget1.IsEmpty)
357  {
359  }
360  if (!ApplyToTarget2.IsEmpty)
361  {
363  }
364 
365  Character player = null;
366  Entity target = null;
367  if (entity1 is Character { IsPlayer: true })
368  {
369  player = entity1 as Character;
370  target = entity2;
371  }
372  else if (entity2 is Character { IsPlayer: true })
373  {
374  player = entity2 as Character;
375  target = entity1;
376  }
377  if (player != null && SelectOnTrigger)
378  {
379  if (target is Character targetCharacter)
380  {
381  player.SelectCharacter(targetCharacter);
382  }
383  else if (target is Item targetItem)
384  {
385  if (targetItem.IsSecondaryItem)
386  {
387  player.SelectedSecondaryItem = targetItem;
388  }
389  else
390  {
391  player.SelectedItem = targetItem;
392  }
393  }
394  }
395 
396  isRunning = false;
397  isFinished = true;
398  }
399 
400  public override string ToDebugString()
401  {
402  if (TargetModuleType.IsEmpty)
403  {
404  return
405  $"{ToolBox.GetDebugSymbol(isFinished, isRunning)} {nameof(TriggerAction)} -> (" +
407  $"Selected non-player target: {(npcsOrItems?.ToString() ?? "<null>").ColorizeObject()}, " :
408  $"Distance: {((int)distance).ColorizeObject()}, ") +
409  $"Radius: {Radius.ColorizeObject()}, " +
410  $"TargetTags: {Target1Tag.ColorizeObject()}, " +
411  $"{Target2Tag.ColorizeObject()})";
412  }
413  else
414  {
415  return $"{ToolBox.GetDebugSymbol(isFinished, isRunning)} {nameof(TriggerAction)} -> (TargetTags: {Target1Tag.ColorizeObject()}, {TargetModuleType.ColorizeObject()})";
416  }
417  }
418  }
419 }
void SetCustomInteract(Action< Character, Character > onCustomInteract, LocalizedString hudText)
Set an action that's invoked when another character interacts with this one.
Item????????? SelectedItem
The primary selected item. It can be any device that character interacts with. This excludes items li...
Item SelectedSecondaryItem
The secondary selected item. It's an item other than a device (see SelectedItem), e....
virtual Vector2 WorldPosition
Definition: Entity.cs:49
readonly ScriptedEvent ParentEvent
Definition: EventAction.cs:102
static NetworkMember NetworkMember
Definition: GameMain.cs:190
bool Contains(Item item)
Is the item contained in this inventory. Does not recursively check items inside items.
void AssignCampaignInteractionType(CampaignMode.InteractionType interactionType, IEnumerable< Client > targetClients=null)
void AddTarget(Identifier tag, Entity target)
IEnumerable< Entity > GetTargets(Identifier tag)
static bool RectContains(Rectangle rect, Vector2 pos, bool inclusive=false)
Waits for a player to trigger the action before continuing. Triggering can mean entering a specific t...
override bool IsFinished(ref string goTo)
Has the action finished.
override void Update(float deltaTime)
override string ToDebugString()
Rich test to display in debugdraw
override void Reset()
TriggerAction(ScriptedEvent parentEvent, ContentXElement element)