using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using Content.Client.Interfaces.Parallax; using Nett; using Robust.Client.Graphics; using Robust.Client.Interfaces.ResourceManagement; using Robust.Shared.Interfaces.Configuration; using Robust.Shared.Interfaces.Log; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Utility; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; namespace Content.Client.Parallax { internal sealed class ParallaxManager : IParallaxManager, IPostInjectInit { #pragma warning disable 649 [Dependency] private readonly IResourceCache _resourceCache; [Dependency] private readonly ILogManager _logManager; [Dependency] private readonly IConfigurationManager _configurationManager; #pragma warning restore 649 private static readonly ResourcePath ParallaxConfigPath = new ResourcePath("/parallax_config.toml"); // Both of these below are in the user directory. private static readonly ResourcePath ParallaxPath = new ResourcePath("/parallax_cache.png"); private static readonly ResourcePath ParallaxConfigOld = new ResourcePath("/parallax_config_old"); public event Action OnTextureLoaded; public Texture ParallaxTexture { get; private set; } public async void LoadParallax() { if (!_configurationManager.GetCVar("parallax.enabled")) { return; } var debugParallax = _configurationManager.GetCVar("parallax.debug"); string contents; TomlTable table; // Load normal config into memory if (!_resourceCache.TryContentFileRead(ParallaxConfigPath, out var configStream)) { Logger.ErrorS("parallax", "Parallax config not found."); return; } using (configStream) { using (var reader = new StreamReader(configStream, EncodingHelpers.UTF8)) { contents = reader.ReadToEnd(); } if (!debugParallax && _resourceCache.UserData.Exists(ParallaxConfigOld)) { bool match; using (var data = _resourceCache.UserData.Open(ParallaxConfigOld, FileMode.Open)) using (var reader = new StreamReader(data, EncodingHelpers.UTF8)) { match = reader.ReadToEnd() == contents; } if (match) { using (var stream = _resourceCache.UserData.Open(ParallaxPath, FileMode.Open)) { ParallaxTexture = Texture.LoadFromPNGStream(stream, "Parallax"); } OnTextureLoaded?.Invoke(ParallaxTexture); return; } } table = Toml.ReadString(contents); } List> debugImages = null; if (debugParallax) { debugImages = new List>(); } var sawmill = _logManager.GetSawmill("parallax"); // Generate the parallax in the thread pool. var image = await Task.Run(() => ParallaxGenerator.GenerateParallax(table, new Size(1920, 1080), sawmill, debugImages)); // And load it in the main thread for safety reasons. ParallaxTexture = Texture.LoadFromImage(image, "Parallax"); // Store it and CRC so further game starts don't need to regenerate it. using (var stream = _resourceCache.UserData.Open(ParallaxPath, FileMode.Create)) { image.SaveAsPng(stream); } if (debugParallax) { var i = 0; foreach (var debugImage in debugImages) { using (var stream = _resourceCache.UserData.Open(new ResourcePath($"/parallax_debug_{i}.png"), FileMode.Create)) { debugImage.SaveAsPng(stream); } i += 1; } } image.Dispose(); using (var stream = _resourceCache.UserData.Open(ParallaxConfigOld, FileMode.Create)) using (var writer = new StreamWriter(stream, EncodingHelpers.UTF8)) { writer.Write(contents); } OnTextureLoaded?.Invoke(ParallaxTexture); } public void PostInject() { _configurationManager.RegisterCVar("parallax.enabled", true); _configurationManager.RegisterCVar("parallax.debug", false); } } }