diff --git a/NResUI/Abstractions/IConfigProvider.cs b/NResUI/Abstractions/IConfigProvider.cs new file mode 100644 index 0000000..9e9b504 --- /dev/null +++ b/NResUI/Abstractions/IConfigProvider.cs @@ -0,0 +1,27 @@ +namespace NResUI.Abstractions; + +public interface IConfigProvider +{ + ConfigJson GetConfig(); + void Set(ConfigJson config); +} + +public class ConfigProvider : IConfigProvider +{ + private ConfigJson _instance; + + public ConfigProvider(ConfigJson instance) + { + _instance = instance; + } + + public void Set(ConfigJson config) + { + _instance = config; + } + + public ConfigJson GetConfig() + { + return _instance; + } +} diff --git a/NResUI/Abstractions/ILaunchReceiver.cs b/NResUI/Abstractions/ILaunchReceiver.cs new file mode 100644 index 0000000..388f1fe --- /dev/null +++ b/NResUI/Abstractions/ILaunchReceiver.cs @@ -0,0 +1,6 @@ +namespace NResUI.Abstractions; + +public interface ILaunchReceiver +{ + void OnLaunch(); +} diff --git a/NResUI/App.cs b/NResUI/App.cs index 9c0461d..1d68397 100644 --- a/NResUI/App.cs +++ b/NResUI/App.cs @@ -26,6 +26,7 @@ public class App public ImFontPtr OpenSansFont; private List _imGuiPanels = []; + private Later _later = new(); public App() { @@ -40,6 +41,9 @@ public class App IServiceCollection serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(_later); + serviceCollection.AddSingleton(new ConfigProvider(new ConfigJson())); + foreach (var type in Utils.GetAssignableTypes()) { serviceCollection.AddSingleton(type); @@ -50,6 +54,11 @@ public class App serviceCollection.AddSingleton(type); } + foreach (var type in Utils.GetAssignableTypes()) + { + serviceCollection.AddSingleton(type); + } + serviceCollection.AddSingleton(openGl); serviceCollection.AddSingleton(window); @@ -68,6 +77,12 @@ public class App _imGuiPanels = Utils.GetAssignableTypes() .Select(t => (serviceProvider.GetService(t) as IImGuiPanel)!) .ToList(); + + foreach (var type in Utils.GetAssignableTypes()) + { + var launchReceiver = serviceProvider.GetService(type) as ILaunchReceiver; + launchReceiver?.OnLaunch(); + } } public void OnImGuiRender() @@ -123,6 +138,11 @@ public class App { imGuiPanel.OnImGuiRender(); } + + foreach (var action in _later.Enumerate()) + { + action(); + } // ImGui.ShowMetricsWindow(); // ImGui.ShowDemoWindow(); diff --git a/NResUI/ConfigJson.cs b/NResUI/ConfigJson.cs new file mode 100644 index 0000000..34ac2b4 --- /dev/null +++ b/NResUI/ConfigJson.cs @@ -0,0 +1,9 @@ +namespace NResUI; + +public class ConfigJson +{ + /// + /// Path to the game itself (the folder that contains DATA, MISSIONS, UNITS etc) + /// + public string GameBasePath { get; set; } +} diff --git a/NResUI/ILater.cs b/NResUI/ILater.cs new file mode 100644 index 0000000..e62508c --- /dev/null +++ b/NResUI/ILater.cs @@ -0,0 +1,25 @@ +namespace NResUI; + +public interface ILater +{ + void Execute(Action action); +} + +public class Later : ILater +{ + private Queue _queue = new(); + + public void Execute(Action action) + { + _queue.Enqueue(action); + } + + public IEnumerable Enumerate() + { + while (_queue.Count > 0) + { + var action = _queue.Dequeue(); + yield return action; + } + } +} \ No newline at end of file diff --git a/NResUI/ImGuiUI/ViewportPanel.cs b/NResUI/ImGuiUI/ViewportPanel.cs index 523a1b0..e5ba576 100644 --- a/NResUI/ImGuiUI/ViewportPanel.cs +++ b/NResUI/ImGuiUI/ViewportPanel.cs @@ -16,12 +16,14 @@ public sealed class ViewportPanel : IImGuiPanel private readonly ViewportScene _scene; private readonly ViewportCamera _camera = new(); private readonly ViewportInputController _inputController = new(); + private readonly IConfigProvider _configProvider; private string? _loadedModelPath; private string? _loadError; - public ViewportPanel(GL gl, IWindow window) + public ViewportPanel(GL gl, IWindow window, IConfigProvider configProvider) { + _configProvider = configProvider; var cubeMesh = PrimitiveMeshes.CreateCube(gl); var gridMesh = PrimitiveMeshes.CreateWorldGrid(gl); @@ -94,7 +96,7 @@ public sealed class ViewportPanel : IImGuiPanel if (ImGui.Button("Reset cube")) { var cubeMesh = PrimitiveMeshes.CreateCube(_renderer.Gl); - _scene.ReplacePieces(new[] { ViewportPiece.CreateUnitCube(0, "Cube", cubeMesh) }); + _scene.ReplacePieces([ViewportPiece.CreateUnitCube(0, "Cube", cubeMesh)]); _loadedModelPath = null; _loadError = null; _camera.Reset(); @@ -109,7 +111,7 @@ public sealed class ViewportPanel : IImGuiPanel private void LoadMsh(string path) { - var loadResult = MshViewportLoader.LoadFromFile(_renderer.Gl, path); + var loadResult = MshViewportLoader.LoadFromFile(_renderer.Gl, path, _configProvider); if (!loadResult.IsSuccess) { _loadError = loadResult.Error ?? "Unknown error."; diff --git a/NResUI/LoadConfigOnLaunch.cs b/NResUI/LoadConfigOnLaunch.cs new file mode 100644 index 0000000..7897958 --- /dev/null +++ b/NResUI/LoadConfigOnLaunch.cs @@ -0,0 +1,53 @@ +using System.Text.Json; +using NResUI.Abstractions; +using NResUI.ImGuiUI; + +namespace NResUI; + +public class LoadConfigOnLaunch : ILaunchReceiver +{ + private readonly ILater _later; + private readonly MessageBoxModalPanel _messageBoxModalPanel; + private readonly IConfigProvider _configProvider; + + public LoadConfigOnLaunch(ILater later, MessageBoxModalPanel messageBoxModalPanel, IConfigProvider configProvider) + { + _later = later; + _messageBoxModalPanel = messageBoxModalPanel; + _configProvider = configProvider; + } + + public void OnLaunch() + { + if (!File.Exists("config.json")) + { + _later.Execute(() => + { + _messageBoxModalPanel.Show( + "config.json is not present\nViewport may be broken without game files\nPlaceholder file has been created"); + }); + var config = new ConfigJson() { GameBasePath = "" }; + File.WriteAllText("config.json", JsonSerializer.Serialize(config)); + _configProvider.Set(config); + } + else + { + var content = File.ReadAllText("config.json"); + var config = JsonSerializer.Deserialize(content); + if (config is null) + { + _later.Execute(() => + { + _messageBoxModalPanel.Show("config.json was corrupted\nReplacement has been created"); + }); + config = new ConfigJson() { GameBasePath = "" }; + File.WriteAllText("config.json", JsonSerializer.Serialize(config)); + _configProvider.Set(config); + } + else + { + _configProvider.Set(config); + } + } + } +} diff --git a/NResUI/Rendering.zip b/NResUI/Rendering.zip deleted file mode 100644 index 00430b1..0000000 Binary files a/NResUI/Rendering.zip and /dev/null differ diff --git a/NResUI/Rendering/Viewport/Msh/MshViewportLoader.cs b/NResUI/Rendering/Viewport/Msh/MshViewportLoader.cs index 68ad944..2110dad 100644 --- a/NResUI/Rendering/Viewport/Msh/MshViewportLoader.cs +++ b/NResUI/Rendering/Viewport/Msh/MshViewportLoader.cs @@ -1,6 +1,7 @@ using System.Numerics; using MshLib; using NResLib; +using NResUI.Abstractions; using NResUI.Rendering.Viewport.Meshes; using Silk.NET.OpenGL; @@ -11,7 +12,7 @@ public static class MshViewportLoader private const int DefaultModelState = 0; private const int DefaultLod = 0; - public static MshViewportLoadResult LoadFromFile(GL gl, string path) + public static MshViewportLoadResult LoadFromFile(GL gl, string path, IConfigProvider configProvider) { try { @@ -30,7 +31,7 @@ public static class MshViewportLoader } fs.Seek(0, SeekOrigin.Begin); - var pieces = LoadModelPieces(gl, fs, archive, path); + var pieces = LoadModelPieces(gl, fs, archive, path, configProvider); if (pieces.Count == 0) return MshViewportLoadResult.Failure("MSH was parsed, but no renderable geometry pieces were found.", path); @@ -42,7 +43,9 @@ public static class MshViewportLoader } } - private static List LoadModelPieces(GL gl, FileStream fs, NResArchive archive, string path) + private static List LoadModelPieces( + GL gl, FileStream fs, NResArchive archive, string path, IConfigProvider configProvider + ) { var nodes = Msh0x01.ReadComponent(fs, archive); var geometry = Msh0x02.ReadComponent(fs, archive); @@ -54,7 +57,7 @@ public static class MshViewportLoader var restPoses = MshRestPoseBuilder.BuildRestPose(nodes, animationDescriptors); var mshToViewportTransform = Matrix4x4.CreateRotationX(-MathF.PI * 0.5f); var names = TryReadNames(fs, archive); - var materialLibrary = WeaMaterialLibrary.TryLoadForMsh(gl, path); + var materialLibrary = WeaMaterialLibrary.TryLoadForMsh(gl, path, configProvider); var pieces = new List(); diff --git a/NResUI/Rendering/Viewport/Msh/WeaMaterialLibrary.cs b/NResUI/Rendering/Viewport/Msh/WeaMaterialLibrary.cs index 05c4419..4b57318 100644 --- a/NResUI/Rendering/Viewport/Msh/WeaMaterialLibrary.cs +++ b/NResUI/Rendering/Viewport/Msh/WeaMaterialLibrary.cs @@ -1,5 +1,6 @@ using MaterialLib; using NResLib; +using NResUI.Abstractions; using NResUI.Rendering.Viewport.OpenGL; using Silk.NET.OpenGL; using TexmLib; @@ -24,7 +25,7 @@ public sealed class WeaMaterialLibrary : null; } - public static WeaMaterialLibrary TryLoadForMsh(GL gl, string mshPath) + public static WeaMaterialLibrary TryLoadForMsh(GL gl, string mshPath, IConfigProvider configProvider) { var weaPath = FindMatchingWeaPath(mshPath); if (!File.Exists(weaPath)) @@ -34,9 +35,13 @@ public sealed class WeaMaterialLibrary if (materialRefs.Count == 0) return Empty; - // TODO: Hardcoded for now - var materialLibFs = "C:\\IronStrategy\\Material.lib"; + var materialLibFs = Path.Combine(configProvider.GetConfig().GameBasePath, "Material.lib"); + if (!File.Exists(materialLibFs)) + { + return new WeaMaterialLibrary([]); + } + using var materialFs = new FileStream(materialLibFs, FileMode.Open, FileAccess.Read, FileShare.Read); var parseResult = NResParser.ReadFile(materialFs); if (parseResult.Archive == null) @@ -46,7 +51,7 @@ public sealed class WeaMaterialLibrary foreach (var materialRef in materialRefs) { - var texture = TryLoadMaterialTexture(gl, materialFs, parseResult.Archive, materialRef.Name, out var texm); + var texture = TryLoadMaterialTexture(gl, materialFs, parseResult.Archive, materialRef.Name, configProvider, out var texm); result[materialRef.Id] = texture != null ? new ViewportMaterial(materialRef.Name, texture.Value, texm) : new ViewportMaterial(materialRef.Name); @@ -194,7 +199,8 @@ public sealed class WeaMaterialLibrary } private static uint? TryLoadMaterialTexture( - GL gl, FileStream materialFs, NResArchive materialArchive, string materialName, out TexmFile? texm + GL gl, FileStream materialFs, NResArchive materialArchive, string materialName, IConfigProvider configProvider, + out TexmFile? texm ) { texm = null; @@ -212,7 +218,12 @@ public sealed class WeaMaterialLibrary var texture = materialFile.Stages[0].TextureName; - var textureLibFs = "C:\\IronStrategy\\Textures.lib"; + var textureLibFs = Path.Combine(configProvider.GetConfig().GameBasePath, "Textures.lib"); + + if (!File.Exists(textureLibFs)) + { + return null; + } using var textureFs = new FileStream(textureLibFs, FileMode.Open, FileAccess.Read, FileShare.Read); var textureResult = NResParser.ReadFile(textureFs); @@ -269,27 +280,5 @@ public sealed class WeaMaterialLibrary Normalize(x.FileName) == normalized); } - private static string? FindTexturesLib(string? startDirectory) - { - if (string.IsNullOrWhiteSpace(startDirectory)) - return null; - - var directory = new DirectoryInfo(startDirectory); - while (directory != null) - { - var candidate = Path.Combine(directory.FullName, "Textures.lib"); - if (Directory.Exists(candidate)) - return candidate; - - candidate = Path.Combine(directory.FullName, "textures.lib"); - if (Directory.Exists(candidate)) - return candidate; - - directory = directory.Parent; - } - - return null; - } - private readonly record struct WeaMaterialRef(int Id, string Name); }