Client LuaCsForBarotrauma
Tutorial.cs
3 using System;
4 using System.Collections.Generic;
5 using System.Linq;
6 
8 {
9  enum AutoPlayVideo { Yes, No };
10 
12 
13  sealed class Tutorial
14  {
15  #region Constants
16 
17  private const SpawnType SpawnPointType = SpawnType.Human;
18  private const float FadeOutTime = 3f;
19  private const float WaitBeforeFade = 4f;
20 
21  #endregion
22 
23  #region Tutorial variables
24 
25  public readonly Identifier Identifier;
26  public LocalizedString DisplayName { get; }
27  public LocalizedString Description { get; }
28 
29 
30  private bool completed;
31  public bool Completed
32  {
33  get
34  {
35  return completed;
36  }
37  private set
38  {
39  if (completed == value) { return; }
40  completed = value;
41  if (value)
42  {
44  }
45  GameSettings.SaveCurrentConfig();
46  }
47  }
48 
49  public readonly TutorialPrefab TutorialPrefab;
50  private readonly EventPrefab eventPrefab;
51 
52  private CoroutineHandle tutorialCoroutine;
53  private CoroutineHandle completedCoroutine;
54 
55  private Character character;
56 
57  private string SubmarinePath => TutorialPrefab.SubmarinePath.Value;
58  private string StartOutpostPath => TutorialPrefab.OutpostPath.Value;
59  private string LevelSeed => TutorialPrefab.LevelSeed;
60  private string LevelParams => TutorialPrefab.LevelParams;
61 
62  private SubmarineInfo startOutpost = null;
63 
64  public readonly List<(Entity entity, Identifier iconStyle)> Icons = new List<(Entity entity, Identifier iconStyle)>();
65 
66  public bool Paused { get; private set; }
67 
68  #endregion
69 
70  #region Tutorial Controls
71 
72  public Tutorial(TutorialPrefab prefab)
73  {
74  Identifier = $"tutorial.{prefab.Identifier}".ToIdentifier();
75  DisplayName = TextManager.Get(Identifier);
76  Description = TextManager.Get($"tutorial.{prefab.Identifier}.description");
77  TutorialPrefab = prefab;
78  eventPrefab = EventSet.GetEventPrefab(prefab.EventIdentifier);
79  }
80 
81  private IEnumerable<CoroutineStatus> Loading()
82  {
83  SubmarineInfo subInfo = new SubmarineInfo(SubmarinePath);
84 
85  LevelGenerationParams.LevelParams.TryGet(LevelParams, out LevelGenerationParams generationParams);
86 
87  yield return CoroutineStatus.Running;
88 
89  GameMain.GameSession = new GameSession(subInfo, Option.None, GameModePreset.Tutorial, missionPrefabs: null);
91 
92  if (generationParams is not null)
93  {
94  Biome biome =
95  Biome.Prefabs.FirstOrDefault(b => generationParams.AllowedBiomeIdentifiers.Contains(b.Identifier)) ??
96  Biome.Prefabs.First();
97 
98  if (!string.IsNullOrEmpty(StartOutpostPath))
99  {
100  startOutpost = new SubmarineInfo(StartOutpostPath);
101  }
102 
103  LevelData tutorialLevel = new LevelData(LevelSeed, 0, 0, generationParams, biome);
104  GameMain.GameSession.StartRound(tutorialLevel, startOutpost: startOutpost);
105  }
106  else
107  {
108  GameMain.GameSession.StartRound(LevelSeed);
109  }
110 
111  GameMain.GameSession.EventManager.ClearEvents();
112  GameMain.GameSession.EventManager.Enabled = true;
113  GameMain.GameScreen.Select();
114 
115  if (Submarine.MainSub != null)
116  {
117  Submarine.MainSub.GodMode = true;
118  }
119  foreach (Structure wall in Structure.WallList)
120  {
121  if (wall.Submarine != null && wall.Submarine.Info.IsOutpost)
122  {
123  wall.Indestructible = true;
124  }
125  }
126 
127  var charInfo = TutorialPrefab.GetTutorialCharacterInfo();
128 
129  var wayPoint = WayPoint.GetRandom(SpawnPointType, charInfo.Job?.Prefab, Level.Loaded.StartOutpost);
130 
131  if (wayPoint == null)
132  {
133  DebugConsole.ThrowError("A waypoint with the spawntype \"" + SpawnPointType + "\" is required for the tutorial event");
134  yield return CoroutineStatus.Failure;
135  yield break;
136  }
137 
138  character = Character.Create(charInfo, wayPoint.WorldPosition, "", isRemotePlayer: false, hasAi: false);
139  character.TeamID = CharacterTeamType.Team1;
140  Character.Controlled = character;
141  character.GiveJobItems(isPvPMode: false, null);
142 
143  var idCard = character.Inventory.FindItemByTag("identitycard".ToIdentifier());
144  if (idCard == null)
145  {
146  DebugConsole.ThrowError("Item prefab \"ID Card\" not found!");
147  yield return CoroutineStatus.Failure;
148  yield break;
149  }
150  idCard.AddTag("com");
151  idCard.AddTag("eng");
152 
153  foreach (Item item in Item.ItemList)
154  {
155  Door door = item.GetComponent<Door>();
156  if (door != null)
157  {
158  door.CanBeWelded = false;
159  }
160  }
161 
162  tutorialCoroutine = CoroutineManager.StartCoroutine(UpdateState());
163 
164  GameMain.GameSession.CrewManager.AllowCharacterSwitch = TutorialPrefab.AllowCharacterSwitch;
165  GameMain.GameSession.CrewManager.AutoHideCrewList();
166 
167  if (Character.Controlled?.Inventory is CharacterInventory inventory)
168  {
169  foreach (Item item in inventory.AllItemsMod)
170  {
171  if (item.HasTag(TutorialPrefab.StartingItemTags)) { continue; }
172  item.Unequip(Character.Controlled);
173  Character.Controlled.Inventory.RemoveItem(item);
174  }
175  }
176 
177  yield return CoroutineStatus.Success;
178  }
179 
180  public void Start()
181  {
183 
184  GameMain.Instance.ShowLoading(Loading());
185  ObjectiveManager.ResetObjectives();
186 
187  // Setup doors: Clear all requirements, unless the door is setup as locked.
188  foreach (var item in Item.ItemList)
189  {
190  var door = item.GetComponent<Door>();
191  if (door != null)
192  {
193  if (door.RequiredItems.Values.None(ris => ris.None(ri => ri.Identifiers.None(i => i == "locked"))))
194  {
195  door.RequiredItems.Clear();
196  }
197  }
198  }
199  }
200 
201  public void Update()
202  {
203  if (character != null)
204  {
205  if (character.Oxygen < 1)
206  {
207  character.Oxygen = 1;
208  }
209  if (character.IsDead)
210  {
211  CoroutineManager.StartCoroutine(Dead());
212  }
213  else if (Character.Controlled == null)
214  {
215  if (tutorialCoroutine != null)
216  {
217  CoroutineManager.StopCoroutines(tutorialCoroutine);
218  }
219  if (completedCoroutine == null && !CoroutineManager.IsCoroutineRunning(completedCoroutine))
220  {
221  GUI.PreventPauseMenuToggle = false;
222  }
223  ObjectiveManager.ClearContent();
224  }
225  else
226  {
227  character = Character.Controlled;
228  }
229  }
230  }
231 
232  private IEnumerable<CoroutineStatus> Dead()
233  {
234  GUI.PreventPauseMenuToggle = true;
235  Character.Controlled = character = null;
236  Stop();
237 
238  GameAnalyticsManager.AddDesignEvent("Tutorial:Died");
239 
240  yield return new WaitForSeconds(3.0f);
241 
242  var messageBox = new GUIMessageBox(TextManager.Get("Tutorial.TryAgainHeader"), TextManager.Get("Tutorial.TryAgain"), new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") });
243 
244  messageBox.Buttons[0].OnClicked += Restart;
245  messageBox.Buttons[0].OnClicked += messageBox.Close;
246 
247 
248  messageBox.Buttons[1].OnClicked = GameMain.MainMenuScreen.ReturnToMainMenu;
249  messageBox.Buttons[1].OnClicked += messageBox.Close;
250 
251  yield return CoroutineStatus.Success;
252  }
253 
254  public IEnumerable<CoroutineStatus> UpdateState()
255  {
257  {
258  yield return new WaitForSeconds(0.1f);
259  }
260 
261  if (eventPrefab == null)
262  {
263  DebugConsole.LogError($"No tutorial event defined for the tutorial (identifier: \"{TutorialPrefab?.Identifier.ToString() ?? "null"})\"");
264  yield return CoroutineStatus.Failure;
265  }
266 
267  if (eventPrefab.CreateInstance(GameMain.GameSession.EventManager.RandomSeed) is Event eventInstance)
268  {
269  GameMain.GameSession.EventManager.QueuedEvents.Enqueue(eventInstance);
270  while (!eventInstance.IsFinished)
271  {
272  yield return CoroutineStatus.Running;
273  }
274  }
275  else
276  {
277  DebugConsole.LogError($"Failed to create an instance for a tutorial event (identifier: \"{eventPrefab.Identifier}\"");
278  yield return CoroutineStatus.Failure;
279  }
280 
281  yield return CoroutineStatus.Success;
282  }
283 
284  public void Complete()
285  {
286  GameAnalyticsManager.AddDesignEvent($"Tutorial:{Identifier}:Completed");
287  completedCoroutine = CoroutineManager.StartCoroutine(TutorialCompleted());
288 
289  IEnumerable<CoroutineStatus> TutorialCompleted()
290  {
291  while (GUI.PauseMenuOpen) { yield return CoroutineStatus.Running; }
292 
293  GUI.PreventPauseMenuToggle = true;
295  Character.Controlled = null;
296  GameAnalyticsManager.AddDesignEvent("Tutorial:Completed");
297 
298  yield return new WaitForSeconds(WaitBeforeFade);
299 
300  Action onEnd = () => GameMain.MainMenuScreen.ReturnToMainMenu(null, null);
301 
302  TutorialPrefab nextTutorialPrefab = null;
303  bool displayEndMessage =
304  TutorialPrefab.EndMessage.EndType == TutorialPrefab.EndType.Restart ||
305  (TutorialPrefab.EndMessage.EndType == TutorialPrefab.EndType.Continue && TutorialPrefab.Prefabs.TryGet(TutorialPrefab.EndMessage.NextTutorialIdentifier, out nextTutorialPrefab));
306 
307  if (displayEndMessage)
308  {
309  Paused = true;
310  var endingMessageBox = new GUIMessageBox(
311  headerText: "",
312  text: TextManager.Get($"{Identifier}.completed"),
313  buttons: new LocalizedString[]
314  {
315  TextManager.Get(nextTutorialPrefab is null ? "restart" : "campaigncontinue"),
316  TextManager.Get("pausemenuquit")
317  });
318 
319  endingMessageBox.Buttons[0].OnClicked += (_, _) =>
320  {
321  if (nextTutorialPrefab is null)
322  {
323  onEnd = () => Restart(null, null);
324  }
325  else
326  {
327  onEnd = () =>
328  {
330  new Tutorial(nextTutorialPrefab).Start();
331  };
332  }
333  return true;
334  };
335  endingMessageBox.Buttons[0].OnClicked += endingMessageBox.Close;
336  endingMessageBox.Buttons[0].OnClicked += (_, _) => Paused = false;
337  endingMessageBox.Buttons[1].OnClicked += endingMessageBox.Close;
338  endingMessageBox.Buttons[1].OnClicked += (_, _) => Paused = false;
339  }
340 
341  while (Paused) { yield return CoroutineStatus.Running; }
342 
343  var endCinematic = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, Alignment.Center, panDuration: FadeOutTime);
344  Completed = true;
345 
346  while (endCinematic.Running) { yield return CoroutineStatus.Running; }
347 
348  Stop();
349  onEnd();
350  }
351  }
352 
353  private bool Restart(GUIButton button, object obj)
354  {
355  GUIMessageBox.MessageBoxes.Clear();
357  Start();
358  return true;
359  }
360 
361  public void Stop()
362  {
363  if (tutorialCoroutine != null)
364  {
365  CoroutineManager.StopCoroutines(tutorialCoroutine);
366  }
367  ObjectiveManager.ResetUI();
368  }
369 
370  #endregion
371  }
372 }
static readonly PrefabCollection< Biome > Prefabs
Definition: Biome.cs:10
void GiveJobItems(bool isPvPMode, WayPoint spawnPoint=null)
static CompletedTutorials Instance
void Add(Identifier identifier)
string???????????? Value
Definition: ContentPath.cs:27
static CoroutineStatus Running
static CoroutineStatus Failure
static CoroutineStatus Success
Event sets are sets of random events that occur within a level (most commonly, monster spawns and scr...
Definition: EventSet.cs:31
static EventPrefab GetEventPrefab(Identifier identifier)
Definition: EventSet.cs:80
static readonly List< GUIComponent > MessageBoxes
static GameSession?? GameSession
Definition: GameMain.cs:88
CoroutineHandle ShowLoading(IEnumerable< CoroutineStatus > loader, bool waitKeyHit=true)
Definition: GameMain.cs:1241
static GameScreen GameScreen
Definition: GameMain.cs:52
static MainMenuScreen MainMenuScreen
Definition: GameMain.cs:53
static GameMain Instance
Definition: GameMain.cs:144
static LuaCsSetup LuaCs
Definition: GameMain.cs:26
static GameModePreset Tutorial
void StartRound(string levelSeed, float? difficulty=null, LevelGenerationParams? levelGenerationParams=null, Identifier forceBiome=default)
Item FindItemByTag(Identifier tag, bool recursive=false)
static readonly List< Item > ItemList
Dictionary< RelatedItem.RelationType, List< RelatedItem > > RequiredItems
static readonly PrefabCollection< LevelGenerationParams > LevelParams
bool ReturnToMainMenu(GUIButton button, object obj)
static Submarine MainSub
Note that this can be null in some situations, e.g. editors and missions that don't load a submarine.
static readonly PrefabCollection< TutorialPrefab > Prefabs
readonly bool AllowCharacterSwitch
readonly ImmutableArray< Identifier > StartingItemTags
readonly ContentPath SubmarinePath
readonly Identifier EventIdentifier
readonly ContentPath OutpostPath
readonly EndMessageInfo EndMessage
readonly string LevelSeed
CharacterInfo GetTutorialCharacterInfo()
readonly string LevelParams
readonly List<(Entity entity, Identifier iconStyle)> Icons
Definition: Tutorial.cs:64
Tutorial(TutorialPrefab prefab)
Definition: Tutorial.cs:72
IEnumerable< CoroutineStatus > UpdateState()
Definition: Tutorial.cs:254
readonly Identifier Identifier
Definition: Tutorial.cs:25
LocalizedString DisplayName
Definition: Tutorial.cs:26
readonly TutorialPrefab TutorialPrefab
Definition: Tutorial.cs:49
LocalizedString Description
Definition: Tutorial.cs:27
@ Character
Characters only
@ Structure
Structures and hulls, but also items (for backwards support)!