407 lines
17 KiB
C#
407 lines
17 KiB
C#
using Barotrauma.Extensions;
|
|
using Barotrauma.Media;
|
|
using Microsoft.Xna.Framework;
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
using Microsoft.Xna.Framework.Input;
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace Barotrauma
|
|
{
|
|
sealed class LoadingScreen
|
|
{
|
|
private readonly Sprite defaultBackgroundTexture, overlay;
|
|
private readonly SpriteSheet decorativeGraph, decorativeMap;
|
|
private Sprite currentBackgroundTexture;
|
|
private readonly Sprite noiseSprite;
|
|
|
|
private string randText = "";
|
|
|
|
private Sprite languageSelectionCursor;
|
|
private ScalableFont languageSelectionFont, languageSelectionFontCJK;
|
|
|
|
private Video currSplashScreen;
|
|
private DateTime videoStartTime;
|
|
|
|
public struct PendingSplashScreen
|
|
{
|
|
public string Filename;
|
|
public float Gain;
|
|
public PendingSplashScreen(string filename, float gain)
|
|
{
|
|
Filename = filename;
|
|
Gain = gain;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Triplet.first = filepath, Triplet.second = resolution, Triplet.third = audio gain
|
|
/// </summary>
|
|
public readonly ConcurrentQueue<PendingSplashScreen> PendingSplashScreens = new ConcurrentQueue<PendingSplashScreen>();
|
|
|
|
public bool PlayingSplashScreen
|
|
{
|
|
get
|
|
{
|
|
return currSplashScreen != null || PendingSplashScreens.Count > 0;
|
|
}
|
|
}
|
|
|
|
private RichString selectedTip;
|
|
private void SetSelectedTip(LocalizedString tip)
|
|
{
|
|
selectedTip = RichString.Rich(tip);
|
|
}
|
|
|
|
public float LoadState;
|
|
|
|
public bool WaitForLanguageSelection
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public LanguageIdentifier[] AvailableLanguages = null;
|
|
|
|
public LoadingScreen(GraphicsDevice graphics)
|
|
{
|
|
defaultBackgroundTexture = new Sprite("Content/Map/LocationPortraits/MainMenu1.png", Vector2.Zero);
|
|
|
|
decorativeMap = new SpriteSheet("Content/Map/MapHUD.png", 6, 5, Vector2.Zero, sourceRect: new Rectangle(0, 0, 2048, 640));
|
|
decorativeGraph = new SpriteSheet("Content/Map/MapHUD.png", 4, 10, Vector2.Zero, sourceRect: new Rectangle(1025, 1259, 1024, 732));
|
|
|
|
overlay = new Sprite("Content/UI/MainMenuVignette.png", Vector2.Zero);
|
|
noiseSprite = new Sprite("Content/UI/noise.png", Vector2.Zero);
|
|
|
|
SetSelectedTip(TextManager.Get("LoadingScreenTip"));
|
|
}
|
|
|
|
public void Draw(SpriteBatch spriteBatch, GraphicsDevice graphics, float deltaTime)
|
|
{
|
|
if (GameSettings.CurrentConfig.EnableSplashScreen)
|
|
{
|
|
try
|
|
{
|
|
DrawSplashScreen(spriteBatch, graphics);
|
|
if (currSplashScreen != null || PendingSplashScreens.Count > 0) { return; }
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
DebugConsole.ThrowError("Playing splash screen video failed", e);
|
|
DisableSplashScreen();
|
|
}
|
|
}
|
|
|
|
drawn = true;
|
|
|
|
currentBackgroundTexture ??= defaultBackgroundTexture;
|
|
|
|
float overlayScale = Math.Min(GameMain.GraphicsWidth / overlay.size.X, GameMain.GraphicsHeight / overlay.size.Y);
|
|
|
|
Rectangle drawArea = new Rectangle(
|
|
(int)(overlay.size.X * overlayScale / 2), 0,
|
|
(int)(GameMain.GraphicsWidth - overlay.size.X * overlayScale / 2), GameMain.GraphicsHeight);
|
|
|
|
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, samplerState: GUI.SamplerState);
|
|
|
|
GUI.DrawBackgroundSprite(spriteBatch, currentBackgroundTexture, Color.White, drawArea);
|
|
overlay.Draw(spriteBatch, Vector2.Zero, scale: overlayScale);
|
|
|
|
double noiseT = Timing.TotalTime * 0.02f;
|
|
float noiseStrength = (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0);
|
|
float noiseScale = (float)PerlinNoise.CalculatePerlin(noiseT * 5.0f, noiseT * 2.0f, 0) * 4.0f;
|
|
noiseSprite.DrawTiled(spriteBatch, Vector2.Zero, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight),
|
|
startOffset: new Vector2(Rand.Range(0.0f, noiseSprite.SourceRect.Width), Rand.Range(0.0f, noiseSprite.SourceRect.Height)),
|
|
color: Color.White * noiseStrength * 0.1f,
|
|
textureScale: Vector2.One * noiseScale);
|
|
|
|
Vector2 textPos = new Vector2((int)(GameMain.GraphicsWidth * 0.05f), (int)(GameMain.GraphicsHeight * 0.75f));
|
|
if (WaitForLanguageSelection)
|
|
{
|
|
DrawLanguageSelectionPrompt(spriteBatch, graphics);
|
|
}
|
|
else
|
|
{
|
|
LocalizedString loadText;
|
|
var loadState = LoadState; // avoid multiple reads here to prevent jank
|
|
if (loadState >= 100.0f)
|
|
{
|
|
#if DEBUG
|
|
if (GameSettings.CurrentConfig.AutomaticQuickStartEnabled || GameSettings.CurrentConfig.AutomaticCampaignLoadEnabled || (GameSettings.CurrentConfig.TestScreenEnabled && GameMain.FirstLoad))
|
|
{
|
|
loadText = "QUICKSTARTING ...";
|
|
}
|
|
else
|
|
{
|
|
#endif
|
|
loadText = TextManager.Get("PressAnyKey");
|
|
#if DEBUG
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
loadText = TextManager.Get("Loading");
|
|
if (loadState >= 0f)
|
|
{
|
|
loadText += $" {loadState:N0} %";
|
|
}
|
|
|
|
#if DEBUG
|
|
if (GameMain.FirstLoad && GameMain.CancelQuickStart)
|
|
{
|
|
loadText += " (Quickstart aborted)";
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (GUIStyle.LargeFont.HasValue)
|
|
{
|
|
GUIStyle.LargeFont.DrawString(spriteBatch, loadText.ToUpper(),
|
|
textPos,
|
|
Color.White);
|
|
textPos.Y += GUIStyle.LargeFont.MeasureString(loadText.ToUpper()).Y * 1.2f;
|
|
}
|
|
|
|
if (GUIStyle.Font.HasValue && selectedTip != null)
|
|
{
|
|
string wrappedTip = ToolBox.WrapText(selectedTip.SanitizedValue, GameMain.GraphicsWidth * 0.3f, GUIStyle.Font.Value);
|
|
string[] lines = wrappedTip.Split('\n');
|
|
float lineHeight = GUIStyle.Font.MeasureString(selectedTip).Y;
|
|
|
|
if (selectedTip.RichTextData != null)
|
|
{
|
|
int rtdOffset = 0;
|
|
for (int i = 0; i < lines.Length; i++)
|
|
{
|
|
GUIStyle.Font.DrawStringWithColors(spriteBatch, lines[i],
|
|
new Vector2(textPos.X, (int)(textPos.Y + i * lineHeight)),
|
|
Color.White,
|
|
0f, Vector2.Zero, 1f, SpriteEffects.None, 0f, selectedTip.RichTextData.Value, rtdOffset);
|
|
rtdOffset += lines[i].Length;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < lines.Length; i++)
|
|
{
|
|
GUIStyle.Font.DrawString(spriteBatch, lines[i],
|
|
new Vector2(textPos.X, (int)(textPos.Y + i * lineHeight)),
|
|
new Color(228, 217, 167, 255));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
GUI.DrawMessageBoxesOnly(spriteBatch);
|
|
spriteBatch.End();
|
|
|
|
spriteBatch.Begin(blendState: BlendState.Additive);
|
|
|
|
Vector2 decorativeScale = new Vector2(GameMain.GraphicsHeight / 1080.0f);
|
|
|
|
float noiseVal = (float)PerlinNoise.CalculatePerlin(Timing.TotalTime * 0.25f, Timing.TotalTime * 0.5f, 0);
|
|
if (!WaitForLanguageSelection)
|
|
{
|
|
decorativeGraph.Draw(spriteBatch, (int)(decorativeGraph.FrameCount * noiseVal),
|
|
new Vector2(GameMain.GraphicsWidth * 0.001f, textPos.Y),
|
|
Color.White, new Vector2(0, decorativeMap.FrameSize.Y), 0.0f, decorativeScale, SpriteEffects.FlipVertically);
|
|
}
|
|
|
|
decorativeMap.Draw(spriteBatch, (int)(decorativeMap.FrameCount * noiseVal),
|
|
new Vector2(GameMain.GraphicsWidth * 0.99f, GameMain.GraphicsHeight * 0.01f),
|
|
Color.White, new Vector2(decorativeMap.FrameSize.X, 0), 0.0f, decorativeScale, SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically);
|
|
|
|
if (noiseVal < 0.2f)
|
|
{
|
|
//SCP-CB reference
|
|
randText = (new string[] { "NIL", "black white gray", "Sometimes we would have had time to scream", "e8m106]af", "NO" }).GetRandomUnsynced();
|
|
}
|
|
else if (noiseVal < 0.3f)
|
|
{
|
|
randText = ToolBox.RandomSeed(9);
|
|
}
|
|
else if (noiseVal < 0.5f)
|
|
{
|
|
randText =
|
|
Rand.Int(100).ToString().PadLeft(2, '0') + " " +
|
|
Rand.Int(100).ToString().PadLeft(2, '0') + " " +
|
|
Rand.Int(100).ToString().PadLeft(2, '0') + " " +
|
|
Rand.Int(100).ToString().PadLeft(2, '0');
|
|
}
|
|
|
|
if (GUIStyle.LargeFont.HasValue)
|
|
{
|
|
Vector2 textSize = GUIStyle.LargeFont.MeasureString(randText);
|
|
GUIStyle.LargeFont.DrawString(spriteBatch, randText,
|
|
new Vector2(GameMain.GraphicsWidth * 0.95f - textSize.X, GameMain.GraphicsHeight * 0.06f),
|
|
Color.White * (1.0f - noiseVal));
|
|
}
|
|
|
|
spriteBatch.End();
|
|
}
|
|
|
|
private void DrawLanguageSelectionPrompt(SpriteBatch spriteBatch, GraphicsDevice graphicsDevice)
|
|
{
|
|
if (AvailableLanguages is null) { return; }
|
|
|
|
if (languageSelectionFont == null)
|
|
{
|
|
languageSelectionFont = new ScalableFont("Content/Fonts/NotoSans/NotoSans-Bold.ttf",
|
|
(uint)(30 * (GameMain.GraphicsHeight / 1080.0f)), graphicsDevice);
|
|
}
|
|
if (languageSelectionFontCJK == null)
|
|
{
|
|
languageSelectionFontCJK = new ScalableFont("Content/Fonts/NotoSans/NotoSansCJKsc-Bold.otf",
|
|
(uint)(30 * (GameMain.GraphicsHeight / 1080.0f)), graphicsDevice, dynamicLoading: true);
|
|
}
|
|
if (languageSelectionCursor == null)
|
|
{
|
|
languageSelectionCursor = new Sprite("Content/UI/cursor.png", Vector2.Zero);
|
|
}
|
|
|
|
Vector2 textPos = new Vector2((int)(GameMain.GraphicsWidth * 0.05f), (int)(GameMain.GraphicsHeight * 0.3f));
|
|
Vector2 textSpacing = new Vector2(0.0f, GameMain.GraphicsHeight * 0.5f / AvailableLanguages.Length);
|
|
foreach (LanguageIdentifier language in AvailableLanguages)
|
|
{
|
|
string localizedLanguageName = TextManager.GetTranslatedLanguageName(language);
|
|
var font = TextManager.IsCJK(localizedLanguageName) ? languageSelectionFontCJK : languageSelectionFont;
|
|
|
|
Vector2 textSize = font.MeasureString(localizedLanguageName);
|
|
bool hover =
|
|
PlayerInput.MousePosition.X > textPos.X && PlayerInput.MousePosition.X < textPos.X + textSize.X &&
|
|
PlayerInput.MousePosition.Y > textPos.Y && PlayerInput.MousePosition.Y < textPos.Y + textSize.Y;
|
|
|
|
font.DrawString(spriteBatch, localizedLanguageName, textPos,
|
|
hover ? Color.White : Color.White * 0.6f);
|
|
if (hover && PlayerInput.PrimaryMouseButtonClicked())
|
|
{
|
|
var config = GameSettings.CurrentConfig;
|
|
config.Language = language;
|
|
GameSettings.SetCurrentConfig(config);
|
|
//reload tip in the selected language
|
|
SetSelectedTip(TextManager.Get("LoadingScreenTip"));
|
|
WaitForLanguageSelection = false;
|
|
languageSelectionFont?.Dispose(); languageSelectionFont = null;
|
|
languageSelectionFontCJK?.Dispose(); languageSelectionFontCJK = null;
|
|
break;
|
|
}
|
|
|
|
textPos += textSpacing;
|
|
}
|
|
|
|
languageSelectionCursor.Draw(spriteBatch, PlayerInput.LatestMousePosition, scale: 0.5f);
|
|
}
|
|
|
|
private void DrawSplashScreen(SpriteBatch spriteBatch, GraphicsDevice graphics)
|
|
{
|
|
if (currSplashScreen == null)
|
|
{
|
|
if (!PendingSplashScreens.TryDequeue(out var newSplashScreen)) { return; }
|
|
string fileName = newSplashScreen.Filename;
|
|
try
|
|
{
|
|
currSplashScreen = Video.Load(graphics, GameMain.SoundManager, fileName);
|
|
currSplashScreen.AudioGain = newSplashScreen.Gain;
|
|
videoStartTime = DateTime.Now;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
DisableSplashScreen();
|
|
DebugConsole.ThrowError("Playing the splash screen \"" + fileName + "\" failed.", e);
|
|
PendingSplashScreens.Clear();
|
|
currSplashScreen = null;
|
|
}
|
|
}
|
|
|
|
if (currSplashScreen == null) { return; }
|
|
|
|
if (currSplashScreen.IsPlaying)
|
|
{
|
|
graphics.Clear(Color.Black);
|
|
float videoAspectRatio = (float)currSplashScreen.Width / (float)currSplashScreen.Height;
|
|
int width; int height;
|
|
if (GameMain.GraphicsHeight * videoAspectRatio > GameMain.GraphicsWidth)
|
|
{
|
|
width = GameMain.GraphicsWidth;
|
|
height = (int)(GameMain.GraphicsWidth / videoAspectRatio);
|
|
}
|
|
else
|
|
{
|
|
width = (int)(GameMain.GraphicsHeight * videoAspectRatio);
|
|
height = GameMain.GraphicsHeight;
|
|
}
|
|
|
|
spriteBatch.Begin();
|
|
spriteBatch.Draw(
|
|
currSplashScreen.GetTexture(),
|
|
destinationRectangle: new Rectangle(
|
|
GameMain.GraphicsWidth / 2 - width / 2,
|
|
GameMain.GraphicsHeight / 2 - height / 2,
|
|
width,
|
|
height),
|
|
sourceRectangle: new Rectangle(0, 0, currSplashScreen.Width, currSplashScreen.Height),
|
|
Color.White,
|
|
rotation: 0.0f,
|
|
origin: Vector2.Zero,
|
|
SpriteEffects.None,
|
|
layerDepth: 0.0f);
|
|
spriteBatch.End();
|
|
|
|
if (DateTime.Now > videoStartTime + new TimeSpan(0, 0, 0, 0, milliseconds: 500)
|
|
&& GameMain.WindowActive
|
|
&& (PlayerInput.KeyHit(Keys.Escape)
|
|
|| PlayerInput.KeyHit(Keys.Space)
|
|
|| PlayerInput.KeyHit(Keys.Enter)
|
|
|| PlayerInput.PrimaryMouseButtonDown()))
|
|
{
|
|
currSplashScreen.Dispose(); currSplashScreen = null;
|
|
}
|
|
}
|
|
else if (DateTime.Now > videoStartTime + new TimeSpan(0, 0, 0, 0, milliseconds: 1500))
|
|
{
|
|
currSplashScreen.Dispose(); currSplashScreen = null;
|
|
}
|
|
}
|
|
|
|
private void DisableSplashScreen()
|
|
{
|
|
var config = GameSettings.CurrentConfig;
|
|
config.EnableSplashScreen = false;
|
|
GameSettings.SetCurrentConfig(config);
|
|
}
|
|
|
|
bool drawn;
|
|
public IEnumerable<CoroutineStatus> DoLoading(IEnumerable<CoroutineStatus> loader)
|
|
{
|
|
drawn = false;
|
|
LoadState = -1f;
|
|
SetSelectedTip(TextManager.Get("LoadingScreenTip"));
|
|
currentBackgroundTexture = LocationType.Prefabs.Where(p => p.UsePortraitInRandomLoadingScreens).GetRandomUnsynced()?.GetPortrait(Rand.Int(int.MaxValue));
|
|
if (GameMain.GameSession?.GameMode?.Missions is { } missions && missions.Any(m => m.Prefab.HasPortraits))
|
|
{
|
|
currentBackgroundTexture = missions.Where(m => m.Prefab.HasPortraits).First().Prefab.GetPortrait(Rand.Int(int.MaxValue));
|
|
}
|
|
|
|
while (!drawn)
|
|
{
|
|
yield return CoroutineStatus.Running;
|
|
}
|
|
|
|
CoroutineManager.StartCoroutine(loader);
|
|
|
|
yield return CoroutineStatus.Running;
|
|
|
|
while (CoroutineManager.IsCoroutineRunning(loader.ToString()))
|
|
{
|
|
yield return CoroutineStatus.Running;
|
|
}
|
|
|
|
LoadState = 100.0f;
|
|
|
|
yield return CoroutineStatus.Success;
|
|
}
|
|
}
|
|
}
|