Client LuaCsForBarotrauma
ObjectiveManager.cs
2 using Barotrauma.IO;
4 using Microsoft.Xna.Framework;
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 
9 namespace Barotrauma;
10 
11 static class ObjectiveManager
12 {
13  public class Segment
14  {
15  public readonly record struct Text(
16  Identifier Tag,
17  int Width = DefaultWidth,
18  int Height = DefaultHeight,
19  Anchor Anchor = Anchor.Center);
20 
21  public readonly record struct Video(
22  string FullPath,
23  Identifier TextTag,
24  int Width = DefaultWidth,
25  int Height = DefaultHeight)
26  {
27  public string FileName => Path.GetFileName(FullPath.CleanUpPath());
28  public string ContentPath => Path.GetDirectoryName(FullPath.CleanUpPath());
29  }
30 
31  private const int DefaultWidth = 450;
32  private const int DefaultHeight = 80;
33 
34  public GUIImage ObjectiveStateIndicator;
35  public GUIButton ObjectiveButton;
36  public GUITextBlock LinkedTextBlock;
37  public LocalizedString ObjectiveText;
38 
39  public readonly Identifier Id;
40  public readonly Text TextContent;
41  public readonly Video VideoContent;
42  public readonly AutoPlayVideo AutoPlayVideo;
43 
44  public Action OnClickObjective;
45 
46  public bool IsCompleted { get; set; }
47 
48  public bool CanBeCompleted { get; set; }
49 
50  public Identifier ParentId { get; set; }
51 
52  public SegmentType SegmentType { get; private set; }
53 
54  public static Segment CreateInfoBoxSegment(Identifier id, Identifier objectiveTextTag, AutoPlayVideo autoPlayVideo, Text textContent = default, Video videoContent = default)
55  {
56  return new Segment(id, objectiveTextTag, autoPlayVideo, textContent, videoContent);
57  }
58 
59  public static Segment CreateMessageBoxSegment(Identifier id, Identifier objectiveTextTag, Action onClickObjective)
60  {
61  return new Segment(id, objectiveTextTag, onClickObjective);
62  }
63 
64  public static Segment CreateObjectiveSegment(Identifier id, Identifier objectiveTextTag)
65  {
66  return new Segment(id, objectiveTextTag);
67  }
68 
69  private Segment(Identifier id, Identifier objectiveTextTag, AutoPlayVideo autoPlayVideo, Text textContent = default, Video videoContent = default)
70  {
71  Id = id;
72  ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag).Fallback(objectiveTextTag.Value));
73  AutoPlayVideo = autoPlayVideo;
74  TextContent = textContent;
75  VideoContent = videoContent;
76  SegmentType = SegmentType.InfoBox;
77  }
78 
79  private Segment(Identifier id, Identifier objectiveTextTag, Action onClickObjective)
80  {
81  Id = id;
82  ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag).Fallback(objectiveTextTag.Value));
83  OnClickObjective = onClickObjective;
84  SegmentType = SegmentType.MessageBox;
85  }
86 
87  private Segment(Identifier id, Identifier objectiveTextTag)
88  {
89  Id = id;
90  ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag).Fallback(objectiveTextTag.Value));
91  SegmentType = SegmentType.Objective;
92  }
93 
94  public void ConnectMessageBox(Segment messageBoxSegment)
95  {
96  SegmentType = SegmentType.MessageBox;
97  OnClickObjective = messageBoxSegment.OnClickObjective;
98  }
99  }
100 
101  private readonly record struct ScreenSettings(
102  Point ScreenResolution = default,
103  float UiScale = default,
104  WindowMode WindowMode = default)
105  {
106  public bool HaveChanged() =>
107  GameMain.GraphicsWidth != ScreenResolution.X ||
108  GameMain.GraphicsHeight != ScreenResolution.Y ||
109  GUI.Scale != UiScale ||
110  GameSettings.CurrentConfig.Graphics.DisplayMode != WindowMode;
111  };
112 
113  private const float ObjectiveComponentAnimationTime = 1.5f;
114 
115  public static bool ContentRunning { get; private set; }
116 
117  public static VideoPlayer VideoPlayer { get; } = new VideoPlayer();
118 
119  private static Segment ActiveContentSegment { get; set; }
120 
121  private readonly static List<Segment> activeObjectives = new List<Segment>();
122  private static GUIComponent infoBox;
123  private static Action infoBoxClosedCallback;
124  private static ScreenSettings screenSettings;
125  private static GUILayoutGroup objectiveGroup;
126  private static LocalizedString objectiveTextTranslated;
127 
128  public static void AddToGUIUpdateList()
129  {
130  if (screenSettings.HaveChanged())
131  {
132  CreateObjectiveFrame();
133  }
134  if (activeObjectives.Count > 0 && GameMain.GameSession?.Campaign is not { ShowCampaignUI: true })
135  {
136  objectiveGroup?.AddToGUIUpdateList(order: -1);
137  }
138  infoBox?.AddToGUIUpdateList(order: 100);
139  VideoPlayer.AddToGUIUpdateList(order: 100);
140  }
141 
142  public static bool IsSegmentActive(Identifier segmentId)
143  {
144  return activeObjectives.Any(o => o.Id == segmentId);
145  }
146 
147  public static void TriggerSegment(Segment segment, bool connectObjective = false)
148  {
149  if (segment.SegmentType != SegmentType.InfoBox)
150  {
151  activeObjectives.Add(segment);
152  AddToObjectiveList(segment, connectObjective);
153  return;
154  }
155 
156  Inventory.DraggingItems.Clear();
157  ContentRunning = true;
158  ActiveContentSegment = segment;
159 
160  var title = TextManager.Get(segment.Id);
161  LocalizedString text = TextManager.GetFormatted(segment.TextContent.Tag).Fallback(segment.TextContent.Tag.Value);
162  text = TextManager.ParseInputTypes(text);
163 
164  switch (segment.AutoPlayVideo)
165  {
166  case AutoPlayVideo.Yes:
167  infoBox = CreateInfoFrame(
168  title,
169  text,
170  segment.TextContent.Width,
171  segment.TextContent.Height,
172  segment.TextContent.Anchor,
173  hasButton: true,
174  onInfoBoxClosed: LoadActiveContentVideo);
175  break;
176  case AutoPlayVideo.No:
177  infoBox = CreateInfoFrame(
178  title,
179  text,
180  segment.TextContent.Width,
181  segment.TextContent.Height,
182  segment.TextContent.Anchor,
183  hasButton: true,
184  onInfoBoxClosed: StopCurrentContentSegment,
185  onVideoButtonClicked: LoadActiveContentVideo);
186  break;
187  }
188  }
189 
190  public static void CompleteSegment(Identifier segmentId)
191  {
192  if (GetActiveObjective(segmentId) is not Segment segment || !segment.CanBeCompleted || segment.IsCompleted)
193  {
194  return;
195  }
196  CompleteSegment(segment, failed: false);
197  }
198 
199  public static void FailSegment(Identifier segmentId)
200  {
201  if (GetActiveObjective(segmentId) is not Segment segment)
202  {
203  return;
204  }
205  CompleteSegment(segment, failed: true);
206  }
207 
208  private static void CompleteSegment(Segment segment, bool failed = false)
209  {
210  if (failed)
211  {
212  if (!MarkSegmentFailed(segment)) { return; }
213  }
214  else
215  {
216  if (!MarkSegmentCompleted(segment)) { return; }
217  }
218  if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode)
219  {
220  GameAnalyticsManager.AddDesignEvent($"Tutorial:{tutorialMode.Tutorial?.Identifier}:{segment.Id}:{(failed ? "Failed" : "Completed")}");
221  }
222  }
223 
224  private static bool MarkSegmentCompleted(Segment segment, bool flash = true)
225  {
226  return MarkSegment(segment, "ObjectiveIndicatorCompleted", flash, flashColor: GUIStyle.Green);
227  }
228 
229  private static bool MarkSegmentFailed(Segment segment, bool flash = true)
230  {
231  return MarkSegment(segment, "MissionFailedIcon", flash, flashColor: GUIStyle.Red);
232  }
233 
234  private static bool MarkSegment(Segment segment, string iconStyleName, bool flash, Color flashColor)
235  {
236  segment.IsCompleted = true;
237  if (GUIStyle.GetComponentStyle(iconStyleName) is GUIComponentStyle style)
238  {
239  if (segment.ObjectiveStateIndicator.Style == style)
240  {
241  return false;
242  }
243  segment.ObjectiveStateIndicator.ApplyStyle(style);
244  }
245  if (flash)
246  {
247  segment.ObjectiveStateIndicator.Parent.Flash(color: flashColor, flashDuration: 0.35f, useRectangleFlash: true);
248  }
249  segment.ObjectiveButton.OnClicked = null;
250  segment.ObjectiveButton.CanBeFocused = false;
251  return true;
252  }
253 
254  public static void RemoveSegment(Identifier segmentId)
255  {
256  if (GetActiveObjective(segmentId) is not Segment segment)
257  {
258  return;
259  }
260  segment.ObjectiveStateIndicator.FadeOut(ObjectiveComponentAnimationTime, false);
261  segment.LinkedTextBlock.FadeOut(ObjectiveComponentAnimationTime, false);
262  var parent = segment.LinkedTextBlock.Parent;
263  parent.FadeOut(ObjectiveComponentAnimationTime, true, onRemove: () =>
264  {
265  activeObjectives.Remove(segment);
266  objectiveGroup?.Recalculate();
267  });
268  parent.RectTransform.MoveOverTime(GetObjectiveHiddenPosition(parent.RectTransform), ObjectiveComponentAnimationTime);
269  segment.ObjectiveButton.OnClicked = null;
270  segment.ObjectiveButton.CanBeFocused = false;
271  }
272 
273  public static void CloseActiveContentGUI()
274  {
275  if (VideoPlayer.IsPlaying)
276  {
277  VideoPlayer.Stop();
278  }
279  else if (infoBox != null)
280  {
281  CloseInfoFrame();
282  }
283  }
284 
285  public static void ClearContent()
286  {
287  ContentRunning = false;
288  infoBox = null;
289  }
290 
291  public static void ResetUI()
292  {
293  ContentRunning = false;
294  infoBox = null;
295  VideoPlayer.Remove();
296  }
297 
298  #region Objectives
299  private static Segment GetActiveObjective(Identifier id) => activeObjectives.FirstOrDefault(s => s.Id == id);
300 
301  public static void ResetObjectives()
302  {
303  activeObjectives.Clear();
304  ActiveContentSegment = null;
305  CreateObjectiveFrame();
306  }
307 
311  private static void CreateObjectiveFrame()
312  {
313  var objectiveListFrame = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.TutorialObjectiveListArea, GUI.Canvas), style: null)
314  {
315  CanBeFocused = false
316  };
317  objectiveGroup = new GUILayoutGroup(new RectTransform(Vector2.One, objectiveListFrame.RectTransform))
318  {
319  AbsoluteSpacing = (int)GUIStyle.Font.LineHeight
320  };
321  for (int i = 0; i < activeObjectives.Count; i++)
322  {
323  AddToObjectiveList(activeObjectives[i], useExistingIndex: true);
324  }
325  screenSettings = new ScreenSettings(new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight), GUI.Scale, GameSettings.CurrentConfig.Graphics.DisplayMode);
326  }
327 
331  private static void StopCurrentContentSegment()
332  {
333  if (!ActiveContentSegment.ObjectiveText.IsNullOrEmpty())
334  {
335  activeObjectives.Add(ActiveContentSegment);
336  AddToObjectiveList(ActiveContentSegment);
337  }
338  ContentRunning = false;
339  ActiveContentSegment = null;
340  }
341 
345  private static void AddToObjectiveList(Segment segment, bool connectExisting = false, bool useExistingIndex = false)
346  {
347  if (connectExisting)
348  {
349  if (activeObjectives.Find(o => o.Id == segment.Id) is { } existingSegment)
350  {
351  existingSegment.ConnectMessageBox(segment);
352  SetButtonBehavior(existingSegment);
353  }
354  return;
355  }
356 
357  var frameRt = new RectTransform(new Vector2(1.0f, 0.1f), objectiveGroup.RectTransform)
358  {
359  MinSize = new Point(0, objectiveGroup.AbsoluteSpacing)
360  };
361  Segment parentSegment = activeObjectives.FirstOrDefault(s => s.Id == segment.ParentId);
362  if (parentSegment is not null)
363  {
364  // Add this child as the last child in case there are other existing children already
365  int childIndex = useExistingIndex ? activeObjectives.IndexOf(segment) :
366  activeObjectives.IndexOf(parentSegment) + activeObjectives.Count(s => s.ParentId == segment.ParentId);
367  if (objectiveGroup.RectTransform.GetChildIndex(frameRt) != childIndex)
368  {
369  if (childIndex < 0 || childIndex >= frameRt.Parent.CountChildren)
370  {
371  DebugConsole.ThrowError(
372  $"Error in {nameof(ObjectiveManager.AddToObjectiveList)}. " +
373  $"Failed to reposition an objective in the list. Text \"{segment.ObjectiveText}\", parentId: {segment.ParentId}, childIndex: {childIndex}");
374  }
375  else
376  {
377  frameRt.RepositionChildInHierarchy(childIndex);
378  activeObjectives.Remove(segment);
379  activeObjectives.Insert(childIndex, segment);
380  }
381  }
382  }
383  frameRt.AbsoluteOffset = GetObjectiveHiddenPosition();
384 
385  var frame = new GUIFrame(frameRt, style: null)
386  {
387  CanBeFocused = true
388  };
389 
390  objectiveGroup.Recalculate();
391 
392  int textWidth = parentSegment is null ? frameRt.Rect.Width - objectiveGroup.AbsoluteSpacing
393  : frameRt.Rect.Width - 2 * objectiveGroup.AbsoluteSpacing;
394  segment.LinkedTextBlock = new GUITextBlock(
395  new RectTransform(new Point(textWidth, 0), frame.RectTransform, anchor: Anchor.TopRight),
396  TextManager.ParseInputTypes(segment.ObjectiveText),
397  wrap: true);
398 
399  var size = new Point(segment.LinkedTextBlock.Rect.Width, segment.LinkedTextBlock.Rect.Height);
400  segment.LinkedTextBlock.RectTransform.NonScaledSize = size;
401  segment.LinkedTextBlock.RectTransform.MinSize = size;
402  segment.LinkedTextBlock.RectTransform.MaxSize = size;
403  segment.LinkedTextBlock.RectTransform.IsFixedSize = true;
404  frame.RectTransform.Resize(new Point(frame.Rect.Width, segment.LinkedTextBlock.RectTransform.Rect.Height), resizeChildren: false);
405  frame.RectTransform.IsFixedSize = true;
406 
407  var indicatorRt = new RectTransform(new Point(objectiveGroup.AbsoluteSpacing), frame.RectTransform, isFixedSize: true);
408  if (parentSegment is not null)
409  {
410  indicatorRt.AbsoluteOffset = new Point(objectiveGroup.AbsoluteSpacing, 0);
411  }
412  segment.ObjectiveStateIndicator = new GUIImage(indicatorRt, "ObjectiveIndicatorIncomplete");
413 
414  SetTransparent(segment.LinkedTextBlock);
415 
416  objectiveTextTranslated ??= TextManager.Get("Tutorial.Objective");
417  segment.ObjectiveButton = new GUIButton(new RectTransform(Vector2.One, segment.LinkedTextBlock.RectTransform, Anchor.TopLeft, Pivot.TopLeft), style: null)
418  {
419  ToolTip = objectiveTextTranslated
420  };
421  SetButtonBehavior(segment);
422  SetTransparent(segment.ObjectiveButton);
423 
424  frameRt.MoveOverTime(new Point(0, frameRt.AbsoluteOffset.Y), ObjectiveComponentAnimationTime, onDoneMoving: () => objectiveGroup?.Recalculate());
425 
426  // Check if the objective has already been completed in the campaign
427  if (!segment.IsCompleted && GameMain.GameSession?.Campaign?.CampaignMetadata is CampaignMetadata data && data.GetBoolean(segment.Id))
428  {
429  MarkSegmentCompleted(segment, flash: false);
430  }
431 
432  static void SetTransparent(GUIComponent component) => component.Color = component.HoverColor = component.PressedColor = component.SelectedColor = Color.Transparent;
433 
434  void SetButtonBehavior(Segment segment)
435  {
436  segment.ObjectiveButton.CanBeFocused = segment.SegmentType != SegmentType.Objective;
437  segment.ObjectiveButton.OnClicked = (GUIButton btn, object userdata) =>
438  {
439  if (segment.SegmentType == SegmentType.InfoBox)
440  {
441  if (segment.AutoPlayVideo == AutoPlayVideo.Yes)
442  {
443  ReplaySegmentVideo(segment);
444  }
445  else
446  {
447  ShowSegmentText(segment);
448  }
449  }
450  else if (segment.SegmentType == SegmentType.MessageBox)
451  {
452  segment.OnClickObjective?.Invoke();
453  }
454  return true;
455  };
456  }
457  }
458 
459  private static void ReplaySegmentVideo(Segment segment)
460  {
461  if (ContentRunning) { return; }
462  Inventory.DraggingItems.Clear();
463  ContentRunning = true;
464  LoadVideo(segment);
465  }
466 
467  private static void ShowSegmentText(Segment segment)
468  {
469  if (ContentRunning) { return; }
470  Inventory.DraggingItems.Clear();
471  ContentRunning = true;
472  ActiveContentSegment = segment;
473  infoBox = CreateInfoFrame(
474  TextManager.Get(segment.Id).Fallback(segment.Id.Value),
475  TextManager.Get(segment.TextContent.Tag).Fallback(segment.TextContent.Tag.Value),
476  segment.TextContent.Width,
477  segment.TextContent.Height,
478  segment.TextContent.Anchor,
479  hasButton: true,
480  onInfoBoxClosed: () => ContentRunning = false,
481  onVideoButtonClicked: () => LoadVideo(segment));
482  }
483 
484  private static Point GetObjectiveHiddenPosition(RectTransform rt = null)
485  {
486  return new Point(GameMain.GraphicsWidth - objectiveGroup.Rect.X, rt?.AbsoluteOffset.Y ?? 0);
487  }
488 
489  public static Segment GetObjective(Identifier identifier)
490  {
491  return activeObjectives.FirstOrDefault(o => o.Id == identifier);
492  }
493 
494  public static bool AllActiveObjectivesCompleted()
495  {
496  return activeObjectives.None() || activeObjectives.All(o => !o.CanBeCompleted || o.IsCompleted);
497  }
498 
499  public static bool AnyObjectives => activeObjectives.Any();
500 
501  #endregion
502 
503  #region InfoFrame
504 
505  private static void CloseInfoFrame() => CloseInfoFrame(null, null);
506 
507  private static bool CloseInfoFrame(GUIButton button, object userData)
508  {
509  infoBox = null;
510  infoBoxClosedCallback?.Invoke();
511  return true;
512  }
513 
515  // Creates and displays a tutorial info box
517  private static GUIComponent CreateInfoFrame(LocalizedString title, LocalizedString text, int width = 300, int height = 80, Anchor anchor = Anchor.TopRight, bool hasButton = false, Action onInfoBoxClosed = null, Action onVideoButtonClicked = null)
518  {
519  if (hasButton)
520  {
521  height += 60;
522  }
523 
524  width = (int)(width * GUI.Scale);
525  height = (int)(height * GUI.Scale);
526 
527  LocalizedString wrappedText = ToolBox.WrapText(text, width, GUIStyle.Font);
528  height += (int)GUIStyle.Font.MeasureString(wrappedText).Y;
529 
530  if (title.Length > 0)
531  {
532  height += (int)GUIStyle.Font.MeasureString(title).Y + (int)(150 * GUI.Scale);
533  }
534 
535  var background = new GUIFrame(new RectTransform(new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight), GUI.Canvas, Anchor.Center), style: "GUIBackgroundBlocker");
536 
537  var infoBlock = new GUIFrame(new RectTransform(new Point(width, height), background.RectTransform, anchor));
538  infoBlock.Flash(GUIStyle.Green);
539 
540  var infoContent = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), infoBlock.RectTransform, Anchor.Center))
541  {
542  Stretch = true,
543  AbsoluteSpacing = 5
544  };
545 
546  if (title.Length > 0)
547  {
548  var titleBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform),
549  title, font: GUIStyle.LargeFont, textAlignment: Alignment.Center, textColor: new Color(253, 174, 0));
550  titleBlock.RectTransform.IsFixedSize = true;
551  }
552 
553  text = RichString.Rich(text);
554  GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), text, wrap: true);
555 
556  textBlock.RectTransform.IsFixedSize = true;
557  infoBoxClosedCallback = onInfoBoxClosed;
558 
559  if (hasButton)
560  {
561  var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), infoContent.RectTransform), isHorizontal: true)
562  {
563  RelativeSpacing = 0.1f
564  };
565  buttonContainer.RectTransform.IsFixedSize = true;
566 
567  if (onVideoButtonClicked != null)
568  {
569  buttonContainer.Stretch = true;
570  var videoButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonContainer.RectTransform),
571  TextManager.Get("Video"), style: "GUIButtonLarge")
572  {
573  OnClicked = (GUIButton button, object obj) =>
574  {
575  onVideoButtonClicked();
576  return true;
577  }
578  };
579  }
580  else
581  {
582  buttonContainer.Stretch = false;
583  buttonContainer.ChildAnchor = Anchor.Center;
584  }
585 
586  var okButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonContainer.RectTransform),
587  TextManager.Get("OK"), style: "GUIButtonLarge")
588  {
589  OnClicked = CloseInfoFrame
590  };
591  }
592 
593  infoBlock.RectTransform.NonScaledSize = new Point(infoBlock.Rect.Width, (int)(infoContent.Children.Sum(c => c.Rect.Height + infoContent.AbsoluteSpacing) / infoContent.RectTransform.RelativeSize.Y));
594 
595  SoundPlayer.PlayUISound(GUISoundType.UIMessage);
596 
597  return background;
598  }
599 
600  #endregion
601 
602  #region Video
603 
604  private static void LoadVideo(Segment segment)
605  {
606  if (segment.AutoPlayVideo == AutoPlayVideo.Yes)
607  {
608  VideoPlayer.LoadContent(
609  contentPath: segment.VideoContent.ContentPath,
610  videoSettings: new VideoPlayer.VideoSettings(segment.VideoContent.FileName),
611  textSettings: new VideoPlayer.TextSettings(segment.VideoContent.TextTag, segment.VideoContent.Width),
612  contentId: segment.Id,
613  startPlayback: true,
614  objective: segment.ObjectiveText,
615  onStop: StopCurrentContentSegment);
616  }
617  else
618  {
619  VideoPlayer.LoadContent(
620  contentPath: segment.VideoContent.ContentPath,
621  videoSettings: new VideoPlayer.VideoSettings(segment.VideoContent.FileName),
622  textSettings: null,
623  contentId: segment.Id,
624  startPlayback: true,
625  objective: string.Empty);
626  }
627  }
628 
629  private static void LoadActiveContentVideo() => LoadVideo(ActiveContentSegment);
630 
631  #endregion
632 }
LocalizedString ObjectiveText
void ConnectMessageBox(Segment messageBoxSegment)
static Segment CreateInfoBoxSegment(Identifier id, Identifier objectiveTextTag, AutoPlayVideo autoPlayVideo, Text textContent=default, Video videoContent=default)
readonly record struct Video(string FullPath, Identifier TextTag, int Width=DefaultWidth, int Height=DefaultHeight)
readonly record struct Text(Identifier Tag, int Width=DefaultWidth, int Height=DefaultHeight, Anchor Anchor=Anchor.Center)
readonly Identifier Id
static Segment CreateMessageBoxSegment(Identifier id, Identifier objectiveTextTag, Action onClickObjective)
static Segment CreateObjectiveSegment(Identifier id, Identifier objectiveTextTag)
readonly AutoPlayVideo AutoPlayVideo
GUISoundType
Definition: GUI.cs:21