Client LuaCsForBarotrauma
LoadingScreen.cs
2 using Barotrauma.Media;
3 using Microsoft.Xna.Framework;
4 using Microsoft.Xna.Framework.Graphics;
5 using Microsoft.Xna.Framework.Input;
6 using System;
7 using System.Collections.Concurrent;
8 using System.Collections.Generic;
9 using System.Linq;
10 
11 namespace Barotrauma
12 {
13  sealed class LoadingScreen
14  {
15  private readonly Sprite defaultBackgroundTexture, overlay;
16  private readonly SpriteSheet decorativeGraph, decorativeMap;
17  private Sprite currentBackgroundTexture;
18  private readonly Sprite noiseSprite;
19 
20  private string randText = "";
21 
22  private Sprite languageSelectionCursor;
23  private ScalableFont languageSelectionFont, languageSelectionFontCJK;
24 
25  private Video currSplashScreen;
26  private DateTime videoStartTime;
27 
28  private bool mirrorBackground;
29 
30  public struct PendingSplashScreen
31  {
32  public string Filename;
33  public float Gain;
34  public PendingSplashScreen(string filename, float gain)
35  {
36  Filename = filename;
37  Gain = gain;
38  }
39  }
40 
44  public readonly ConcurrentQueue<PendingSplashScreen> PendingSplashScreens = new ConcurrentQueue<PendingSplashScreen>();
45 
46  public bool PlayingSplashScreen
47  {
48  get
49  {
50  return currSplashScreen != null || PendingSplashScreens.Count > 0;
51  }
52  }
53 
54  private RichString selectedTip;
55  private void SetSelectedTip(LocalizedString tip)
56  {
57  selectedTip = RichString.Rich(tip);
58  }
59 
60  public float LoadState;
61 
63  {
64  get;
65  set;
66  }
67 
69 
70  public LoadingScreen(GraphicsDevice graphics)
71  {
72  defaultBackgroundTexture = new Sprite("Content/Map/LocationPortraits/MainMenu1.png", Vector2.Zero);
73 
74  decorativeMap = new SpriteSheet("Content/Map/MapHUD.png", 6, 5, Vector2.Zero, sourceRect: new Rectangle(0, 0, 2048, 640));
75  decorativeGraph = new SpriteSheet("Content/Map/MapHUD.png", 4, 10, Vector2.Zero, sourceRect: new Rectangle(1025, 1259, 1024, 732));
76 
77  overlay = new Sprite("Content/UI/MainMenuVignette.png", Vector2.Zero);
78  noiseSprite = new Sprite("Content/UI/noise.png", Vector2.Zero);
79 
80  SetSelectedTip(TextManager.Get("LoadingScreenTip"));
81  }
82 
83  public void Draw(SpriteBatch spriteBatch, GraphicsDevice graphics, float deltaTime)
84  {
85  if (GameSettings.CurrentConfig.EnableSplashScreen)
86  {
87  try
88  {
89  DrawSplashScreen(spriteBatch, graphics);
90  if (currSplashScreen != null || PendingSplashScreens.Count > 0) { return; }
91  }
92  catch (Exception e)
93  {
94  DebugConsole.ThrowError("Playing splash screen video failed", e);
95  DisableSplashScreen();
96  }
97  }
98 
99  drawn = true;
100 
101  currentBackgroundTexture ??= defaultBackgroundTexture;
102 
103  float overlayScale = Math.Min(GameMain.GraphicsWidth / overlay.size.X, GameMain.GraphicsHeight / overlay.size.Y);
104 
105  Rectangle drawArea = new Rectangle(
106  (int)(overlay.size.X * overlayScale / 2), 0,
107  (int)(GameMain.GraphicsWidth - overlay.size.X * overlayScale / 2), GameMain.GraphicsHeight);
108 
109  spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, samplerState: GUI.SamplerState);
110 
111  GUI.DrawBackgroundSprite(spriteBatch, currentBackgroundTexture, Color.White, drawArea,
112  spriteEffects: mirrorBackground ? SpriteEffects.FlipHorizontally : SpriteEffects.None);
113  overlay.Draw(spriteBatch, Vector2.Zero, scale: overlayScale);
114 
115  double noiseT = Timing.TotalTime * 0.02f;
116  float noiseStrength = (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0);
117  float noiseScale = (float)PerlinNoise.CalculatePerlin(noiseT * 5.0f, noiseT * 2.0f, 0) * 4.0f;
118  noiseSprite.DrawTiled(spriteBatch, Vector2.Zero, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight),
119  startOffset: new Vector2(Rand.Range(0.0f, noiseSprite.SourceRect.Width), Rand.Range(0.0f, noiseSprite.SourceRect.Height)),
120  color: Color.White * noiseStrength * 0.1f,
121  textureScale: Vector2.One * noiseScale);
122 
123  Vector2 textPos = new Vector2((int)(GameMain.GraphicsWidth * 0.05f), (int)(GameMain.GraphicsHeight * 0.75f));
125  {
126  DrawLanguageSelectionPrompt(spriteBatch, graphics);
127  }
128  else
129  {
130  LocalizedString loadText;
131  var loadState = LoadState; // avoid multiple reads here to prevent jank
132  if (loadState >= 100.0f)
133  {
134 #if DEBUG
135  if (GameSettings.CurrentConfig.AutomaticQuickStartEnabled || GameSettings.CurrentConfig.AutomaticCampaignLoadEnabled || (GameSettings.CurrentConfig.TestScreenEnabled && GameMain.FirstLoad))
136  {
137  loadText = "QUICKSTARTING ...";
138  }
139  else
140  {
141 #endif
142  loadText = TextManager.Get("PressAnyKey");
143 #if DEBUG
144  }
145 #endif
146  }
147  else
148  {
149  loadText = TextManager.Get("Loading");
150  if (loadState >= 0f)
151  {
152  loadText += $" {loadState:N0} %";
153  }
154 
155 #if DEBUG
156  if (GameMain.FirstLoad && GameMain.CancelQuickStart)
157  {
158  loadText += " (Quickstart aborted)";
159  }
160 #endif
161  }
162 
163  if (GUIStyle.LargeFont.HasValue)
164  {
165  GUIStyle.LargeFont.DrawString(spriteBatch, loadText.ToUpper(),
166  textPos,
167  Color.White);
168  textPos.Y += GUIStyle.LargeFont.MeasureString(loadText.ToUpper()).Y * 1.2f;
169  }
170 
171  if (GUIStyle.Font.HasValue && selectedTip != null)
172  {
173  string wrappedTip = ToolBox.WrapText(selectedTip.SanitizedValue, GameMain.GraphicsWidth * 0.3f, GUIStyle.Font.Value);
174  string[] lines = wrappedTip.Split('\n');
175  float lineHeight = GUIStyle.Font.MeasureString(selectedTip).Y;
176 
177  if (selectedTip.RichTextData != null)
178  {
179  int rtdOffset = 0;
180  for (int i = 0; i < lines.Length; i++)
181  {
182  GUIStyle.Font.DrawStringWithColors(spriteBatch, lines[i],
183  new Vector2(textPos.X, (int)(textPos.Y + i * lineHeight)),
184  Color.White,
185  0f, Vector2.Zero, 1f, SpriteEffects.None, 0f, selectedTip.RichTextData.Value, rtdOffset);
186  rtdOffset += lines[i].Length;
187  }
188  }
189  else
190  {
191  for (int i = 0; i < lines.Length; i++)
192  {
193  GUIStyle.Font.DrawString(spriteBatch, lines[i],
194  new Vector2(textPos.X, (int)(textPos.Y + i * lineHeight)),
195  new Color(228, 217, 167, 255));
196  }
197  }
198  }
199  }
200  GUI.DrawMessageBoxesOnly(spriteBatch);
201  spriteBatch.End();
202 
203  spriteBatch.Begin(blendState: BlendState.Additive);
204 
205  Vector2 decorativeScale = new Vector2(GameMain.GraphicsHeight / 1080.0f);
206 
207  float noiseVal = (float)PerlinNoise.CalculatePerlin(Timing.TotalTime * 0.25f, Timing.TotalTime * 0.5f, 0);
209  {
210  decorativeGraph.Draw(spriteBatch, (int)(decorativeGraph.FrameCount * noiseVal),
211  new Vector2(GameMain.GraphicsWidth * 0.001f, textPos.Y),
212  Color.White, new Vector2(0, decorativeMap.FrameSize.Y), 0.0f, decorativeScale, SpriteEffects.FlipVertically);
213  }
214 
215  decorativeMap.Draw(spriteBatch, (int)(decorativeMap.FrameCount * noiseVal),
216  new Vector2(GameMain.GraphicsWidth * 0.99f, GameMain.GraphicsHeight * 0.01f),
217  Color.White, new Vector2(decorativeMap.FrameSize.X, 0), 0.0f, decorativeScale, SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically);
218 
219  if (noiseVal < 0.2f)
220  {
221  //SCP-CB reference
222  randText = (new string[] { "NIL", "black white gray", "Sometimes we would have had time to scream", "e8m106]af", "NO" }).GetRandomUnsynced();
223  }
224  else if (noiseVal < 0.3f)
225  {
226  randText = ToolBox.RandomSeed(9);
227  }
228  else if (noiseVal < 0.5f)
229  {
230  randText =
231  Rand.Int(100).ToString().PadLeft(2, '0') + " " +
232  Rand.Int(100).ToString().PadLeft(2, '0') + " " +
233  Rand.Int(100).ToString().PadLeft(2, '0') + " " +
234  Rand.Int(100).ToString().PadLeft(2, '0');
235  }
236 
237  if (GUIStyle.LargeFont.HasValue)
238  {
239  Vector2 textSize = GUIStyle.LargeFont.MeasureString(randText);
240  GUIStyle.LargeFont.DrawString(spriteBatch, randText,
241  new Vector2(GameMain.GraphicsWidth * 0.95f - textSize.X, GameMain.GraphicsHeight * 0.06f),
242  Color.White * (1.0f - noiseVal));
243  }
244 
245  spriteBatch.End();
246  }
247 
248  private void DrawLanguageSelectionPrompt(SpriteBatch spriteBatch, GraphicsDevice graphicsDevice)
249  {
250  if (AvailableLanguages is null) { return; }
251 
252  if (languageSelectionFont == null)
253  {
254  languageSelectionFont = new ScalableFont("Content/Fonts/NotoSans/NotoSans-Bold.ttf",
255  (uint)(30 * (GameMain.GraphicsHeight / 1080.0f)), graphicsDevice);
256  }
257  if (languageSelectionFontCJK == null)
258  {
259  languageSelectionFontCJK = new ScalableFont("Content/Fonts/NotoSans/NotoSansCJKsc-Bold.otf",
260  (uint)(30 * (GameMain.GraphicsHeight / 1080.0f)), graphicsDevice, dynamicLoading: true);
261  }
262  if (languageSelectionCursor == null)
263  {
264  languageSelectionCursor = new Sprite("Content/UI/cursor.png", Vector2.Zero);
265  }
266 
267  Vector2 textPos = new Vector2((int)(GameMain.GraphicsWidth * 0.05f), (int)(GameMain.GraphicsHeight * 0.3f));
268  Vector2 textSpacing = new Vector2(0.0f, GameMain.GraphicsHeight * 0.5f / AvailableLanguages.Length);
269  foreach (LanguageIdentifier language in AvailableLanguages)
270  {
271  string localizedLanguageName = TextManager.GetTranslatedLanguageName(language);
272  var font = TextManager.IsCJK(localizedLanguageName) ? languageSelectionFontCJK : languageSelectionFont;
273 
274  Vector2 textSize = font.MeasureString(localizedLanguageName);
275  bool hover =
276  PlayerInput.MousePosition.X > textPos.X && PlayerInput.MousePosition.X < textPos.X + textSize.X &&
277  PlayerInput.MousePosition.Y > textPos.Y && PlayerInput.MousePosition.Y < textPos.Y + textSize.Y;
278 
279  font.DrawString(spriteBatch, localizedLanguageName, textPos,
280  hover ? Color.White : Color.White * 0.6f);
281  if (hover && PlayerInput.PrimaryMouseButtonClicked())
282  {
283  var config = GameSettings.CurrentConfig;
284  config.Language = language;
285  GameSettings.SetCurrentConfig(config);
286  //reload tip in the selected language
287  SetSelectedTip(TextManager.Get("LoadingScreenTip"));
288  WaitForLanguageSelection = false;
289  languageSelectionFont?.Dispose(); languageSelectionFont = null;
290  languageSelectionFontCJK?.Dispose(); languageSelectionFontCJK = null;
291  break;
292  }
293 
294  textPos += textSpacing;
295  }
296 
297  languageSelectionCursor.Draw(spriteBatch, PlayerInput.LatestMousePosition, scale: 0.5f);
298  }
299 
300  private void DrawSplashScreen(SpriteBatch spriteBatch, GraphicsDevice graphics)
301  {
302  if (currSplashScreen == null)
303  {
304  if (!PendingSplashScreens.TryDequeue(out var newSplashScreen)) { return; }
305  string fileName = newSplashScreen.Filename;
306  try
307  {
308  currSplashScreen = Video.Load(graphics, GameMain.SoundManager, fileName);
309  currSplashScreen.AudioGain = newSplashScreen.Gain;
310  videoStartTime = DateTime.Now;
311  }
312  catch (Exception e)
313  {
314  DisableSplashScreen();
315  DebugConsole.ThrowError("Playing the splash screen \"" + fileName + "\" failed.", e);
316  PendingSplashScreens.Clear();
317  currSplashScreen = null;
318  }
319  }
320 
321  if (currSplashScreen == null) { return; }
322 
323  if (currSplashScreen.IsPlaying)
324  {
325  graphics.Clear(Color.Black);
326  float videoAspectRatio = (float)currSplashScreen.Width / (float)currSplashScreen.Height;
327  int width; int height;
328  if (GameMain.GraphicsHeight * videoAspectRatio > GameMain.GraphicsWidth)
329  {
330  width = GameMain.GraphicsWidth;
331  height = (int)(GameMain.GraphicsWidth / videoAspectRatio);
332  }
333  else
334  {
335  width = (int)(GameMain.GraphicsHeight * videoAspectRatio);
336  height = GameMain.GraphicsHeight;
337  }
338 
339  spriteBatch.Begin();
340  spriteBatch.Draw(
341  currSplashScreen.GetTexture(),
342  destinationRectangle: new Rectangle(
343  GameMain.GraphicsWidth / 2 - width / 2,
344  GameMain.GraphicsHeight / 2 - height / 2,
345  width,
346  height),
347  sourceRectangle: new Rectangle(0, 0, currSplashScreen.Width, currSplashScreen.Height),
348  Color.White,
349  rotation: 0.0f,
350  origin: Vector2.Zero,
351  SpriteEffects.None,
352  layerDepth: 0.0f);
353  spriteBatch.End();
354 
355  if (DateTime.Now > videoStartTime + new TimeSpan(0, 0, 0, 0, milliseconds: 500)
356  && GameMain.WindowActive
357  && (PlayerInput.KeyHit(Keys.Escape)
358  || PlayerInput.KeyHit(Keys.Space)
359  || PlayerInput.KeyHit(Keys.Enter)
360  || PlayerInput.PrimaryMouseButtonDown()))
361  {
362  currSplashScreen.Dispose(); currSplashScreen = null;
363  }
364  }
365  else if (DateTime.Now > videoStartTime + new TimeSpan(0, 0, 0, 0, milliseconds: 1500))
366  {
367  currSplashScreen.Dispose(); currSplashScreen = null;
368  }
369  }
370 
371  private void DisableSplashScreen()
372  {
373  var config = GameSettings.CurrentConfig;
374  config.EnableSplashScreen = false;
375  GameSettings.SetCurrentConfig(config);
376  }
377 
378  bool drawn;
379  public IEnumerable<CoroutineStatus> DoLoading(IEnumerable<CoroutineStatus> loader)
380  {
381  drawn = false;
382  LoadState = -1f;
383  SetSelectedTip(TextManager.Get("LoadingScreenTip"));
384  currentBackgroundTexture = LocationType.Prefabs.Where(p => p.UsePortraitInRandomLoadingScreens).GetRandomUnsynced()?.GetPortrait(Rand.Int(int.MaxValue));
385  if (GameMain.GameSession?.GameMode?.Missions is { } missions && missions.Any(m => m.Prefab.HasPortraits))
386  {
387  currentBackgroundTexture = missions.Where(m => m.Prefab.HasPortraits).First().Prefab.GetPortrait(Rand.Int(int.MaxValue));
388  }
389  mirrorBackground = Rand.Range(0.0f, 1.0f) < 0.5f;
390 
391  while (!drawn)
392  {
393  yield return CoroutineStatus.Running;
394  }
395 
396  CoroutineManager.StartCoroutine(loader);
397 
398  yield return CoroutineStatus.Running;
399 
400  while (CoroutineManager.IsCoroutineRunning(loader.ToString()))
401  {
402  yield return CoroutineStatus.Running;
403  }
404 
405  LoadState = 100.0f;
406 
407  yield return CoroutineStatus.Success;
408  }
409  }
410 }
static CoroutineStatus Running
static CoroutineStatus Success
static int GraphicsWidth
Definition: GameMain.cs:162
static GameSession?? GameSession
Definition: GameMain.cs:88
static int GraphicsHeight
Definition: GameMain.cs:168
LoadingScreen(GraphicsDevice graphics)
void Draw(SpriteBatch spriteBatch, GraphicsDevice graphics, float deltaTime)
readonly ConcurrentQueue< PendingSplashScreen > PendingSplashScreens
Triplet.first = filepath, Triplet.second = resolution, Triplet.third = audio gain
LanguageIdentifier[] AvailableLanguages
IEnumerable< CoroutineStatus > DoLoading(IEnumerable< CoroutineStatus > loader)
IReadOnlyList< LocalizedString > Split(params char[] separators)
virtual LocalizedString ToUpper()
static readonly PrefabCollection< LocationType > Prefabs
Definition: LocationType.cs:15
static Video Load(GraphicsDevice graphicsDevice, SoundManager soundManager, string filename)
Definition: Video.cs:72
Texture2D GetTexture()
Definition: Video.cs:147
ImmutableArray< RichTextData >? RichTextData
Definition: RichString.cs:42
static RichString Rich(LocalizedString str, Func< string, string >? postProcess=null)
Definition: RichString.cs:67
Vector2 MeasureString(LocalizedString str, bool removeExtraSpacing=false)
void Draw(ISpriteBatch spriteBatch, Vector2 pos, float rotate=0.0f, float scale=1.0f, SpriteEffects spriteEffect=SpriteEffects.None)
PendingSplashScreen(string filename, float gain)