diff --git a/JortPob.Tests/DescribeUtility.cs b/JortPob.Tests/DescribeUtility.cs index 6f39148..bea82fb 100644 --- a/JortPob.Tests/DescribeUtility.cs +++ b/JortPob.Tests/DescribeUtility.cs @@ -27,9 +27,9 @@ float ConvertValue(float colorValue) Color ConvertColor(Color color) { byte alpha = color.A; - byte red = (byte)(ConvertValue(color.R / 255f) * 255); - byte green = (byte)(ConvertValue(color.G / 255f) * 255); - byte blue = (byte)(ConvertValue(color.B / 255f) * 255); + byte red = (byte)Math.Round(ConvertValue(color.R / 255f) * 255); + byte green = (byte)Math.Round(ConvertValue(color.G / 255f) * 255); + byte blue = (byte)Math.Round(ConvertValue(color.B / 255f) * 255); return Color.FromArgb(alpha, red, green, blue); } @@ -38,7 +38,7 @@ Color ConvertColor(Color color) [Color.ForestGreen, Color.DarkSalmon, Color.Chocolate]]; Color[][] expectedOutputColor = inputColors.Select(cArr => cArr.Select(ConvertColor).ToArray()).ToArray(); - var inputBitmap = new Bitmap(3, 3, PixelFormat.Format32bppArgb); + using var inputBitmap = new Bitmap(3, 3, PixelFormat.Format32bppArgb); for (var h = 0; h < inputColors.Length; h++) { for (var w = 0; w < inputColors[h].Length; w++) @@ -47,7 +47,7 @@ Color ConvertColor(Color color) } } - var convertedBitmap = Utility.LinearToSRGB(inputBitmap); + using var convertedBitmap = Utility.LinearToSRGB(inputBitmap); for (var h = 0; h < inputColors.Length; h++) { for (var w = 0; w < inputColors[h].Length; w++) diff --git a/JortPob/BigTile.cs b/JortPob/BigTile.cs index 4d61ac1..b140c8a 100644 --- a/JortPob/BigTile.cs +++ b/JortPob/BigTile.cs @@ -4,13 +4,15 @@ using System.Diagnostics; using System.Numerics; +#nullable enable + namespace JortPob { /* BigTile is a 2x2 grid of Tiles. Sort of like an LOD type thing. */ [DebuggerDisplay("Big m{map}_{coordinate.x}_{coordinate.y}_{block} :: [{cells.Count}] Cells")] public class BigTile : BaseTile { - public HugeTile huge; + public HugeTile? huge { get; set; } public List tiles; public BigTile(int m, int x, int y, int b) : base(m, x, y, b) @@ -39,7 +41,7 @@ public bool PositionInside(Vector3 position) public override void AddCell(Cell cell) { cells.Add(cell); - Tile tile = GetTile(cell.center); + var tile = GetTile(cell.center); if (tile == null) { Lort.Log($" ## WARNING ## Cell fell outside of reality [{cell.coordinate.x}, {cell.coordinate.y}] -- {cell.name} :: B00", Lort.Type.Debug); return; } tile.AddCell(cell); } @@ -50,12 +52,12 @@ public override void AddCell(Cell cell) switch (content) { case AssetContent a: - ModelInfo modelInfo = cache.GetModel(a.mesh); + ModelInfo modelInfo = cache.GetModel(a.mesh) ?? throw new System.Exception($"Asset mesh is invalid '{a.mesh}'"); if (modelInfo.size * (content.scale*0.01f) > Const.CONTENT_SIZE_BIG) { float x = (coordinate.x * 2f * Const.TILE_SIZE) + (Const.TILE_SIZE * 0.5f); float y = (coordinate.y * 2f * Const.TILE_SIZE) + (Const.TILE_SIZE * 0.5f); content.relative = (content.position + Const.LAYOUT_COORDINATE_OFFSET) - new Vector3(x, 0, y); - Tile t = GetTile(cell.center); + var t = GetTile(cell.center); if (t == null) { break; } // Content fell outside of the bounds of any valid msbs. BAD! content.load = t.coordinate; base.AddContent(cache, cell, content); @@ -63,14 +65,14 @@ public override void AddCell(Cell cell) } goto default; default: - Tile tile = GetTile(cell.center); + var tile = GetTile(cell.center); if (tile == null) { break; } // Content fell outside of the bounds of any valid msbs. BAD! tile.AddContent(cache, cell, content); break; } } - public Tile GetTile(Vector3 position) + public Tile? GetTile(Vector3 position) { foreach (Tile tile in tiles) { diff --git a/JortPob/Cache.cs b/JortPob/Cache.cs index 7a35155..a8af133 100644 --- a/JortPob/Cache.cs +++ b/JortPob/Cache.cs @@ -246,7 +246,7 @@ public void AddConvertedEmitter(EmitterContent emitterContent) /* Big stupid load function */ public static Cache Load(ESM esm) { - string manifestPath = Const.CACHE_PATH + @"cache.json"; + string manifestPath = Path.Combine(Const.CACHE_PATH, "cache.json"); /* Cache Exists ? */ if (File.Exists(manifestPath)) @@ -324,9 +324,9 @@ void Scoop(List contents) nu.liquids = LiquidManager.GenerateLiquids(esm, materialContext); /* Add some pregen assets */ - ModelInfo boxModelInfo = new("InteriorShadowBox", $"meshes\\interior_shadow_box.flver", 100); - ModelConverter.NIFToFLVER(materialContext, boxModelInfo, false, Utility.ResourcePath(@"mesh\\box.nif"), $"{Const.CACHE_PATH}{boxModelInfo.path}"); - FLVER2 boxFlver = FLVER2.Read($"{Const.CACHE_PATH}{boxModelInfo.path}"); // we need this box to be exactly 1 unit in each direction no matter what so we just edit it real quick + ModelInfo boxModelInfo = new("InteriorShadowBox", @"meshes\interior_shadow_box.flver", 100); + ModelConverter.NIFToFLVER(materialContext, boxModelInfo, false, Utility.ResourcePath(@"mesh\box.nif"), Path.Combine(Const.CACHE_PATH, boxModelInfo.path)); + FLVER2 boxFlver = FLVER2.Read(Path.Combine(Const.CACHE_PATH, boxModelInfo.path)); // we need this box to be exactly 1 unit in each direction no matter what so we just edit it real quick foreach (FLVER.Vertex v in boxFlver.Meshes[0].Vertices) { float x = v.Position.X > 0f ? .5f : -.5f; @@ -335,11 +335,12 @@ void Scoop(List contents) v.Position = new Vector3(x, y, z); } BoundingBoxSolver.FLVER(boxFlver); // redo bounding box since we edited the mesh - boxFlver.Write($"{Const.CACHE_PATH}{boxModelInfo.path}"); + boxFlver.Write(Path.Combine(Const.CACHE_PATH, boxModelInfo.path)); nu.assets.Add(boxModelInfo); - string defaultCollisionObjPath = "meshes\\default_collision_plane.obj"; - if(!File.Exists($"{Const.CACHE_PATH}\\{defaultCollisionObjPath}")) { File.Copy(Utility.ResourcePath(@"mesh\plane.obj.file"), $"{Const.CACHE_PATH}{defaultCollisionObjPath}"); } + string defaultCollisionObjPath = @"meshes\default_collision_plane.obj"; + if(!File.Exists(Path.Combine(Const.CACHE_PATH, defaultCollisionObjPath))) + File.Copy(Utility.ResourcePath(@"mesh\plane.obj.file"), Path.Combine(Const.CACHE_PATH, defaultCollisionObjPath)); nu.defaultCollision = new("DefaultCollisionPlane", defaultCollisionObjPath); /* Write textures */ @@ -441,14 +442,14 @@ void Scoop(List contents) } /* Write new cache file */ - string jsonOutput = JsonSerializer.Serialize(nu, new JsonSerializerOptions { IncludeFields = true, NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowNamedFloatingPointLiterals }); + string jsonOutput = JsonSerializer.Serialize(nu, new JsonSerializerOptions { IncludeFields = true, NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals }); File.WriteAllText(manifestPath, jsonOutput); Lort.Log($"Generated new cache: {Const.CACHE_PATH}", Lort.Type.Main); } /* Load cache manifest */ string tempRawJson = File.ReadAllText(manifestPath); - Cache cache = JsonSerializer.Deserialize(tempRawJson, new JsonSerializerOptions { IncludeFields = true, NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowNamedFloatingPointLiterals })!; + Cache cache = JsonSerializer.Deserialize(tempRawJson, new JsonSerializerOptions { IncludeFields = true, NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals })!; return cache!; } } diff --git a/JortPob/Common/Bind.cs b/JortPob/Common/Bind.cs index f6e2e4a..ec78968 100644 --- a/JortPob/Common/Bind.cs +++ b/JortPob/Common/Bind.cs @@ -5,6 +5,8 @@ using System.IO; using System.Linq; +#nullable enable + namespace JortPob.Common { public class Bind @@ -84,6 +86,11 @@ public static void BindEmitters(Cache cache) { foreach(EmitterInfo emitterInfo in cache.emitters) { + if (emitterInfo.model == null) + { + Lort.LogDebug($" ## WARNING ## Trying to bind EmitterInfo {emitterInfo.id} without a model!"); + continue; + } string outPath = Path.Combine(Const.OUTPUT_PATH, @$"asset\aeg\{emitterInfo.AssetPath()}.geombnd.dcx"); // Bind up emitter asset flver diff --git a/JortPob/Common/Const.cs b/JortPob/Common/Const.cs index 6bb9ed8..8071127 100644 --- a/JortPob/Common/Const.cs +++ b/JortPob/Common/Const.cs @@ -6,6 +6,8 @@ using System.IO; using System.Numerics; +#nullable enable + namespace JortPob.Common { public static class Const @@ -137,7 +139,7 @@ public static class Const public static readonly bool DEBUG_SKIP_FMG_PARAM_SORTING = false; public static readonly bool DEBUG_SKIP_ESD = false; // skip building dialog esd for npcs, can be slow public static readonly bool DEBUG_SKIP_NICE_WATER_CIRCLIFICATION = true; // slow as shit, skipping this saves about a minute per build - public static readonly string DEBUG_EXCLUSIVE_CELL_BUILD_BY_NAME = null; // set to "null" to build entire map. + public static readonly string? DEBUG_EXCLUSIVE_CELL_BUILD_BY_NAME = null; // set to "null" to build entire map. public static readonly int[] DEBUG_EXCLUSIVE_BUILD_BY_BOX = new int[] { -3, -10, -1, -8 }; // also set to null to build entire map. format x1, y1, x2, y2. smaller values first, 1 = 1 cell, use cell coordinates // seyda neen area (small) = new int[] {-3, -10, -1, -8 } // seyda neen area (large) = new int[] { -5, -15, 5, -5 } diff --git a/JortPob/Common/DDS.cs b/JortPob/Common/DDS.cs index c7a9e19..11797c4 100644 --- a/JortPob/Common/DDS.cs +++ b/JortPob/Common/DDS.cs @@ -8,6 +8,8 @@ using System.Runtime.InteropServices; using TeximpNet.DDS; +#nullable enable + namespace JortPob.Common { public class DDS @@ -124,7 +126,7 @@ public static byte[] MakeTextureFromPixelData(Byte4[] pixels, int width, int hei lock(_lock) { /* For some damn reason the System.Drawing.Common is a NuGet dll. Something something windows only something */ - Bitmap img = new(width, height); + using Bitmap img = new(width, height); for (int x = 0; x < img.Width; x++) { for (int y = 0; y < img.Height; y++) @@ -147,22 +149,25 @@ public static byte[] MakeTextureFromPixelData(Byte4[] pixels, int width, int hei ScratchImage sImage = TexHelper.Instance.LoadFromWICMemory(pinnedArray.AddrOfPinnedObject(), pngBytes.Length, WIC_FLAGS.DEFAULT_SRGB); if (scaleX != null && scaleY != null) - sImage = sImage.Resize(0, scaleX.Value, scaleY.Value, filterFlags); + { + var tImage = sImage.Resize(0, scaleX.Value, scaleY.Value, filterFlags); + sImage.Dispose(); + sImage = tImage; + } - sImage = sImage.Compress(format, texCompFlag, 0.5f); - sImage.OverrideFormat(format); + using var compressedSImage = sImage.Compress(format, texCompFlag, 0.5f); + sImage.Dispose(); + compressedSImage.OverrideFormat(format); /* Save the DDS to memory stream and then read the stream into a byte array. */ byte[] bytes; - using (UnmanagedMemoryStream uStream = sImage.SaveToDDSMemory(ddsFlags)) + using (UnmanagedMemoryStream uStream = compressedSImage.SaveToDDSMemory(ddsFlags)) { bytes = new byte[uStream.Length]; uStream.Read(bytes); } pinnedArray.Free(); //We have to manually free pinned stuff, or it will never be collected. - img.Dispose(); - sImage.Dispose(); return bytes; } } @@ -196,7 +201,7 @@ public static byte[] Scale(byte[] dds) return scaled; } catch (Exception ex) { Lort.Log($"{ex.Message} {ex.StackTrace} {ex.Source}", Lort.Type.Debug); - return null; + throw; } } @@ -207,12 +212,16 @@ public static Bitmap DDStoBitmap(byte[] dds, int width = 0, int height = 0) DirectXTexNet.ScratchImage scratchImage = DirectXTexNet.TexHelper.Instance.LoadFromDDSMemory(pinnedArray.AddrOfPinnedObject(), dds.Length, DirectXTexNet.DDS_FLAGS.NONE); if (TexHelper.Instance.IsCompressed(scratchImage.GetMetadata().Format)) { - scratchImage = scratchImage.Decompress(DirectXTexNet.DXGI_FORMAT.R8G8B8A8_UNORM); + var tempScratchImage = scratchImage.Decompress(DirectXTexNet.DXGI_FORMAT.R8G8B8A8_UNORM); + scratchImage.Dispose(); + scratchImage = tempScratchImage; } if(width > 0 || height > 0) { - scratchImage = scratchImage.Resize(0, width, height, TEX_FILTER_FLAGS.CUBIC); + var tempScratchImage = scratchImage.Resize(0, width, height, TEX_FILTER_FLAGS.CUBIC); + scratchImage.Dispose(); + scratchImage = tempScratchImage; } Bitmap bitmap = new(scratchImage.GetImage(0).Width, scratchImage.GetImage(0).Height); @@ -258,23 +267,22 @@ public static byte[] BitmapToDDS /* pin the array to memory so the garbage collector can't mess with it, */ GCHandle pinnedArray = GCHandle.Alloc(pngBytes, GCHandleType.Pinned); - ScratchImage sImage = TexHelper.Instance.LoadFromWICMemory(pinnedArray.AddrOfPinnedObject(), pngBytes.Length, WIC_FLAGS.DEFAULT_SRGB); + using ScratchImage sImage = TexHelper.Instance.LoadFromWICMemory(pinnedArray.AddrOfPinnedObject(), pngBytes.Length, WIC_FLAGS.DEFAULT_SRGB); //sImage = sImage.Compress(DXGI_FORMAT.BC2_UNORM_SRGB, texCompFlag, 0.5f); - // sImage = sImage.Decompress(DirectXTexNet.DXGI_FORMAT.R8G8B8A8_UNORM); - sImage = sImage.Compress(format, texCompFlag, 0.5f); - sImage.OverrideFormat(format); + // sImage = sImage.Decompress(DirectXTexNet.DXGI_FORMAT.R8G8B8A8_UNORM); + using var compressedImage = sImage.Compress(format, texCompFlag, 0.5f); + compressedImage.OverrideFormat(format); /* Save the DDS to memory stream and then read the stream into a byte array. */ byte[] bytes; - using (UnmanagedMemoryStream uStream = sImage.SaveToDDSMemory(ddsFlags)) + using (UnmanagedMemoryStream uStream = compressedImage.SaveToDDSMemory(ddsFlags)) { bytes = new byte[uStream.Length]; uStream.Read(bytes); } pinnedArray.Free(); //We have to manually free pinned stuff, or it will never be collected. - sImage.Dispose(); return bytes; } diff --git a/JortPob/Common/Log.cs b/JortPob/Common/Log.cs index 5a77e54..8f8bf9e 100644 --- a/JortPob/Common/Log.cs +++ b/JortPob/Common/Log.cs @@ -6,31 +6,27 @@ using System.Linq; using System.Threading; +#nullable enable + namespace JortPob.Common { public class Lort { - public static ConcurrentBag mainOutput { get; private set; } - public static ConcurrentBag debugOutput { get; private set; } - public static string progressOutput { get; private set; } + public static ConcurrentBag mainOutput { get; } = new(); + public static ConcurrentBag debugOutput { get; } = new(); + public static string progressOutput { get; private set; } = string.Empty; public static int total { get; private set; } public static int current { get; private set; } public static bool update { get; set; } - public static string logFilePath { get; private set; } + public static string logFilePath { get; private set; } = string.Empty; public static void Initialize() { - mainOutput = new(); - debugOutput = new(); - progressOutput = string.Empty; total = 0; current = 0; update = false; - if (!Directory.Exists(Path.Combine(Const.OUTPUT_PATH, "logs"))) - { - Directory.CreateDirectory(Path.Combine(Const.OUTPUT_PATH, "logs")); - } + Directory.CreateDirectory(Path.Combine(Const.OUTPUT_PATH, "logs")); logFilePath = Path.Combine(Const.OUTPUT_PATH, @$"logs\jortpob-log-{DateTime.UtcNow.ToLongTimeString().Replace(":", "").Replace(" PM", "")}.txt"); File.WriteAllText(logFilePath, ""); @@ -42,7 +38,7 @@ public enum Type Debug } - public static void Log(string message, Lort.Type type) + public static void Log(string message, Type type) { switch (type) { @@ -55,6 +51,20 @@ public static void Log(string message, Lort.Type type) AppendTextToLog(message, type); } + public static void LogDebug(string message) + { + debugOutput.Add(message); + update = true; + AppendTextToLog(message, Type.Debug); + } + + public static void LogMain(string message) + { + mainOutput.Add(message); + update = true; + AppendTextToLog(message, Type.Main); + } + public static void NewTask(string task, int max) { progressOutput = $"{task}"; @@ -71,6 +81,9 @@ public static void TaskIterate() private static void AppendTextToLog(string message, Type type) { + if (string.IsNullOrEmpty(logFilePath)) + return; + switch (type) { case Type.Main: diff --git a/JortPob/Common/MapGenerator.cs b/JortPob/Common/MapGenerator.cs index fad1184..882ebbb 100644 --- a/JortPob/Common/MapGenerator.cs +++ b/JortPob/Common/MapGenerator.cs @@ -10,6 +10,8 @@ using System.Threading.Tasks; using System.Xml; +#nullable enable + namespace JortPob.Common { // most credit goes to https://github.com/Pear0533/ERMapGenerator @@ -42,20 +44,20 @@ public class MapGenerator : IDisposable { ZoomLevel.L2, 11 } }; - private BND4 mapTileMaskBnd; - private BXF4 mapTileTpfBxf; + private BND4? mapTileMaskBnd; + private BXF4? mapTileTpfBxf; private BXF4 mapTileBxf; private MapTileMatrix tileFlags; - private XmlNode mapTileMaskRoot; + private XmlNode? mapTileMaskRoot; - private Bitmap blankTileL0; - private Bitmap blankTileL1; - private Bitmap blankTileL2; + private Bitmap? blankTileL0; + private Bitmap? blankTileL1; + private Bitmap? blankTileL2; - private Bitmap scaledL1Image; - private Bitmap scaledL2Image; + private Bitmap? scaledL1Image; + private Bitmap? scaledL2Image; - private Action progressCallback; + private Action? progressCallback; public static (byte[] bhdBytes, byte[] bdtBytes) ReplaceMapTiles( Bitmap sourceImage, @@ -64,7 +66,7 @@ public static (byte[] bhdBytes, byte[] bdtBytes) ReplaceMapTiles( string mapTileMaskBndPath, string mapTileTpfBhdPath, string mapTileTpfBtdPath, - Action progressCallback = null, + Action? progressCallback = null, CancellationToken cancellationToken = default) { using var replacer = new MapGenerator(); @@ -92,7 +94,7 @@ private MapGenerator() string mapTileMaskBndPath, string mapTileTpfBhdPath, string mapTileTpfBtdPath, - Action progressCallback, + Action? progressCallback, CancellationToken cancellationToken) { this.progressCallback = progressCallback; @@ -182,9 +184,9 @@ private Bitmap GetScaledMapForZoomLevel(Bitmap sourceImage, ZoomLevel zoomLevel) { case ZoomLevel.L0 when sourceImage.Width == L0_SIZE && sourceImage.Height == L0_SIZE: return new Bitmap(sourceImage); - case ZoomLevel.L1: + case ZoomLevel.L1 when scaledL1Image is not null: return new Bitmap(scaledL1Image); - case ZoomLevel.L2: + case ZoomLevel.L2 when scaledL2Image is not null: return new Bitmap(scaledL2Image); } @@ -308,7 +310,7 @@ private void ExportTilesForLevel(Bitmap sourceImage, string groundLevel, ZoomLev private void ReadMapTileMask(string groundLevel) { - BinderFile file = mapTileMaskBnd.Files.Find(i => i.Name.Contains(groundLevel)); + var file = mapTileMaskBnd?.Files.Find(i => i.Name.Contains(groundLevel)); if (file != null) { string fileName = Path.GetFileName(file.Name); @@ -332,7 +334,7 @@ private void SetTileFlags() for (int i = 0; i < mapTileMaskRoot.ChildNodes.Count; ++i) { - XmlNode node = mapTileMaskRoot.ChildNodes[i]; + XmlNode node = mapTileMaskRoot.ChildNodes[i]!; if (node == null) continue; if (node.Attributes == null || node.Attributes.Count < 2) continue; @@ -371,16 +373,20 @@ private void WriteTileToPackage(Bitmap tileImage, string tileName) private (byte[] bhdBytes, byte[] bdtBytes) GetBinderBytes() { + // Something fucked up + if (mapTileBxf == null) + return ([], []); + // remove old tiles that are being replaced IEnumerable files = mapTileBxf.Files.Select(file => - mapTileTpfBxf.Files.FindIndex(i => + mapTileTpfBxf!.Files.FindIndex(i => string.Equals(i.Name, file.Name, StringComparison.OrdinalIgnoreCase))); foreach (int i in files.Where(index => index != -1)) - mapTileTpfBxf.Files.RemoveAt(i); + mapTileTpfBxf!.Files.RemoveAt(i); // add new tiles - mapTileTpfBxf.Files.AddRange(mapTileBxf.Files); + mapTileTpfBxf!.Files.AddRange(mapTileBxf.Files); // sort and re-index mapTileTpfBxf.Files = mapTileTpfBxf.Files.OrderBy(i => i.Name).ToList(); @@ -399,19 +405,8 @@ public void Dispose() blankTileL2?.Dispose(); scaledL1Image?.Dispose(); scaledL2Image?.Dispose(); - - // hacky but works - foreach (var file in mapTileMaskBnd.Files) - { - file.Bytes = null; - } - mapTileTpfBxf.Files.Clear(); - - foreach (var file in mapTileTpfBxf.Files) - { - file.Bytes = null; - } - mapTileTpfBxf.Files.Clear(); + mapTileMaskBnd?.Files.Clear(); + mapTileTpfBxf?.Files.Clear(); } private class MapTileMatrix diff --git a/JortPob/Common/Obj.cs b/JortPob/Common/Obj.cs index 42a46a1..2d302cf 100644 --- a/JortPob/Common/Obj.cs +++ b/JortPob/Common/Obj.cs @@ -5,6 +5,8 @@ using System.Numerics; using System.Text; +#nullable enable + namespace JortPob.Common { public class Obj @@ -60,7 +62,7 @@ public Obj(string path) } case "usemtl": { - last.mtl = values[1]; + last!.mtl = values[1]; break; } case "f": @@ -73,7 +75,7 @@ public Obj(string path) ObjV B = new(int.Parse(b[0]) - 1, int.Parse(b[1]) - 1, int.Parse(b[2]) - 1); ObjV C = new(int.Parse(c[0]) - 1, int.Parse(c[1]) - 1, int.Parse(c[2]) - 1); - last.fs.Add(new(A, B, C)); + last!.fs.Add(new(A, B, C)); break; } default: break; @@ -222,7 +224,7 @@ public void write(string outPath) /* write to file */ if (File.Exists(outPath)) { File.Delete(outPath); } - if(!Directory.Exists(Path.GetDirectoryName(outPath))) { Directory.CreateDirectory(Path.GetDirectoryName(outPath)); } + Directory.CreateDirectory(Path.GetDirectoryName(outPath) ?? throw new Exception($"Invalid path {outPath}!")); File.WriteAllText(outPath, sb.ToString()); } @@ -270,7 +272,9 @@ public enum CollisionMaterial public class ObjG { - public string name, mtl; + public string? name { get; set; } + public string? mtl { get; set; } + public List fs; public ObjG() { diff --git a/JortPob/Common/SAM.cs b/JortPob/Common/SAM.cs index 1d284d3..942e9dc 100644 --- a/JortPob/Common/SAM.cs +++ b/JortPob/Common/SAM.cs @@ -8,6 +8,8 @@ using System.Text; using System.Text.RegularExpressions; +#nullable enable + /* This exists for me to test if full voice acting will work properly before we get voice actors involved */ namespace JortPob.Common { @@ -75,7 +77,7 @@ public static string Generate(Dialog.DialogRecord dialog, Dialog.DialogInfoRecor CreateNoWindow = true }; startInfo.ArgumentList.AddRange(["create-new-project", $"\"{projectPath}\"", "--platform", "Windows"]); - using Process process = Process.Start(startInfo); + using var process = Process.Start(startInfo) ?? throw new Exception($" ## ERROR ## Failed to run wwise with arguments: {string.Join(", ", startInfo.ArgumentList)}"); process.WaitForExit(); } @@ -89,13 +91,13 @@ public static string Generate(Dialog.DialogRecord dialog, Dialog.DialogInfoRecor CreateNoWindow = true }; startInfo.ArgumentList.AddRange(["convert-external-source", $"\"{projectPath}\"", "--source-file", xmlRelative, "--output", "Windows", $"\"{lineDir}\""]); - using Process process = Process.Start(startInfo); + using var process = Process.Start(startInfo) ?? throw new Exception($" ## ERROR ## Failed to run wwise with arguments: {string.Join(", ", startInfo.ArgumentList)}"); process.WaitForExit(); } } - catch + catch(Exception ex) { - Lort.Log($"## ERROR ## Failed to generate dialog {wavPath}", Lort.Type.Debug); + Lort.Log($"## ERROR ## Failed to generate dialog {wavPath}, exception: {ex.Message}", Lort.Type.Debug); } if (File.Exists(wemPath)) { break; } // if the file is created successfully we don't need to retry. @@ -222,7 +224,7 @@ public static string GenerateAlt(Dialog.DialogRecord dialog, Dialog.DialogInfoRe // If processes succeeded but the file isn't there, something is wrong, we retry throw new FileNotFoundException($"WEM file was not found after successful conversion: {wemPath}"); } - catch (Exception ex) + catch (Exception) { // Keep retrying. Don't spam log after every failed generation as it's bloat. // If we fail up to MAX_RETRY then we throw an exception and print log. @@ -242,11 +244,7 @@ public static string GenerateAlt(Dialog.DialogRecord dialog, Dialog.DialogInfoRe private static void ExecuteProcess(ProcessStartInfo startInfo) { - using Process process = Process.Start(startInfo); - if (process == null) - { - throw new InvalidOperationException($"Failed to start process: {startInfo.FileName}"); - } + using var process = Process.Start(startInfo) ?? throw new InvalidOperationException($"Failed to start process: {startInfo.FileName}"); bool exited = process.WaitForExit(5000); diff --git a/JortPob/Common/Settable.cs b/JortPob/Common/Settable.cs index 941c474..64de7d1 100644 --- a/JortPob/Common/Settable.cs +++ b/JortPob/Common/Settable.cs @@ -3,20 +3,23 @@ using System.IO; using System.Text.Json.Nodes; +#nullable enable + namespace JortPob.Common { public class Settable { - private static JsonNode json; + private static JsonNode? json; + public static string Get(string key) { if(json == null) { - string tempRawJson = File.ReadAllText($"{AppDomain.CurrentDomain.BaseDirectory}settings.json"); + string tempRawJson = File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings.json")); json = JsonNode.Parse(tempRawJson); } - return json[key]?.ToString() ?? throw new Exception($"Setting with key '{key}' does not exist in settings.json"); + return json![key]?.ToString() ?? throw new Exception($"Setting with key '{key}' does not exist in settings.json"); } public static string[] GetArray(string key) @@ -29,9 +32,11 @@ public static string[] GetArray(string key) List strings = new(); - JsonArray array = json[key]?.AsArray() ?? throw new Exception($"Setting with key '{key}' does not exist in settings.json"); - foreach(JsonNode jsonNode in array) + JsonArray array = json![key]?.AsArray() ?? throw new Exception($"Setting with key '{key}' does not exist in settings.json"); + foreach(var jsonNode in array) { + if (jsonNode is null) + continue; strings.Add(jsonNode.GetValue()); } diff --git a/JortPob/Common/SillyJsonUtils.cs b/JortPob/Common/SillyJsonUtils.cs index e8d2357..68f65b9 100644 --- a/JortPob/Common/SillyJsonUtils.cs +++ b/JortPob/Common/SillyJsonUtils.cs @@ -7,6 +7,8 @@ using WitchyFormats; using static IronPython.Modules._ast; +#nullable enable + namespace JortPob.Common { public class SillyJsonUtils @@ -16,7 +18,7 @@ public static void SetField(Paramanager paramanager, Paramanager.ParamType param { FsParam param = paramanager.param[paramType]; FsParam.Row row = paramanager.GetRow(param, rowId); - FsParam.Cell cell = (FsParam.Cell)row[fieldName]; + FsParam.Cell cell = row[fieldName] ?? throw new Exception($"Cell with fieldName {fieldName} does not exist in row {row.ID}"); cell.SetValue(value); } @@ -25,14 +27,15 @@ public static void SetField(Paramanager paramanager, Paramanager.ParamType param { FsParam param = paramanager.param[paramType]; FsParam.Row row = paramanager.GetRow(param, rowId); - FsParam.Cell cell = (FsParam.Cell)row[fieldName]; + FsParam.Cell cell = row[fieldName] ?? throw new Exception($"Cell with fieldName {fieldName} does not exist in row {row.ID}"); cell.SetValue(value); } public static void CopyRowAndModify(Paramanager paramanager, SpeffManager speffManager, Paramanager.ParamType paramType, string name, int sourceRow, int destRow, Dictionary data) { FsParam param = paramanager.param[paramType]; - FsParam.Row row = paramanager.CloneRow(param[sourceRow], name, destRow); + FsParam.Row row = paramanager.CloneRow(param[sourceRow] ?? throw new Exception($"Row with id {sourceRow} does not exist in param type {Enum.GetName(paramType)}"), + name, destRow); /* List of named speff fields that should be resolved before applying */ List equipSpeffFieldNames = [ @@ -93,7 +96,7 @@ public static void CopyRowAndModify(Paramanager paramanager, SpeffManager speffM } /* Apply values from json to the param */ - FsParam.Cell cell = (FsParam.Cell)row[key]; + FsParam.Cell cell = row[key] ?? throw new Exception($"Cell with fieldName {key} does not exist in row {row.ID}"); switch (cell.Value.GetType()) { case Type t when t == typeof(int): diff --git a/JortPob/Common/Types.cs b/JortPob/Common/Types.cs index 35d970c..dbef1d1 100644 --- a/JortPob/Common/Types.cs +++ b/JortPob/Common/Types.cs @@ -3,20 +3,22 @@ using System.Collections.Generic; using System.Numerics; +#nullable enable + namespace JortPob.Common { public sealed class BinderFileIdComparer : IComparer { readonly Dictionary _cache = new(); - public int Compare(BinderFile x, BinderFile y) + public int Compare(BinderFile? x, BinderFile? y) { uint xi = GetId(x); uint yi = GetId(y); return xi < yi ? -1 : (xi > yi ? 1 : 0); } - uint GetId(BinderFile f) + uint GetId(BinderFile? f) { if (f == null) return uint.MaxValue; if (_cache.TryGetValue(f, out uint v)) return v; @@ -104,17 +106,23 @@ public Int2(int x, int y) this.x = x; this.y = y; } - public static bool operator ==(Int2 a, Int2 b) + public static bool operator ==(Int2? a, Int2? b) { - return a.Equals(b); + return a?.Equals(b) ?? b is null; } + public static bool operator !=(Int2 a, Int2 b) => !(a == b); - public bool Equals(Int2 b) + public bool Equals(Int2? b) { + if (ReferenceEquals(this, b)) + return true; + else if (b is null) + return false; + return x == b.x && y == b.y; } - public override bool Equals(object a) => Equals(a as Int2); + public override bool Equals(object? a) => Equals(a as Int2); public static Int2 operator +(Int2 a, Int2 b) { @@ -151,17 +159,21 @@ public UShort2(ushort x, ushort y) this.x = x; this.y = y; } - public static bool operator ==(UShort2 a, UShort2 b) + public static bool operator ==(UShort2? a, UShort2? b) { - return a.Equals(b); + return a?.Equals(b) ?? b is null; } - public static bool operator !=(UShort2 a, UShort2 b) => !(a == b); + public static bool operator !=(UShort2? a, UShort2? b) => !(a == b); - public bool Equals(UShort2 b) + public bool Equals(UShort2? b) { + if (ReferenceEquals(this, b)) + return true; + else if (b is null) + return false; return x == b.x && y == b.y; } - public override bool Equals(object a) => Equals(a as UShort2); + public override bool Equals(object? a) => Equals(a as UShort2); public override int GetHashCode() { diff --git a/JortPob/Common/Utility.cs b/JortPob/Common/Utility.cs index c64a589..5601350 100644 --- a/JortPob/Common/Utility.cs +++ b/JortPob/Common/Utility.cs @@ -11,6 +11,8 @@ using static Community.CsharpSqlite.Sqlite3; using System.Runtime.InteropServices; +#nullable enable + namespace JortPob.Common { public static class Utility @@ -33,7 +35,7 @@ float ConvertValue(float colorValue) for (var i = 0; i < LinSRGBLUT.Length; i++) { - LinSRGBLUT[i] = (byte)(Math.Min(1f, ConvertValue(i / 255f)) * 255); + LinSRGBLUT[i] = (byte)Math.Round(Math.Min(1f, ConvertValue(i / 255f)) * 255); } } @@ -230,7 +232,7 @@ public static bool StringIsOperator(string text) return true; } - private static Random random; + private static Random? random { get; set; } public static int RandomRange(int min, int max) { if(random == null) { random = new(Const.RANDOM_SEED); } @@ -250,7 +252,9 @@ public static Bitmap XbrzUpscale(Bitmap bitmap, int factor) // Perform scaling XbrzScaler scaler = new(factor, withAlpha: true); +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. The library handles this properly, it's just not part of the signature int[] scaledPixels = scaler.ScaleImage(srcPixels, null, width, height); +#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. // Convert back to Bitmap int scaledWidth = width * factor, scaledHeight = height * factor; @@ -302,85 +306,6 @@ public static Bitmap LinearToSRGB(Bitmap bitmap) return linearBitmap; } - public static unsafe Bitmap LinearToSRGBAlt(Bitmap bitmap) - { - Bitmap srgbBitmap = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format32bppArgb); - - Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height); - - // technically this can be done in place, but better safe than sorry - // due to bit manipulation shenanigens, bit locking is required - BitmapData bmpDataIn = bitmap.LockBits(rect, ImageLockMode.ReadOnly, bitmap.PixelFormat); - BitmapData bmpDataOut = srgbBitmap.LockBits(rect, ImageLockMode.WriteOnly, srgbBitmap.PixelFormat); - - try - { - byte* ptrIn = (byte*)bmpDataIn.Scan0; - byte* ptrOut = (byte*)bmpDataOut.Scan0; - - // LUT calculation - float[] sRGB_LUT = new float[256]; - for (int i = 0; i < 256; i++) - { - float linearValue = i / 255.0f; - sRGB_LUT[i] = Value(linearValue); - } - - int totalBytes = bmpDataIn.Stride * bitmap.Height; - - for (int i = 0; i < totalBytes; i += 4) - { - // since colors are big endian, they're reversed as b g r a - byte linearB = ptrIn[i]; - byte linearG = ptrIn[i + 1]; - byte linearR = ptrIn[i + 2]; - byte alphaA = ptrIn[i + 3]; - - // lut to the save - float srgbR_float = sRGB_LUT[linearR]; - float srgbG_float = sRGB_LUT[linearG]; - float srgbB_float = sRGB_LUT[linearB]; - - // squish the results back to range - ptrOut[i] = (byte)Math.Round(srgbB_float * 255f); - ptrOut[i + 1] = (byte)Math.Round(srgbG_float * 255f); - ptrOut[i + 2] = (byte)Math.Round(srgbR_float * 255f); - - ptrOut[i + 3] = alphaA; - } - } - finally - { - // MUST unlock the bits after processing - bitmap.UnlockBits(bmpDataIn); - srgbBitmap.UnlockBits(bmpDataOut); - } - - return srgbBitmap; - - float Value(float linearValue) - { - linearValue = Math.Max(0f, Math.Min(1f, linearValue)); - - if (linearValue <= 0.0031308f) - { - return linearValue * 12.92f; - } - else - { - return 1.055f * ((float)FastPow(linearValue, 1.0f / 2.4f)) - 0.055f; - } - } - } - - // source: trust me bro - public static double FastPow(double a, double b) - { - long tmp = BitConverter.DoubleToInt64Bits(a); - long tmp2 = (long)(b * (tmp - 4606921280493453312L)) + 4606921280493453312L; - return BitConverter.Int64BitsToDouble(tmp2); - } - /* Code borrowed from https://stackoverflow.com/questions/1922040/how-to-resize-an-image-c-sharp */ public static Bitmap ResizeBitmap(Image image, int width, int height) { diff --git a/JortPob/DialogESD.cs b/JortPob/DialogESD.cs index f54b94a..f1766bb 100644 --- a/JortPob/DialogESD.cs +++ b/JortPob/DialogESD.cs @@ -12,6 +12,8 @@ using static JortPob.FactionInfo; using static JortPob.NpcManager.TopicData; +#nullable enable + namespace JortPob { /* Handles python state machine code generation for a dialog ESD */ @@ -179,8 +181,8 @@ public DialogESD(ESM esm, ScriptManager scriptManager, Paramanager paramanager, public void Write(string pyPath) { - if (!Directory.Exists(Path.GetDirectoryName(pyPath))) { Directory.CreateDirectory(Path.GetDirectoryName(pyPath)); } - System.IO.File.WriteAllLines(pyPath, defs); + Directory.CreateDirectory(Path.GetDirectoryName(pyPath) ?? throw new Exception($"Path is not valid {pyPath}")); + File.WriteAllLines(pyPath, defs); } @@ -1396,15 +1398,15 @@ private string GeneratedState_RankReq(uint id, int x) Script.Flag repFlag = scriptManager.GetFlag(Script.Flag.Designation.FactionReputation, npcContent.faction); Script.Flag rankFlag = scriptManager.GetFlag(Script.Flag.Designation.FactionRank, npcContent.faction); Script.Flag returnValue = areaScript.CreateFlag(Script.Flag.Category.Temporary, Script.Flag.Type.Nibble, Script.Flag.Designation.ReturnValueRankReq, npcContent.entity.ToString()); - FactionInfo faction = esm.GetFaction(npcContent.faction); + FactionInfo? faction = esm.GetFaction(npcContent.faction); // First rank s += $" if GetEventFlagValue({rankFlag.id}, {rankFlag.Bits()}) == 0:\r\n"; s += $" SetEventFlagValue({returnValue.id}, {returnValue.Bits()}, 3)\r\n"; - for (int i = 0; i < faction.ranks.Count()-1; i++) + for (int i = 0; i < (faction?.ranks.Count() ?? 0)-1; i++) { - FactionInfo.Rank rank = faction.ranks[i]; + FactionInfo.Rank rank = faction!.ranks[i]; FactionInfo.Rank nextRank = faction.ranks[i + 1]; // Not max rank diff --git a/JortPob/ESM/Cell.cs b/JortPob/ESM/Cell.cs index 74a7097..7ea863b 100644 --- a/JortPob/ESM/Cell.cs +++ b/JortPob/ESM/Cell.cs @@ -6,6 +6,8 @@ using System.Text.Json.Nodes; using static JortPob.NpcContent; +#nullable enable + namespace JortPob { public class Cell @@ -13,7 +15,7 @@ public class Cell public enum Flag { IsInterior, HasWater, RestingIsIllegal, BehavesLikeExterior, Unk40 } public readonly string name; - public readonly string region; + public string? region { get; init; } public readonly Int2 coordinate; // Position on the cell grid public readonly Vector3 center; public readonly Vector3 boundsMin; @@ -35,11 +37,11 @@ public enum Flag { IsInterior, HasWater, RestingIsIllegal, BehavesLikeExterior, public Cell(ESM esm, JsonNode json) { /* Cell Data */ - name = json["name"]?.ToString(); - region = json["region"]?.ToString(); + name = json["name"]?.ToString() ?? throw new Exception("Could not find 'name' value in cell json!"); + region = json["region"]?.GetValue(); flags = new(); - string[] fs = json["data"]["flags"].GetValue().ToLower().Split("|"); + string[] fs = json["data"]?["flags"]?.GetValue().ToLower().Split("|") ?? []; foreach(string f in fs) { string trim = f.Trim().ToLower().Replace("_", ""); @@ -48,8 +50,8 @@ public Cell(ESM esm, JsonNode json) flags.Add(flag); } - int x = int.Parse(json["data"]["grid"][0].ToString()); - int y = int.Parse(json["data"]["grid"][1].ToString()); + int x = json["data"]?["grid"]?[0]?.GetValue() ?? throw new Exception("Could not find cell->data->grid->0 value!"); + int y = json["data"]?["grid"]?[1]?.GetValue() ?? throw new Exception("Could not find cell->data->grid->1 value!"); coordinate = new Int2(x, y); float half = Const.CELL_SIZE / 2f; @@ -67,14 +69,14 @@ public Cell(ESM esm, JsonNode json) pickables = new(); items = new(); - foreach (JsonNode reference in json["references"].AsArray()) + foreach (var reference in json["references"]?.AsArray() ?? []) { - string id = reference["id"].ToString(); + string id = reference?["id"]?.ToString() ?? throw new Exception("Could not find cell->references->id!"); Record record = esm.FindRecordById(id); if(record == null) { continue; } - string mesh = record.json["mesh"]?.ToString(); // mesh can just be "" sometimes + var mesh = record.json["mesh"]?.ToString(); // mesh can just be "" sometimes switch(record.type) { @@ -86,7 +88,7 @@ public Cell(ESM esm, JsonNode json) if (!string.IsNullOrEmpty(mesh)) { doors.Add(new DoorContent(this, reference, record)); } break; case ESM.Type.Light: - if (!string.IsNullOrEmpty(mesh)) { lights.Add(new LightContent(this, reference, record)); } + if (string.IsNullOrEmpty(mesh)) { lights.Add(new LightContent(this, reference, record)); } else { emitters.Add(new EmitterContent(this, reference, record)); } break; case ESM.Type.Npc: diff --git a/JortPob/ESM/Content.cs b/JortPob/ESM/Content.cs index 42dae56..555c0d9 100644 --- a/JortPob/ESM/Content.cs +++ b/JortPob/ESM/Content.cs @@ -7,12 +7,14 @@ using System.Text.Json.Nodes; using static JortPob.NpcContent; +#nullable enable + namespace JortPob { /* Content is effectively any physical object in the game world. Anything that has a physical position in a cell */ public abstract class Content { - public readonly Cell cell; + public Cell cell { get; init; } public readonly string id; // record id public readonly string? name; // can be null! @@ -21,7 +23,7 @@ public abstract class Content public uint entity; // entity id, usually 0 public string? papyrus { get; private set; } // papyrus script id if it has one (usually null) public Vector3 relative; - public Int2 load; // if a piece of content needs tile load data this is where it's stored + public Int2 load { get; set; } // if a piece of content needs tile load data this is where it's stored public readonly Vector3 position; public Vector3 rotation; @@ -32,7 +34,8 @@ public abstract class Content public Content(Cell cell, JsonNode json, Record record) { this.cell = cell; - id = record.json["id"].ToString(); + load = new(0, 0); + id = record.json["id"]?.ToString() ?? throw new Exception("Could not find id from content json!"); name = record.json["name"]?.GetValue(); type = record.type; @@ -40,13 +43,13 @@ public Content(Cell cell, JsonNode json, Record record) papyrus = string.IsNullOrEmpty(record.json["script"]?.GetValue()) ? null : record.json["script"]!.GetValue(); - float x = float.Parse(json["translation"][0].ToString()); - float z = float.Parse(json["translation"][1].ToString()); - float y = float.Parse(json["translation"][2].ToString()); + float x = float.Parse(json["translation"]?[0]?.ToString() ?? throw new Exception("Content translation x value is missing!")); + float z = float.Parse(json["translation"]?[1]?.ToString() ?? throw new Exception("Content translation y value is missing!")); + float y = float.Parse(json["translation"]?[2]?.ToString() ?? throw new Exception("Content translation z value is missing!")); - float i = float.Parse(json["rotation"][0].ToString()); - float j = float.Parse(json["rotation"][1].ToString()); - float k = float.Parse(json["rotation"][2].ToString()); + float i = float.Parse(json["rotation"]?[0]?.ToString() ?? throw new Exception("Content rotation i value is missing!")); + float j = float.Parse(json["rotation"]?[1]?.ToString() ?? throw new Exception("Content rotation j value is missing!")); + float k = float.Parse(json["rotation"]?[2]?.ToString() ?? throw new Exception("Content rotation k value is missing!")); /* The following unholy code converts morrowind (Z up) euler rotations into dark souls (Y up) euler rotations */ /* Big thanks to katalash, dropoff, and the TESUnity dudes for helping me sort this out */ @@ -86,10 +89,10 @@ Vector3 MatrixToEulerXZY(Matrix4x4 m) relative = new(); position = new Vector3(x, y, z) * Const.GLOBAL_SCALE; rotation = eu * (float)(180 / Math.PI); - scale = (int)((json["scale"] != null ? float.Parse(json["scale"].ToString()) : 1f) * 100); + scale = (int)((json["scale"] != null ? float.Parse(json["scale"]!.ToString()) : 1f) * 100); } - public Content(Cell cell, string id, string name, ESM.Type type, Int2 load, string papyrus, Vector3 position, Vector3 rotation, int scale) + public Content(Cell cell, string id, string? name, ESM.Type type, Int2 load, string? papyrus, Vector3 position, Vector3 rotation, int scale) { this.cell = cell; this.id = id; @@ -115,7 +118,8 @@ public enum Service { OffersRepairs, BartersLockpicks, BartersProbes, BartersLights }; - public readonly string job, faction; // class is job, cant used reserved word + public string job { get; init; } // class is job, cant used reserved word + public string? faction { get; init; } public readonly Race race; public readonly Sex sex; @@ -133,15 +137,15 @@ public enum Service { public List<(string id, int quantity)> inventory; public List spells; // spells this character knows or sells as a vendor - public List<(string id, int quantity)> barter; // can be null + public List<(string id, int quantity)>? barter; // can be null public List travel; // travel destinations for silt strider people, mage guild teles, etc... - public Script.Flag treasure; // only used if this is a dead body npc and it has treasure. otherwise null. NEVER SET THIS FOR A LIVING NPC!!! + public Script.Flag? treasure; // only used if this is a dead body npc and it has treasure. otherwise null. NEVER SET THIS FOR A LIVING NPC!!! public class Travel : DoorContent.Warp { - public string name; + public string? name { get; set; } public int cost; public Travel(JsonNode json) : base(json) { @@ -166,7 +170,7 @@ public Stats(JsonNode json, int level) foreach (Attribute attribute in Enum.GetValues(typeof(Attribute))) { - int val = json[attribute.ToString().ToLower()].GetValue(); + int val = json[attribute.ToString().ToLower()]!.GetValue(); attributes.Add(attribute, val); } @@ -182,19 +186,24 @@ public Stats(JsonNode json) attributes = new(); skills = new(); - JsonArray jsonAttributes = json["attributes"].AsArray(); - JsonArray jsonSkills = json["skills"].AsArray(); + JsonArray jsonAttributes = json["attributes"]?.AsArray() ?? throw new Exception("Stats attributes value is missing!"); + JsonArray jsonSkills = json["skills"]?.AsArray() ?? throw new Exception("Stats skills value is missing!"); + if (jsonAttributes.Count < Enum.GetValues().Length) + throw new Exception("Attributes array is not long enough!"); + else if (jsonSkills.Count < Enum.GetValues().Length) + throw new Exception("Skills array is not long enough!"); + int i = 0; - foreach (Attribute attribute in Enum.GetValues(typeof(Attribute))) + foreach (Attribute attribute in Enum.GetValues()) { - attributes.Add(attribute, jsonAttributes[i++].GetValue()); + attributes.Add(attribute, jsonAttributes[i++]!.GetValue()); } i = 0; - foreach (Skill skill in Enum.GetValues(typeof(Skill))) + foreach (Skill skill in Enum.GetValues()) { - skills.Add(skill, jsonSkills[i++].GetValue()); + skills.Add(skill, jsonSkills[i++]!.GetValue()); } } @@ -242,39 +251,39 @@ private Attribute GetParent(Skill skill) { switch (skill) { - case CharacterContent.Stats.Skill.HeavyArmor: - case CharacterContent.Stats.Skill.MediumArmor: - case CharacterContent.Stats.Skill.Spear: + case Skill.HeavyArmor: + case Skill.MediumArmor: + case Skill.Spear: return Attribute.Endurance; - case CharacterContent.Stats.Skill.Acrobatics: - case CharacterContent.Stats.Skill.Armorer: - case CharacterContent.Stats.Skill.Axe: - case CharacterContent.Stats.Skill.BluntWeapon: - case CharacterContent.Stats.Skill.LongBlade: + case Skill.Acrobatics: + case Skill.Armorer: + case Skill.Axe: + case Skill.BluntWeapon: + case Skill.LongBlade: return Attribute.Strength; - case CharacterContent.Stats.Skill.Block: - case CharacterContent.Stats.Skill.LightArmor: - case CharacterContent.Stats.Skill.Marksman: - case CharacterContent.Stats.Skill.Sneak: + case Skill.Block: + case Skill.LightArmor: + case Skill.Marksman: + case Skill.Sneak: return Attribute.Agility; - case CharacterContent.Stats.Skill.Athletics: - case CharacterContent.Stats.Skill.HandToHand: - case CharacterContent.Stats.Skill.ShortBlade: - case CharacterContent.Stats.Skill.Unarmored: + case Skill.Athletics: + case Skill.HandToHand: + case Skill.ShortBlade: + case Skill.Unarmored: return Attribute.Speed; - case CharacterContent.Stats.Skill.Mercantile: - case CharacterContent.Stats.Skill.Speechcraft: - case CharacterContent.Stats.Skill.Illusion: + case Skill.Mercantile: + case Skill.Speechcraft: + case Skill.Illusion: return Attribute.Personality; - case CharacterContent.Stats.Skill.Security: - case CharacterContent.Stats.Skill.Alchemy: - case CharacterContent.Stats.Skill.Conjuration: - case CharacterContent.Stats.Skill.Enchant: + case Skill.Security: + case Skill.Alchemy: + case Skill.Conjuration: + case Skill.Enchant: return Attribute.Intelligence; - case CharacterContent.Stats.Skill.Alteration: - case CharacterContent.Stats.Skill.Destruction: - case CharacterContent.Stats.Skill.Mysticism: - case CharacterContent.Stats.Skill.Restoration: + case Skill.Alteration: + case Skill.Destruction: + case Skill.Mysticism: + case Skill.Restoration: return Attribute.Willpower; default: throw new Exception("What the fuck"); @@ -315,23 +324,26 @@ public CharacterContent(ESM esm, Cell cell, JsonNode json, Record record) : base /* NPC Specific data */ if (type == ESM.Type.Npc) { - race = (Race)System.Enum.Parse(typeof(Race), record.json["race"].ToString().Replace(" ", "")); - job = record.json["class"].ToString(); - faction = record.json["faction"].ToString().Trim() != "" ? record.json["faction"].ToString() : null; + race = Enum.Parse(record.json["race"]!.GetValue().Replace(" ", "")); + job = record.json["class"]!.GetValue(); + faction = string.IsNullOrWhiteSpace(record.json["faction"]?.GetValue()) ? null : record.json["faction"]!.GetValue(); - sex = record.json["npc_flags"].ToString().ToLower().Contains("female") ? Sex.Female : Sex.Male; + sex = record.json["npc_flags"]!.GetValue().Contains("female") ? Sex.Female : Sex.Male; - disposition = int.Parse(record.json["data"]["disposition"].ToString()); - reputation = int.Parse(record.json["data"]["reputation"].ToString()); - rank = int.Parse(record.json["data"]["rank"].ToString()); + disposition = record.json["data"]!["disposition"]!.GetValue(); + reputation = record.json["data"]!["reputation"]!.GetValue(); + rank = record.json["data"]!["rank"]!.GetValue(); - if (record.json["data"]["stats"] != null) + if (record.json["data"]?["stats"] != null) { - stats = new(record.json["data"]["stats"]); + stats = new(record.json["data"]!["stats"]!); } else { - stats = new(sex, esm.GetRace(record.json["race"].ToString()), esm.GetJob(job), level); + stats = new(sex, + esm.GetRace(record.json["race"]!.GetValue()) ?? throw new Exception($"Could not find race with value '{record.json["race"]!.GetValue()}'"), + esm.GetJob(job) ?? throw new Exception($"Could not find job with value '{job}'"), + level); } } @@ -348,24 +360,25 @@ public CharacterContent(ESM esm, Cell cell, JsonNode json, Record record) : base reputation = 0; rank = 0; - stats = new(record.json["data"], level); + stats = new(record.json["data"]!, level); } /* Generic data used by both NPC and Creature */ - essential = record.json["npc_flags"] != null ? record.json["npc_flags"].GetValue().ToLower().Contains("essential") : false; + essential = record.json["npc_flags"]?.GetValue().ToLower().Contains("essential") ?? false; - level = int.Parse(record.json["data"]["level"].ToString()); - gold = int.Parse(record.json["data"]["gold"].ToString()); + level = record.json["data"]!["level"]!.GetValue(); + gold = record.json["data"]!["gold"]!.GetValue(); - hello = int.Parse(record.json["ai_data"]["hello"].ToString()); - fight = int.Parse(record.json["ai_data"]["fight"].ToString()); - flee = int.Parse(record.json["ai_data"]["flee"].ToString()); - alarm = int.Parse(record.json["ai_data"]["alarm"].ToString()); + hello = record.json["ai_data"]!["hello"]!.GetValue(); + fight = record.json["ai_data"]!["fight"]!.GetValue(); + flee = record.json["ai_data"]!["flee"]!.GetValue(); + alarm = record.json["ai_data"]!["alarm"]!.GetValue(); hostile = fight >= 80; // @TODO: recalc with disposition mods based off UESP calc - dead = record.json["data"]["stats"] != null && record.json["data"]["stats"]["health"] != null ? (int.Parse(record.json["data"]["stats"]["health"].ToString()) <= 0) : false; + + dead = (record.json["data"]?["stats"]?["health"]?.GetValue() ?? 1) <= 0; - string[] serviceFlags = record.json["ai_data"]["services"].ToString().Split("|"); + string[] serviceFlags = record.json["ai_data"]!["services"]!.GetValue().Split("|"); services = new(); foreach (string s in serviceFlags) { @@ -381,27 +394,33 @@ public CharacterContent(ESM esm, Cell cell, JsonNode json, Record record) : base rotation += new Vector3(0f, 180f, 8); // models are rotated during conversion, placements like this are rotated here during serializiation to match inventory = new(); - JsonArray invJson = record.json["inventory"].AsArray(); - foreach(JsonNode node in invJson) + JsonArray invJson = record.json["inventory"]?.AsArray() ?? []; + foreach(var node in invJson) { + if (node == null) + continue; + JsonArray item = node.AsArray(); - inventory.Add(new(item[1].GetValue().ToLower(), Math.Max(1, Math.Abs(item[0].GetValue())))); + if (item.Count < 2) + throw new Exception("NpcContent inventory node is has less than the required elements!"); + inventory.Add(new(item[1]!.GetValue().ToLower(), Math.Max(1, Math.Abs(item[0]!.GetValue())))); } spells = new(); if (record.json["spells"] != null) { - JsonArray spellJson = record.json["spells"].AsArray(); + JsonArray spellJson = record.json["spells"]!.AsArray(); for(int i=0;i().ToLower()); + spells.Add(spellJson[i]!.GetValue().ToLower()); } } travel = new(); - JsonArray travelJson = record.json["travel_destinations"].AsArray(); - foreach (JsonNode t in travelJson) + JsonArray travelJson = record.json["travel_destinations"]?.AsArray() ?? []; + foreach (var t in travelJson) { + if (t == null) continue; travel.Add(new Travel(t)); } } @@ -495,12 +514,12 @@ public class AssetContent : Content { public AssetContent(Cell cell, JsonNode json, Record record) : base(cell, json, record) { - mesh = record.json["mesh"].ToString().ToLower(); + mesh = record.json["mesh"]!.ToString().ToLower(); } public EmitterContent ConvertToEmitter() { - return new EmitterContent(cell, id, name, type, load, papyrus, position, rotation, scale, mesh); + return new EmitterContent(cell, id, name, type, load, papyrus, position, rotation, scale, mesh!); } } @@ -510,23 +529,23 @@ public class DoorContent : Content public class Warp { // this data comes from the esm, we use it to resolve the actual data we will use - public readonly string cell; + public string? cell { get; init; } public readonly Vector3 position, rotation; // this is the actual warp data we generate public int map, x, y, block; public uint entity; - public string prompt; // used for the action button prompt. this is either the cell name, region name, or a generic "Morrowind" as a last case + public string prompt { get; set; } = "Morrowind"; // used for the action button prompt. this is either the cell name, region name, or a generic "Morrowind" as a last case public Warp(JsonNode json) { - float x = float.Parse(json["translation"][0].ToString()); - float z = float.Parse(json["translation"][1].ToString()); - float y = float.Parse(json["translation"][2].ToString()); + float x = float.Parse(json["translation"]?[0]?.ToString() ?? throw new Exception("Warp content missing value translation->x")); + float z = float.Parse(json["translation"]?[1]?.ToString() ?? throw new Exception("Warp content missing value translation->y")); + float y = float.Parse(json["translation"]?[2]?.ToString() ?? throw new Exception("Warp content missing value translation->z")); - float i = float.Parse(json["rotation"][0].ToString()); - float j = float.Parse(json["rotation"][1].ToString()); - float k = float.Parse(json["rotation"][2].ToString()); + float i = float.Parse(json["rotation"]?[0]?.ToString() ?? throw new Exception("Warp content missing value rotation->x")); + float j = float.Parse(json["rotation"]?[1]?.ToString() ?? throw new Exception("Warp content missing value rotation->y")); + float k = float.Parse(json["rotation"]?[2]?.ToString() ?? throw new Exception("Warp content missing value rotation->z")); // Same rotation code as in content, just copy pasted because lol lmao /* Katalashes code from MapStudio */ @@ -563,20 +582,20 @@ Vector3 MatrixToEulerXZY(Matrix4x4 m) position = new Vector3(x, y, z) * Const.GLOBAL_SCALE; rotation = (eu * (float)(180 / Math.PI)) + new Vector3(0f, 180f, 0); // bonus rotation here, actual models get rotated 180 Y in the model itself, placements like this need it here - cell = json["cell"].ToString().Trim(); + cell = json["cell"]?.ToString().Trim(); if (cell == "") { cell = null; } } } - public Warp warp; + public Warp? warp; public DoorContent(Cell cell, JsonNode json, Record record) : base(cell, json, record) { - mesh = record.json["mesh"].ToString().ToLower(); + mesh = record.json["mesh"]?.ToString().ToLower(); if (json["destination"] == null) { warp = null; } else { - warp = new(json["destination"]); + warp = new(json["destination"]!); } } } @@ -584,25 +603,30 @@ public DoorContent(Cell cell, JsonNode json, Record record) : base(cell, json, r /* static mesh of a container in the world that can **CAN** (but not always) be lootable */ public class ContainerContent : Content { - public readonly string ownerNpc; // npc record id of the owenr of this container, can be null - public readonly string ownerFaction; // faction id that owns this container, player can take it if they are in that faction. can be null + public readonly string? ownerNpc; // npc record id of the owenr of this container, can be null + public readonly string? ownerFaction; // faction id that owns this container, player can take it if they are in that faction. can be null public List<(string id, int quantity)> inventory; - public Script.Flag treasure; // if this container content has a treasure event and is a lootable container, this flag will be the "has been looted" flag. otherwise null + public Script.Flag? treasure; // if this container content has a treasure event and is a lootable container, this flag will be the "has been looted" flag. otherwise null public ContainerContent(Cell cell, JsonNode json, Record record) : base(cell, json, record) { - mesh = record.json["mesh"].ToString().ToLower(); - if (json["owner"] != null) { ownerNpc = json["owner"].GetValue(); } - if (json["owner_faction"] != null) { ownerFaction = json["owner_faction"].GetValue(); } + mesh = record.json["mesh"]?.ToString().ToLower(); + ownerNpc = json["owner"]?.GetValue(); + ownerFaction = json["owner_faction"]?.GetValue(); inventory = new(); - JsonArray invJson = record.json["inventory"].AsArray(); - foreach (JsonNode node in invJson) + JsonArray invJson = record.json["inventory"]?.AsArray() ?? []; + foreach (var node in invJson) { + if (node == null) + continue; JsonArray item = node.AsArray(); - inventory.Add(new(item[1].GetValue().ToLower(), Math.Max(1, Math.Abs(item[0].GetValue())))); // get item record id and quantity from json + if (item.Count < 2) + throw new Exception("ContainerContent inventory node has less than the required elements!"); + + inventory.Add(new(item[1]!.GetValue().ToLower(), Math.Max(1, Math.Abs(item[0]!.GetValue())))); // get item record id and quantity from json } } @@ -621,14 +645,18 @@ public class PickableContent : Content public PickableContent(Cell cell, JsonNode json, Record record) : base(cell, json, record) { - mesh = record.json["mesh"].ToString().ToLower(); + mesh = record.json["mesh"]?.ToString().ToLower(); inventory = new(); - JsonArray invJson = record.json["inventory"].AsArray(); - foreach (JsonNode node in invJson) + JsonArray invJson = record.json["inventory"]?.AsArray() ?? []; + foreach (var node in invJson) { + if (node == null) continue; JsonArray item = node.AsArray(); - inventory.Add(new(item[1].GetValue().ToLower(), Math.Max(1, Math.Abs(item[0].GetValue())))); // get item record id and quantity from json + if (item.Count < 2) + throw new Exception("PickableContent inventory node has less than the required values!"); + + inventory.Add(new(item[1]!.GetValue().ToLower(), Math.Max(1, Math.Abs(item[0]!.GetValue())))); // get item record id and quantity from json } } @@ -642,19 +670,19 @@ public string ActionText() /* static mesh of an item placed in the world that can **CAN** (but not always) be pickupable */ public class ItemContent : Content { - public readonly string ownerNpc; // npc record id of the owenr of this item, can be null - public readonly string ownerFaction; // faction id that owns this item, player can take it if they are in that faction. can be null + public readonly string? ownerNpc; // npc record id of the owenr of this item, can be null + public readonly string? ownerFaction; // faction id that owns this item, player can take it if they are in that faction. can be null public readonly int value; // morrowind gp value for this item - public Script.Flag treasure; // if this item content has a treasure event and is a lootable item, this flag will be the "is picked up" flag. otherwise null + public Script.Flag? treasure; // if this item content has a treasure event and is a lootable item, this flag will be the "is picked up" flag. otherwise null public ItemContent(Cell cell, JsonNode json, Record record) : base(cell, json, record) { - mesh = record.json["mesh"].ToString().ToLower(); - if (json["owner"] != null ) { ownerNpc = json["owner"].GetValue(); } - if (json["owner_faction"] != null) { ownerFaction = json["owner_faction"].GetValue(); } - value = record.json["data"]["value"].GetValue(); + mesh = record.json["mesh"]?.ToString().ToLower(); + ownerNpc = json["owner"]?.GetValue(); + ownerFaction = json["owner_faction"]?.GetValue(); + value = record.json["data"]?["value"]?.GetValue() ?? 1000; // Default to 1k } // Generates button prompt text for looting this container @@ -670,10 +698,10 @@ public class EmitterContent : Content { public EmitterContent(Cell cell, JsonNode json, Record record) : base(cell, json, record) { - mesh = record.json["mesh"].ToString().ToLower(); + mesh = record.json["mesh"]?.ToString().ToLower(); } - public EmitterContent(Cell cell, string id, string name, ESM.Type type, Int2 load, string papyrus, Vector3 position, Vector3 rotation, int scale, string mesh) : base(cell, id, name, type, load, papyrus, position, rotation, scale) + public EmitterContent(Cell cell, string id, string? name, ESM.Type type, Int2 load, string? papyrus, Vector3 position, Vector3 rotation, int scale, string mesh) : base(cell, id, name, type, load, papyrus, position, rotation, scale) { this.mesh = mesh; } @@ -693,19 +721,19 @@ public enum Mode { Flicker, FlickerSlow, Pulse, PulseSlow, Default } public LightContent(Cell cell, JsonNode json, Record record) : base(cell, json, record) { - int r = int.Parse(record.json["data"]["color"][0].ToString()); - int g = int.Parse(record.json["data"]["color"][1].ToString()); - int b = int.Parse(record.json["data"]["color"][2].ToString()); - int a = int.Parse(record.json["data"]["color"][3].ToString()); + int r = record.json["data"]!["color"]![0]!.GetValue(); + int g = record.json["data"]!["color"]![1]!.GetValue(); + int b = record.json["data"]!["color"]![2]!.GetValue(); + int a = record.json["data"]!["color"]![3]!.GetValue(); color = new(r, g, b, a); // 0 -> 255 colors - radius = float.Parse(record.json["data"]["radius"].ToString()) * Const.GLOBAL_SCALE; - weight = float.Parse(record.json["data"]["weight"].ToString()); + radius = record.json["data"]!["radius"]!.GetValue() * Const.GLOBAL_SCALE; + weight = record.json["data"]!["weight"]!.GetValue(); - value = int.Parse(record.json["data"]["value"].ToString()); - time = int.Parse(record.json["data"]["time"].ToString()); + value = record.json["data"]!["value"]!.GetValue(); + time = record.json["data"]!["time"]!.GetValue(); - string flags = record.json["data"]["flags"].ToString(); + string flags = record.json["data"]!["flags"]!.GetValue(); dynamic = flags.Contains("DYNAMIC"); fire = flags.Contains("FIRE"); diff --git a/JortPob/ESM/ESM.cs b/JortPob/ESM/ESM.cs index fd96cfd..acba635 100644 --- a/JortPob/ESM/ESM.cs +++ b/JortPob/ESM/ESM.cs @@ -41,28 +41,28 @@ public enum Type public ESM(ScriptManager scriptManager) { /* Check if a json has been generated from the esm, if not make one */ - string jsonPath = $"{Const.CACHE_PATH}morrowind.json"; + string jsonPath = Path.Combine(Const.CACHE_PATH, "morrowind.json"); if (!File.Exists(jsonPath)) { /* Merge load order to a single file using merge_to_master */ string esmPath; if (Const.LOAD_ORDER.Length == 1) { - esmPath = $"{Const.MORROWIND_PATH}Data Files\\{Const.LOAD_ORDER[0]}"; + esmPath = Path.Combine(Const.MORROWIND_PATH, "Data Files", Const.LOAD_ORDER[0]); } else { // Copy our master esm to the cache folder - esmPath = $"{Const.CACHE_PATH}morrowind.esm"; + esmPath = Path.Combine(Const.CACHE_PATH, "morrowind.esm"); if(File.Exists(esmPath)) { File.Delete(esmPath); } - if(!Directory.Exists(Const.CACHE_PATH)) { Directory.CreateDirectory(Const.CACHE_PATH); } - File.Copy($"{Const.MORROWIND_PATH}Data Files\\{Const.LOAD_ORDER[0]}", esmPath); + Directory.CreateDirectory(Const.CACHE_PATH); + File.Copy(Path.Combine(Const.MORROWIND_PATH, "Data Files", Const.LOAD_ORDER[0]), esmPath); // Merge the rest of the load order into that esm for (int i=1;i().ToLower(); texturesByIndex.Add( index, - new Texture(ltjson["id"].ToString().ToLower(), $"{Const.MORROWIND_PATH}Data Files\\textures\\{ltjson["file_name"].ToString().ToLower().Substring(0, ltjson["file_name"].ToString().Length - 4)}.dds", index) + new Texture(ltjson["id"].ToString().ToLower(), Path.Combine(Const.MORROWIND_PATH, "Data Files", "textures", Path.ChangeExtension(fileName, "dds")), index) ); } else diff --git a/JortPob/EnvManager.cs b/JortPob/EnvManager.cs index 973eda4..5c89b40 100644 --- a/JortPob/EnvManager.cs +++ b/JortPob/EnvManager.cs @@ -2,8 +2,11 @@ using SoulsFormats; using System; using System.Collections.Generic; +using System.IO; using static JortPob.Paramanager; +#nullable enable + namespace JortPob { public class EnvManager @@ -14,7 +17,7 @@ public enum Rem } // returns a list of texture names and bytes for them - private static byte[] defEnvX, defEnvY, defEnvZ, defEnvW; // function to gen these is slow so hold onto the bytes to reuse + private static byte[]? defEnvX, defEnvY, defEnvZ, defEnvW; // function to gen these is slow so hold onto the bytes to reuse public static List> GenerateIrradianceTextures(int map, int x, int y, int block, int id, int time, int size, Rem rem) { string[] names = new string[] @@ -34,12 +37,14 @@ public static List> GenerateIrradianceTextures(int map, in defEnvW = Common.DDS.MakeVolumeTexture(16, 250, 250, 250, 55); } - List> textures = new(); - textures.Add(new(names[0], defEnvW)); - textures.Add(new(names[1], defEnvX)); - textures.Add(new(names[2], defEnvY)); - textures.Add(new(names[3], defEnvZ)); - textures.Add(new(names[4], System.IO.File.ReadAllBytes(Utility.ResourcePath($"env\\{rem.ToString().ToLower()}_rem.dds")))); + List> textures = + [ + new(names[0], defEnvW), + new(names[1], defEnvX), + new(names[2], defEnvY), + new(names[3], defEnvZ), + new(names[4], System.IO.File.ReadAllBytes(Utility.ResourcePath($"env\\{rem.ToString().ToLower()}_rem.dds"))), + ]; return textures; } @@ -100,7 +105,7 @@ public static void CreateEnvMaps(HugeTile tile, int envId) bnd.Files.Add(file); } - bnd.Write($"{Const.OUTPUT_PATH}map\\m{tile.map:D2}\\m{mid}\\m{mid}_envmap_{timeId:D2}_{level}_00.tpfbnd.dcx"); + bnd.Write(Path.Combine(Const.OUTPUT_PATH, $@"map\m{tile.map:D2}\m{mid}\m{mid}_envmap_{timeId:D2}_{level}_00.tpfbnd.dcx")); } } @@ -124,7 +129,7 @@ public static void CreateEnvMaps(HugeTile tile, int envId) ivBnd.Files.Add(ivFile); } - ivBnd.Write($"{Const.OUTPUT_PATH}map\\m{tile.map.ToString("D2")}\\m{mid}\\m{mid}_{level}.ivinfobnd.dcx"); + ivBnd.Write(Path.Combine(Const.OUTPUT_PATH, $@"map\m{tile.map.ToString("D2")}\m{mid}\m{mid}_{level}.ivinfobnd.dcx")); } } @@ -180,7 +185,7 @@ public static void CreateEnvMaps(InteriorGroup group, int envId) bnd.Files.Add(file); } - bnd.Write($"{Const.OUTPUT_PATH}map\\m{group.map:D2}\\m{mid}\\m{mid}_envmap_{0:D2}_{level}_00.tpfbnd.dcx"); + bnd.Write(Path.Combine(Const.OUTPUT_PATH, $@"map\m{group.map:D2}\m{mid}\m{mid}_envmap_{0:D2}_{level}_00.tpfbnd.dcx")); } @@ -201,7 +206,7 @@ public static void CreateEnvMaps(InteriorGroup group, int envId) ivFile.Name = $"N:\\GR\\data\\INTERROOT_win64\\map\\m{mid}\\tex\\Envmap\\{level}\\IvInfo\\m{mid}_GIIV{envId}_{0:D2}.ivInfo"; ivBnd.Files.Add(ivFile); - ivBnd.Write($"{Const.OUTPUT_PATH}map\\m{group.map:D2}\\m{mid}\\m{mid}_{level}.ivinfobnd.dcx"); + ivBnd.Write(Path.Combine(Const.OUTPUT_PATH, $@"map\m{group.map:D2}\m{mid}\m{mid}_{level}.ivinfobnd.dcx")); } } } diff --git a/JortPob/FxrManager.cs b/JortPob/FxrManager.cs index 6f8d6e1..eae8e0e 100644 --- a/JortPob/FxrManager.cs +++ b/JortPob/FxrManager.cs @@ -176,7 +176,7 @@ public static void Write(Layout layout) Lort.NewTask($"Writing {maps.Count} FFX Binder files... ", maps.Count); foreach (int map in maps) { - ffxbnd.Write($"{Const.OUTPUT_PATH}sfx\\sfxbnd_m{map.ToString("D2")}.ffxbnd.dcx"); + ffxbnd.Write(Path.Combine(Const.OUTPUT_PATH, $@"sfx\sfxbnd_m{map.ToString("D2")}.ffxbnd.dcx")); Lort.TaskIterate(); } } diff --git a/JortPob/IconManager.cs b/JortPob/IconManager.cs index 6c6ad01..d4d65b3 100644 --- a/JortPob/IconManager.cs +++ b/JortPob/IconManager.cs @@ -141,7 +141,7 @@ public void Write() List<(IconInfo iconInfo, Bitmap bitmap)> bitmaps = new(); foreach (IconInfo icon in icons) { - byte[] ddsBytes = System.IO.File.ReadAllBytes($"{Const.MORROWIND_PATH}Data Files\\icons\\{icon.path}"); + byte[] ddsBytes = File.ReadAllBytes(Path.Combine(Const.MORROWIND_PATH, "Data Files", "icons", icon.path)); using Bitmap bitmap = Common.DDS.DDStoBitmap(ddsBytes); Bitmap scaledBitmap = Common.Utility.XbrzUpscale(bitmap, 5); bitmaps.Add((icon, scaledBitmap)); @@ -171,6 +171,7 @@ public void Write() layout.Add(icon.id, new Int2(x * (ICON + PAD), y * (ICON + PAD)), new Int2(ICON, ICON)); + bitmap.Dispose(); i++; Lort.TaskIterate(); } @@ -193,11 +194,11 @@ public void Write() List<(BuffInfo buffInfo, Bitmap bitmap)> buffBitmaps = new(); foreach (BuffInfo buff in buffs) { - byte[] ddsBytes = System.IO.File.ReadAllBytes($"{Const.MORROWIND_PATH}Data Files\\icons\\{buff.path}"); - Bitmap buffBitmap = Common.DDS.DDStoBitmap(ddsBytes); - buffBitmap = Common.Utility.XbrzUpscale(buffBitmap, 4); - buffBitmap = Utility.ResizeBitmap(buffBitmap, BUFF, BUFF); - buffBitmaps.Add((buff, buffBitmap)); + byte[] ddsBytes = File.ReadAllBytes(Path.Combine(Const.MORROWIND_PATH, "Data Files", "icons", buff.path)); + using Bitmap buffBitmap = Common.DDS.DDStoBitmap(ddsBytes); + using var scaledBuffBitmap = Common.Utility.XbrzUpscale(buffBitmap, 4); + var resizedScaledBuffBitmap = Utility.ResizeBitmap(buffBitmap, BUFF, BUFF); + buffBitmaps.Add((buff, resizedScaledBuffBitmap)); } /* Make buff sheets */ @@ -221,6 +222,7 @@ public void Write() buffLayout.Add(buff.id, new Int2(x * (BUFF + BUFF_PAD), y * (BUFF + BUFF_PAD)), new Int2(BUFF, BUFF)); + buffBitmap.Dispose(); i++; Lort.TaskIterate(); } @@ -236,16 +238,15 @@ public void Write() void AddSheets(string layoutPath, string tpfPath, string hilow) { - TPF tpf = TPF.Read($"{Const.ELDEN_PATH}Game\\{tpfPath}"); - BND4 bnd = BND4.Read($"{Const.ELDEN_PATH}Game\\{layoutPath}"); + TPF tpf = TPF.Read(Path.Combine(Const.ELDEN_PATH, "Game", tpfPath)); + BND4 bnd = BND4.Read(Path.Combine(Const.ELDEN_PATH, "Game", layoutPath)); foreach ((Layout layout, Bitmap bitmap) tuple in sheets) { /* Add sheet to TPF */ TPF.Texture texture = new(); - Bitmap linearBitmap = Common.Utility.LinearToSRGB(tuple.bitmap); + using Bitmap linearBitmap = Common.Utility.LinearToSRGB(tuple.bitmap); texture.Bytes = Common.DDS.BitmapToDDS(linearBitmap, DXGI_FORMAT.BC2_UNORM); - linearBitmap.Dispose(); texture.Format = (byte)Common.DDS.GetTpfFormatFromDdsBytes(texture.Bytes); texture.Name = tuple.layout.name; tpf.Textures.Add(texture); @@ -258,8 +259,8 @@ void AddSheets(string layoutPath, string tpfPath, string hilow) bnd.Files.Add(bf); } - tpf.Write($"{Const.OUTPUT_PATH}{tpfPath}"); - bnd.Write($"{Const.OUTPUT_PATH}{layoutPath}"); + tpf.Write(Path.Combine(Const.OUTPUT_PATH, tpfPath)); + bnd.Write(Path.Combine(Const.OUTPUT_PATH, layoutPath)); } Lort.Log($"Binding {sheets.Count()} sheets...", Lort.Type.Main); @@ -268,8 +269,8 @@ void AddSheets(string layoutPath, string tpfPath, string hilow) AddSheets(lowLayoutPath, lowTpfPath, "Low"); Lort.TaskIterate(); /* Clean up */ - foreach ((Layout layout, Bitmap bitmap) tuple in sheets) { tuple.bitmap.Dispose(); } - foreach ((IconInfo iconInfo, Bitmap bitmap) tuple in bitmaps) { tuple.bitmap.Dispose(); } + sheets.ForEach(tuple => tuple.bitmap.Dispose()); + bitmaps.ForEach(tuple => tuple.bitmap.Dispose()); /* Do large icons for use in the description area */ const string hiPath = @"menu\hi\00_solo"; @@ -281,20 +282,19 @@ void AddSheets(string layoutPath, string tpfPath, string hilow) void AddIcons(string path) { /* Load BXF4 from elden ring directory (requires game unpacked!) */ - BXF4 bxf = BXF4.Read($"{Const.ELDEN_PATH}Game\\{path}.tpfbhd", $"{Const.ELDEN_PATH}Game\\{path}.tpfbdt"); + BXF4 bxf = BXF4.Read(Path.Combine(Const.ELDEN_PATH, "Game", $"{path}.tpfbhd"), + Path.Combine(Const.ELDEN_PATH, "Game", $"{path}.tpfbdt")); List filesToInsert = new(); /* Generate binder files to add from dds texture files */ foreach(IconInfo icon in icons) { - byte[] data = System.IO.File.ReadAllBytes($"{Const.MORROWIND_PATH}Data Files\\icons\\{icon.path}"); + byte[] data = File.ReadAllBytes(Path.Combine(Const.MORROWIND_PATH, "Data Files", "icons", icon.path)); - Bitmap bitmap = Common.DDS.DDStoBitmap(data); - Bitmap scaledBitmap = Common.Utility.XbrzUpscale(bitmap, 6); // 32x32x -> 192x192 - Bitmap linearScaledBitmap = Common.Utility.LinearToSRGB(scaledBitmap); + using Bitmap bitmap = Common.DDS.DDStoBitmap(data); + using Bitmap scaledBitmap = Common.Utility.XbrzUpscale(bitmap, 6); // 32x32x -> 192x192 + using Bitmap linearScaledBitmap = Common.Utility.LinearToSRGB(scaledBitmap); byte[] scaledDDS = Common.DDS.BitmapToDDS(linearScaledBitmap, DXGI_FORMAT.BC2_UNORM); - scaledBitmap.Dispose(); - linearScaledBitmap.Dispose(); int format = JortPob.Common.DDS.GetTpfFormatFromDdsBytes(scaledDDS); @@ -326,7 +326,7 @@ void AddIcons(string path) } /* Write */ - bxf.Write($"{Const.OUTPUT_PATH}{path}.tpfbhd", $"{Const.OUTPUT_PATH}{path}.tpfbdt"); + bxf.Write(Path.Combine(Const.OUTPUT_PATH, $"{path}.tpfbhd"), Path.Combine(Const.OUTPUT_PATH, $"{path}.tpfbdt")); } Lort.Log($"Binding {icons.Count()} previews...", Lort.Type.Main); diff --git a/JortPob/JortPob.csproj b/JortPob/JortPob.csproj index c745f19..1d19876 100644 --- a/JortPob/JortPob.csproj +++ b/JortPob/JortPob.csproj @@ -5,6 +5,8 @@ net9.0-windows x64 true + + disable diff --git a/JortPob/LightManager.cs b/JortPob/LightManager.cs index e9a8ed9..d301756 100644 --- a/JortPob/LightManager.cs +++ b/JortPob/LightManager.cs @@ -1,5 +1,6 @@ using JortPob.Common; using SoulsFormats; +using System.IO; using System.Linq; using System.Numerics; @@ -104,7 +105,7 @@ public void CreateLight(LightContent mwl) public void Write() { - string path = $"{Const.OUTPUT_PATH}map\\m{map:D2}\\m{map:D2}_{x:D2}_{y:D2}_{block:D2}\\m{map:D2}_{x:D2}_{y:D2}_{block:D2}_0000.btl.dcx"; + string path = Path.Combine(Const.OUTPUT_PATH, $@"map\m{map:D2}\m{map:D2}_{x:D2}_{y:D2}_{block:D2}\m{map:D2}_{x:D2}_{y:D2}_{block:D2}_0000.btl.dcx"); btl.Write(path, DCX.Type.DCX_DFLT_10000_44_9); //btab.Write($"{path}.btab.dcx", DCX.Type.DCX_DFLT_10000_44_9); } diff --git a/JortPob/LiquidManager.cs b/JortPob/LiquidManager.cs index b667c3e..ed6862d 100644 --- a/JortPob/LiquidManager.cs +++ b/JortPob/LiquidManager.cs @@ -58,7 +58,7 @@ public static List GenerateLiquids(ESM esm, MaterialContext material /* Files happen */ string flverPath = @"water\super_water.flver"; - flver.Write($"{Const.CACHE_PATH}{flverPath}"); + flver.Write(Path.Combine(Const.CACHE_PATH, flverPath)); /* make a waterinfo class about this generated water */ LiquidInfo waterInfo = new(0, flverPath); @@ -68,7 +68,7 @@ public static List GenerateLiquids(ESM esm, MaterialContext material WetMesh wetcollision = tuple.Item2; Obj obj = wetcollision.ToObj(Obj.CollisionMaterial.Water).optimize(); string objPath = $"water\\collision[{coordinate.x},{coordinate.y}].obj"; - obj.write($"{Const.CACHE_PATH}{objPath}"); + obj.write(Path.Combine(Const.CACHE_PATH, objPath)); CollisionInfo collisionInfo = new($"water collision[{coordinate.x}, {coordinate.y}]", objPath); waterInfo.AddCollision(coordinate, collisionInfo); @@ -81,7 +81,7 @@ public static List GenerateLiquids(ESM esm, MaterialContext material WetMesh lavaMesh = new WetMesh(GetCutoutType(Cutout.Type.Lava, true), Cutout.Type.Lava); FLVER2 lavaFlver = GenerateLavaFlver(lavaMesh, materialContext); string lavaFlverPath = @"water\super_lava.flver"; - lavaFlver.Write($"{Const.CACHE_PATH}{lavaFlverPath}"); + lavaFlver.Write(Path.Combine(Const.CACHE_PATH, lavaFlverPath)); LiquidInfo lavaInfo = new(2, lavaFlverPath); @@ -90,7 +90,7 @@ public static List GenerateLiquids(ESM esm, MaterialContext material WetMesh swampMesh = new WetMesh(GetCutoutType(Cutout.Type.Swamp, true), Cutout.Type.Swamp); FLVER2 swampFlver = GenerateSwampFlver(swampMesh, materialContext); string swampFlverPath = @"water\super_swamp.flver"; - swampFlver.Write($"{Const.CACHE_PATH}{swampFlverPath}"); + swampFlver.Write(Path.Combine(Const.CACHE_PATH, swampFlverPath)); LiquidInfo swampInfo = new(1, swampFlverPath); return new List() { waterInfo, swampInfo, lavaInfo }; @@ -199,7 +199,7 @@ public static List GenerateCutouts(ESM esm) Int2 coordinate = tuple.Item1; Obj obj = tuple.Item2; string objPath = $"cutout\\collision[{coordinate.x},{coordinate.y}].obj"; - obj.write($"{Const.CACHE_PATH}{objPath}"); + obj.write(Path.Combine(Const.CACHE_PATH, objPath)); CollisionInfo collisionInfo = new($"cutout collision[{coordinate.x}, {coordinate.y}]", objPath); CutoutInfo cutoutInfo = new(coordinate, collisionInfo); @@ -1843,15 +1843,15 @@ bool InsideCutout(WetFace f) } /* test the new edges of this triangle, skip outline edge */ // not used - bool BaseSkipIntersectTest(WetFace f) - { - foreach (WetEdge cutedge in cutout.Edges()) - { - if (!cutedge.Intersection(new WetEdge(f.a, f.b), false).IsNaN()) { return true; } - if (!cutedge.Intersection(new WetEdge(f.c, f.b), false).IsNaN()) { return true; } - } - return false; - } + //bool BaseSkipIntersectTest(WetFace f) + //{ + // foreach (WetEdge cutedge in cutout.Edges()) + // { + // if (!cutedge.Intersection(new WetEdge(f.a, f.b), false).IsNaN()) { return true; } + // if (!cutedge.Intersection(new WetEdge(f.c, f.b), false).IsNaN()) { return true; } + // } + // return false; + //} /* Check if they are valid, then add them if they are */ cutout.size -= 0.1f; @@ -2054,7 +2054,7 @@ public ShapedCutout(Type type, Vector3 position, Vector3 rotation, string meshPa this.mesh = new(); AssimpContext assimpContext = new(); - Scene fbx = assimpContext.ImportFile($"{Const.MORROWIND_PATH}Data Files\\meshes\\{meshPath.Replace(".nif", ".fbx")}"); + Scene fbx = assimpContext.ImportFile(Path.Combine(Const.MORROWIND_PATH, "Data Files", "meshes", meshPath.Replace(".nif", ".fbx"))); Mesh mesh = null; // find first mesh that's not collision and use it Node node = null; @@ -2440,6 +2440,11 @@ public bool IsIntersect(List B) return false; } + + public override int GetHashCode() + { + return HashCode.Combine(a, b, c); + } } public class WetEdge @@ -2518,6 +2523,11 @@ public Vector3 Intersection(WetEdge B, bool edgeInclusive) return Vector3.NaN; // no intersection } + + public override int GetHashCode() + { + return HashCode.Combine(a, b); + } } } } diff --git a/JortPob/Main.cs b/JortPob/Main.cs index 3d5897e..cfd80b7 100644 --- a/JortPob/Main.cs +++ b/JortPob/Main.cs @@ -825,7 +825,7 @@ public static void Convert() if (param.param[Paramanager.ParamType.TalkParam].Rows.Count() >= ushort.MaxValue) { throw new Exception("Ran out of talk param rows! Will fail to compile params!"); } /* Write sound BNKs */ - sound.Write($"{Const.OUTPUT_PATH}sd\\enus\\"); + sound.Write(Path.Combine(Const.OUTPUT_PATH, "sd", "enus")); /* Write ESD bnds */ character.Write(); @@ -849,14 +849,14 @@ public static void Convert() /* Write FMGs */ Lort.Log($"Binding FMGs...", Lort.Type.Main); - text.Write($"{Const.OUTPUT_PATH}msg\\engus\\"); + text.Write(Path.Combine(Const.OUTPUT_PATH, "msg", "engus")); /* Write FXR files */ Lort.Log($"Binding FXRs...", Lort.Type.Main); FxrManager.Write(layout); /* Bind and write all materials and textures */ - Bind.BindMaterials($"{Const.OUTPUT_PATH}material\\allmaterial.matbinbnd.dcx"); + Bind.BindMaterials(Path.Combine(Const.OUTPUT_PATH, $@"material\allmaterial.matbinbnd.dcx")); Bind.BindTPF(cache, layout.ListCommon()); icon.Write(); @@ -868,7 +868,7 @@ public static void Convert() Bind.BindPickables(cache); foreach (LiquidInfo water in cache.liquids) // bind up them waters toooooo { - Bind.BindAsset(water, $"{Const.OUTPUT_PATH}asset\\aeg\\{water.AssetPath()}.geombnd.dcx"); + Bind.BindAsset(water, Path.Combine(Const.OUTPUT_PATH, $@"asset\aeg\{water.AssetPath()}.geombnd.dcx")); } /* Generate overworld */ diff --git a/JortPob/Model/MaterialContext.cs b/JortPob/Model/MaterialContext.cs index 18cf73b..eb2d0a3 100644 --- a/JortPob/Model/MaterialContext.cs +++ b/JortPob/Model/MaterialContext.cs @@ -205,11 +205,11 @@ public List GenerateMaterials(List texturePaths) } else if (relpath.ToLower().StartsWith("textures\\")) { - abspath = $"{Const.MORROWIND_PATH}Data Files\\{relpath}"; + abspath = Path.Combine(Const.MORROWIND_PATH, "Data Files", relpath); } else { - abspath = $"{Const.MORROWIND_PATH}Data Files\\Textures\\{relpath}"; + abspath = Path.Combine(Const.MORROWIND_PATH, "Data Files", "Textures", relpath); } string fileName = Utility.PathToFileName(abspath); @@ -235,11 +235,11 @@ public MaterialInfo GenerateMaterial(string texturePath, int materialIndex) { if (texturePath.ToLower().StartsWith("textures\\")) { - texturePath = $"{Const.MORROWIND_PATH}Data Files\\{texturePath}"; + texturePath = Path.Combine(Const.MORROWIND_PATH, "Data Files", texturePath); } else { - texturePath = $"{Const.MORROWIND_PATH}Data Files\\Textures\\{texturePath}"; + texturePath = Path.Combine(Const.MORROWIND_PATH, "Data Files", "Textures", texturePath); } if (Path.GetExtension(texturePath) == string.Empty) @@ -771,7 +771,7 @@ public MaterialInfo GenerateMaterialLava(int index) public MaterialInfo GenerateMaterialSwamp(int index) { - string diffuseTextureSourcePathA = $"{Const.MORROWIND_PATH}Data Files\\textures\\tx_bc_scum.dds"; // hardcoded swamp texture + string diffuseTextureSourcePathA = Path.Combine(Const.MORROWIND_PATH, "Data Files", "textures", "tx_bc_scum.dds"); // hardcoded swamp texture string diffuseTextureA; string AddTexture(string diffuseTextureSourcePath) { @@ -828,7 +828,7 @@ public void WriteAll() foreach(KeyValuePair kvp in genMATBINs) { string outFileName = $"{Utility.PathToFileName(kvp.Value.SourcePath)}.matbin"; - kvp.Value.Write($"{Const.CACHE_PATH}materials\\{outFileName}"); + kvp.Value.Write(Path.Combine(Const.CACHE_PATH, "materials", outFileName)); Lort.TaskIterate(); } @@ -874,7 +874,7 @@ public void WriteAll() TPF.Texture tex = new($"{kvp.Value}", (byte)format, 0, data, TPF.TPFPlatform.PC); tpf.Textures.Add(tex); - tpf.Write($"{Const.CACHE_PATH}textures\\{kvp.Value}.tpf.dcx"); + tpf.Write(Path.Combine(Const.CACHE_PATH, "textures", $"{kvp.Value}.tpf.dcx")); } /* And then, make a low detail texture for lods and bind that up */ @@ -890,7 +890,7 @@ public void WriteAll() TPF.Texture tex = new($"{kvp.Value}_l", (byte)format, 0, dataLow, TPF.TPFPlatform.PC); tpf.Textures.Add(tex); - tpf.Write($"{Const.CACHE_PATH}textures\\{kvp.Value}_l.tpf.dcx"); + tpf.Write(Path.Combine(Const.CACHE_PATH, "textures", $"{kvp.Value}_l.tpf.dcx")); } } else @@ -911,7 +911,7 @@ public void WriteAll() TPF.Texture tex = new($"{kvp.Value}", (byte)format, 0, errorData, TPF.TPFPlatform.PC); tpf.Textures.Add(tex); - tpf.Write($"{Const.CACHE_PATH}textures\\{kvp.Value}.tpf.dcx"); + tpf.Write(Path.Combine(Const.CACHE_PATH, "textures", $"{kvp.Value}.tpf.dcx")); } /* And then, make a low detail texture for lods and bind that up */ @@ -927,7 +927,7 @@ public void WriteAll() TPF.Texture tex = new($"{kvp.Value}_l", (byte)format, 0, errorDataLow, TPF.TPFPlatform.PC); tpf.Textures.Add(tex); - tpf.Write($"{Const.CACHE_PATH}textures\\{kvp.Value}_l.tpf.dcx"); + tpf.Write(Path.Combine(Const.CACHE_PATH, "textures", $"{kvp.Value}_l.tpf.dcx")); } } diff --git a/JortPob/NpcManager.cs b/JortPob/NpcManager.cs index a4d6261..a095476 100644 --- a/JortPob/NpcManager.cs +++ b/JortPob/NpcManager.cs @@ -3,6 +3,7 @@ using SoulsFormats; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text.RegularExpressions; using static JortPob.Dialog; @@ -195,8 +196,8 @@ public int GetESD(int[] msbIdList, CharacterContent content) areaScript.RegisterNpcHello(content); // setup hello flags and turntoplayer script DialogESD dialogEsd = new(esm, scriptManager, paramanager, textManager, itemManager, areaScript, (uint)esdId, content, data); - string pyPath = $"{Const.CACHE_PATH}esd\\t{esdId}.py"; - string esdPath = $"{Const.CACHE_PATH}esd\\t{esdId}.esd"; + string pyPath = Path.Combine(Const.CACHE_PATH, "esd", $"t{esdId}.py"); + string esdPath = Path.Combine(Const.CACHE_PATH, "esd", $"t{esdId}.esd"); dialogEsd.Write(pyPath); EsdInfo esdInfo = new(pyPath, esdPath, content.id, esdId); @@ -248,7 +249,7 @@ public void Write() int area = kvp.Key.Item2; BND4 bnd = kvp.Value; - bnd.Write($"{Const.OUTPUT_PATH}script\\talk\\m{map:D2}_{area:D2}_00_00.talkesdbnd.dcx"); + bnd.Write(Path.Combine(Const.OUTPUT_PATH, $@"script\talk\m{map:D2}_{area:D2}_00_00.talkesdbnd.dcx")); Lort.TaskIterate(); } } diff --git a/JortPob/Paramanager.cs b/JortPob/Paramanager.cs index 4b2854c..52c225f 100644 --- a/JortPob/Paramanager.cs +++ b/JortPob/Paramanager.cs @@ -245,7 +245,7 @@ public void Write() Lort.TaskIterate(); } - SFUtil.EncryptERRegulation($"{Const.OUTPUT_PATH}regulation.bin", bnd); + SFUtil.EncryptERRegulation(Path.Combine(Const.OUTPUT_PATH, "regulation.bin"), bnd); } /* picks the partdrawparam for an asset based on its size. smaller assets have shorter render distance etc */ @@ -804,7 +804,7 @@ public int GenerateActionButtonDoorParam(ModelInfo modelInfo, string text) { int rowId = nextActionButtonId++; - FLVER2 flver = FLVER2.Read($"{Const.CACHE_PATH}{modelInfo.path}"); // load flver of this door so we can look at its bounding box + FLVER2 flver = FLVER2.Read(Path.Combine(Const.CACHE_PATH, modelInfo.path)); // load flver of this door so we can look at its bounding box float x = flver.Nodes[0].BoundingBoxMax.X - flver.Nodes[0].BoundingBoxMin.X; float z = flver.Nodes[0].BoundingBoxMax.Z - flver.Nodes[0].BoundingBoxMin.Z; float width = x > z ? x : z; diff --git a/JortPob/Script.cs b/JortPob/Script.cs index 4dea195..9212041 100644 --- a/JortPob/Script.cs +++ b/JortPob/Script.cs @@ -5,6 +5,7 @@ using SoulsIds; using System; using System.Collections.Generic; +using System.IO; using System.Reflection.Metadata.Ecma335; using static JortPob.Script.Flag; using static SoulsFormats.MSB1.Event; @@ -64,7 +65,7 @@ public Script(ScriptCommon common, int map, int x, int y, int block) AUTO = new(Utility.ResourcePath(@"script\\er-common.emedf.json"), true, true); - EMEVD DEBUGTESTDELETE = EMEVD.Read($"{Const.ELDEN_PATH}\\game\\event\\m60_42_36_00.emevd.dcx"); + EMEVD DEBUGTESTDELETE = EMEVD.Read(Path.Combine(Const.ELDEN_PATH, "game", "event", "m60_42_36_00.emevd.dcx")); emevd = new EMEVD(); emevd.Compression = SoulsFormats.DCX.Type.DCX_KRAK; @@ -374,7 +375,7 @@ public Flag FindFlagByLookupKey(ScriptFlagLookupKey key) public void Write() { - emevd.Write($"{Const.OUTPUT_PATH}\\event\\m{map:D2}_{x:D2}_{y:D2}_{block:D2}.emevd.dcx"); + emevd.Write(Path.Combine(Const.OUTPUT_PATH, $@"event\m{map:D2}_{x:D2}_{y:D2}_{block:D2}.emevd.dcx")); } public class Flag diff --git a/JortPob/ScriptCommon.cs b/JortPob/ScriptCommon.cs index c69f932..2acd857 100644 --- a/JortPob/ScriptCommon.cs +++ b/JortPob/ScriptCommon.cs @@ -2,6 +2,7 @@ using SoulsFormats; using SoulsIds; using System.Collections.Generic; +using System.IO; using static JortPob.Script; using static JortPob.Script.Flag; @@ -521,8 +522,8 @@ public uint CreateEntity(EntityType type, string name) public void Write() { - emevd.Write($"{Const.OUTPUT_PATH}\\event\\common.emevd.dcx"); - func.Write($"{Const.OUTPUT_PATH}\\event\\common_func.emevd.dcx"); + emevd.Write(Path.Combine(Const.OUTPUT_PATH, @"event\common.emevd.dcx")); + func.Write(Path.Combine(Const.OUTPUT_PATH, @"event\common_func.emevd.dcx")); } } } diff --git a/JortPob/ScriptManager.cs b/JortPob/ScriptManager.cs index 50e2cf9..c123e87 100644 --- a/JortPob/ScriptManager.cs +++ b/JortPob/ScriptManager.cs @@ -192,9 +192,9 @@ function value_of_bit(num, n) hksFile = hksFile.Replace("-- $$ INJECT JANK UPDATE FUNCTION HERE $$ --", $"{hksJankStart}{hksJankGen}{hksJankEnd}{hksBitwiseShitCode}"); hksFile = hksFile.Replace("-- $$ INJECT JANK UPDATE CALL HERE $$ --", $"{hksSneakShitcode}{hksSoulCounterShitCode}{hksJankCall}"); - string hksOutPath = $"{Const.OUTPUT_PATH}action\\script\\c0000.hks"; + string hksOutPath = Path.Combine(Const.OUTPUT_PATH, @"action\script\c0000.hks"); if (File.Exists(hksOutPath)) { File.Delete(hksOutPath); } - System.IO.Directory.CreateDirectory(Path.GetDirectoryName(hksOutPath)); + Directory.CreateDirectory(Path.GetDirectoryName(hksOutPath)); File.WriteAllText(hksOutPath, hksFile); // Max rep seems to be 120, may need to cap it incase you can somehow overflow that diff --git a/JortPob/SoundBank.cs b/JortPob/SoundBank.cs index 5e87481..8bed4d8 100644 --- a/JortPob/SoundBank.cs +++ b/JortPob/SoundBank.cs @@ -105,9 +105,9 @@ public void Write(string dir, int id) string bnkPath = Path.Combine(dir, $"vc{id.ToString("D3")}.bnk"); Directory.CreateDirectory(Path.GetDirectoryName(bnkPath)); - File.WriteAllText($"{bnkPath}json", json.ToJsonString()); + File.WriteAllText($"{bnkPath}.json", json.ToJsonString()); - ProcessStartInfo startInfo = new(Utility.ResourcePath(@"tools\Bnk2Json\bnk2json.exe"), $"\"{bnkPath}json\"") + ProcessStartInfo startInfo = new(Utility.ResourcePath(@"tools\Bnk2Json\bnk2json.exe"), $"\"{bnkPath}.json\"") { WorkingDirectory = Utility.ResourcePath(@"tools\Bnk2Json"), UseShellExecute = false, diff --git a/JortPob/Test.cs b/JortPob/Test.cs index a3cb1c6..26f43ae 100644 --- a/JortPob/Test.cs +++ b/JortPob/Test.cs @@ -99,7 +99,7 @@ public static void RegeneratLandscape(ESM esm, Layout layout, Cache cache) Bind.BindMaterials($"{Const.OUTPUT_PATH}material\\allmaterial.matbinbnd.dcx"); // all our terrain map pieces are now in the super overworld so this is easier lol - string map = "60"; + //string map = "60"; string name = "60_00_00_99"; foreach (Tuple values in OUTPUT) { diff --git a/JortPob/Worker/CellWorker.cs b/JortPob/Worker/CellWorker.cs index d0a88cf..8baf5df 100644 --- a/JortPob/Worker/CellWorker.cs +++ b/JortPob/Worker/CellWorker.cs @@ -57,7 +57,7 @@ public static (List, List) Go(ESM esm) var cells = cellRecords.AsParallel() .WithDegreeOfParallelism(Const.THREAD_COUNT) .Select(node => { - Cell cell = MakeCell(node, esm); + var cell = MakeCell(node, esm); Lort.TaskIterate(); // Progress bar update return cell; }) diff --git a/JortPob/Worker/EsdWorker.cs b/JortPob/Worker/EsdWorker.cs index 893ce5e..812c761 100644 --- a/JortPob/Worker/EsdWorker.cs +++ b/JortPob/Worker/EsdWorker.cs @@ -141,13 +141,13 @@ EsdDescriptor buildDescriptor(string path, byte[] data = null) } // Create the spec for EldenRing - string gameDir = WindowsifyPath($"{Const.ELDEN_PATH}Game"); + string gameDir = WindowsifyPath(Path.Combine(Const.ELDEN_PATH, "Game")); SoulsIds.GameSpec spec = SoulsIds.GameSpec.ForGame(SoulsIds.GameSpec.FromGame.ER); spec.GameDir = gameDir; // Define universe/editor SoulsIds.Universe universe = new(); - SoulsIds.Scraper scraper = new(spec, $"{Const.ELDEN_PATH}Game\\empty"); + SoulsIds.Scraper scraper = new(spec, Path.Combine(Const.ELDEN_PATH, "Game", "empty")); SoulsIds.GameEditor editor = new GameEditor(spec); // Back-fill map info into the universe diff --git a/JortPob/Worker/FlverWorker.cs b/JortPob/Worker/FlverWorker.cs index b9b18a8..28ad221 100644 --- a/JortPob/Worker/FlverWorker.cs +++ b/JortPob/Worker/FlverWorker.cs @@ -3,6 +3,7 @@ using SharpAssimp; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading; @@ -49,8 +50,8 @@ private void Run() } /* Generate the 100 scale version of the model. This is the baseline. After this we generate dynamics and baked scale versions from this */ - string meshIn = $"{Const.MORROWIND_PATH}Data Files\\meshes\\{premodel.mesh.ToLower()/*.Replace(".nif", ".fbx")*/}"; - string meshOut = $"{Const.CACHE_PATH}meshes\\{premodel.mesh.ToLower().Replace(".nif", ".flver").Replace(@"\", "_").Replace(" ", "")}"; + string meshIn = Path.Combine(Const.MORROWIND_PATH, "Data Files", "meshes", premodel.mesh.ToLower()/*.Replace(".nif", ".fbx")*/); + string meshOut = Path.Combine(Const.CACHE_PATH, "meshes", premodel.mesh.ToLower().Replace(".nif", ".flver").Replace(@"\", "_").Replace(" ", "")); ModelInfo modelInfo = new(premodel.mesh, $"meshes\\{premodel.mesh.ToLower().Replace(".nif", ".flver").Replace(@"\", "_").Replace(" ", "")}", 100); //modelInfo = ModelConverter.FBXtoFLVER(assimpContext, materialContext, modelInfo, premodel.forceCollision, meshIn, meshOut); @@ -76,13 +77,13 @@ private void Run() else { ModelInfo baked = new(modelInfo.name, modelInfo.path.Replace(".flver", $"_s{scale}.flver"), scale); - FLVERUtil.Scale($"{Const.CACHE_PATH}{modelInfo.path}", $"{Const.CACHE_PATH}{baked.path}", scale * 0.01f); + FLVERUtil.Scale(Path.Combine(Const.CACHE_PATH, modelInfo.path), Path.Combine(Const.CACHE_PATH, baked.path), scale * 0.01f); if (modelInfo.collision != null) { baked.collision = new(modelInfo.collision.name, modelInfo.collision.obj.Replace(".obj", $"_s{scale}.obj")); - Obj obj = new($"{Const.CACHE_PATH}{modelInfo.collision.obj}"); + Obj obj = new(Path.Combine(Const.CACHE_PATH, modelInfo.collision.obj)); obj.scale(scale * 0.01f); - obj.write($"{Const.CACHE_PATH}{baked.collision.obj}"); + obj.write(Path.Combine(Const.CACHE_PATH, baked.collision.obj)); } baked.size = modelInfo.size * (scale * 0.01f); models.Add(baked); diff --git a/JortPob/Worker/HkxWorker.cs b/JortPob/Worker/HkxWorker.cs index 6a37f33..dfc1e94 100644 --- a/JortPob/Worker/HkxWorker.cs +++ b/JortPob/Worker/HkxWorker.cs @@ -4,6 +4,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; +using System.IO; namespace JortPob.Worker { @@ -26,7 +27,7 @@ protected static void ProcessCollisions(List collisions, int star for (int i = start; i < limit; i++) { CollisionInfo collisionInfo = collisions[i]; - ModelConverter.OBJtoHKX($"{Const.CACHE_PATH}{collisionInfo.obj}", $"{Const.CACHE_PATH}{collisionInfo.hkx}"); + ModelConverter.OBJtoHKX(Path.Combine(Const.CACHE_PATH, collisionInfo.obj), Path.Combine(Const.CACHE_PATH, collisionInfo.hkx)); Lort.TaskIterate(); // Progress bar update } } diff --git a/JortPob/Worker/LandscapeWorker.cs b/JortPob/Worker/LandscapeWorker.cs index 3df2406..7d6ce6e 100644 --- a/JortPob/Worker/LandscapeWorker.cs +++ b/JortPob/Worker/LandscapeWorker.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Threading; +using System.IO; namespace JortPob.Worker { @@ -40,8 +41,8 @@ private void Run() Landscape landscape = esm.GetLandscape(cell.coordinate); if (landscape == null) { continue; } - TerrainInfo terrainInfo = new(landscape.coordinate, $"terrain\\ext{landscape.coordinate.x},{landscape.coordinate.y}.flver"); - terrainInfo = ModelConverter.LANDSCAPEtoFLVER(materialContext, terrainInfo, landscape, $"{Const.CACHE_PATH}terrain\\ext{landscape.coordinate.x},{landscape.coordinate.y}.flver"); + TerrainInfo terrainInfo = new(landscape.coordinate, $@"terrain\ext{landscape.coordinate.x},{landscape.coordinate.y}.flver"); + terrainInfo = ModelConverter.LANDSCAPEtoFLVER(materialContext, terrainInfo, landscape, Path.Combine(Const.CACHE_PATH, $@"terrain\ext{landscape.coordinate.x},{landscape.coordinate.y}.flver")); // Set some stuff terrainInfo.hasWater = landscape.hasWater; diff --git a/JortPob/Worker/MapWorker.cs b/JortPob/Worker/MapWorker.cs index 61fc1ec..10103ec 100644 --- a/JortPob/Worker/MapWorker.cs +++ b/JortPob/Worker/MapWorker.cs @@ -23,8 +23,8 @@ private void Run() try { Lort.Log("Loading UI map resources... ", Lort.Type.Main); - Bitmap image = new Bitmap(Utility.ResourcePath("menu\\map\\map_v1.png")); - Bitmap map = Utility.LinearToSRGBAlt(image); + using Bitmap image = new Bitmap(Utility.ResourcePath(@"menu\map\map_v1.png")); + using Bitmap map = Utility.LinearToSRGB(image); // direct refrence to the naming convention used in the game string[] groundLevels = new[] { "M00" }; @@ -35,9 +35,9 @@ private void Run() MapGenerator.ZoomLevel.L2 }; - string maskPath = Path.Combine(Const.ELDEN_PATH, "Game\\menu\\71_maptile.mtmskbnd.dcx"); - string bhdPath = Path.Combine(Const.ELDEN_PATH, "Game\\menu\\71_maptile.tpfbhd"); - string bdtPath = Path.Combine(Const.ELDEN_PATH, "Game\\menu\\71_maptile.tpfbdt"); + string maskPath = Path.Combine(Const.ELDEN_PATH, @"Game\menu\71_maptile.mtmskbnd.dcx"); + string bhdPath = Path.Combine(Const.ELDEN_PATH, @"Game\menu\71_maptile.tpfbhd"); + string bdtPath = Path.Combine(Const.ELDEN_PATH, @"Game\menu\71_maptile.tpfbdt"); // L0 chunks + L1 chunks + L2 chunks var chunkCount = (41 * 41) + (31 * 31) + (11 * 11); @@ -59,9 +59,9 @@ private void Run() ); Lort.Log("Writing map files... ", Lort.Type.Main); - File.Copy(maskPath, Path.Combine(Const.OUTPUT_PATH, "menu\\71_maptile.mtmskbnd.dcx")); - File.WriteAllBytes(Path.Combine(Const.OUTPUT_PATH, "menu\\71_maptile.tpfbhd"), result.bhdBytes); - File.WriteAllBytes(Path.Combine(Const.OUTPUT_PATH, "menu\\71_maptile.tpfbdt"), result.bdtBytes); + File.Copy(maskPath, Path.Combine(Const.OUTPUT_PATH, @"menu\71_maptile.mtmskbnd.dcx")); + File.WriteAllBytes(Path.Combine(Const.OUTPUT_PATH, @"menu\71_maptile.tpfbhd"), result.bhdBytes); + File.WriteAllBytes(Path.Combine(Const.OUTPUT_PATH, @"menu\71_maptile.tpfbdt"), result.bdtBytes); } catch (Exception ex) { Lort.Log($"Failed to generate UI map: {ex.Message}", Lort.Type.Debug); diff --git a/JortPob/Worker/MsbWorker.cs b/JortPob/Worker/MsbWorker.cs index e5ee5e6..6ead85f 100644 --- a/JortPob/Worker/MsbWorker.cs +++ b/JortPob/Worker/MsbWorker.cs @@ -25,7 +25,7 @@ private void Run() string map = $"{pool.id[0].ToString("D2")}"; string name = $"{pool.id[0].ToString("D2")}_{pool.id[1].ToString("D2")}_{pool.id[2].ToString("D2")}_{pool.id[3].ToString("D2")}"; - pool.msb.Write($"{Const.OUTPUT_PATH}map\\mapstudio\\m{name}.msb.dcx"); + pool.msb.Write(Path.Combine(Const.OUTPUT_PATH, $@"map\mapstudio\m{name}.msb.dcx")); if (pool.lights.Count() > 0) { pool.lights.Write(); } /* Write map pieces like terrain */ @@ -34,7 +34,7 @@ private void Run() int mpid = mp.Item1; string mppath = mp.Item2; - FLVER2 flver = FLVER2.Read($"{Const.CACHE_PATH}{mppath}"); + FLVER2 flver = FLVER2.Read(Path.Combine(Const.CACHE_PATH, mppath)); BND4 bnd = new(); bnd.Compression = SoulsFormats.DCX.Type.DCX_KRAK; @@ -48,7 +48,7 @@ private void Run() file.Bytes = flver.Write(); bnd.Files.Add(file); - bnd.Write($"{Const.OUTPUT_PATH}map\\m60\\m{name}\\m{name}_{mpid.ToString("D8")}.mapbnd.dcx"); + bnd.Write(Path.Combine(Const.OUTPUT_PATH, $@"map\m60\m{name}\m{name}_{mpid.ToString("D8")}.mapbnd.dcx")); } BXF4 bxfH = new(); @@ -68,11 +68,11 @@ private void Run() BinderFile testH = new(); testH.CompressionType = SoulsFormats.DCX.Type.Zlib; testH.Name = $"m{name}\\h{name}_{index}.hkx.dcx"; - testH.Bytes = DCX.Compress(File.ReadAllBytes($"{Const.CACHE_PATH}{collisionInfo.hkx}"), DCX.Type.DCX_KRAK); + testH.Bytes = DCX.Compress(File.ReadAllBytes(Path.Combine(Const.CACHE_PATH, collisionInfo.hkx)), DCX.Type.DCX_KRAK); testH.ID = id++; bxfH.Files.Add(testH); } - bxfH.Write($"{Const.OUTPUT_PATH}map\\m{map}\\m{name}\\h{name}.hkxbhd", $"{Const.OUTPUT_PATH}map\\m{map}\\m{name}\\h{name}.hkxbdt"); + bxfH.Write(Path.Combine(Const.OUTPUT_PATH, $@"map\m{map}\m{name}\h{name}.hkxbhd"), Path.Combine(Const.OUTPUT_PATH, $@"map\m{map}\m{name}\h{name}.hkxbdt")); BXF4 bxfL = new(); bxfL.Version = "07D7R6"; @@ -91,11 +91,11 @@ private void Run() BinderFile testL = new(); testL.CompressionType = SoulsFormats.DCX.Type.Zlib; testL.Name = $"m{name}\\l{name}_{index}.hkx.dcx"; - testL.Bytes = DCX.Compress(File.ReadAllBytes($"{Const.CACHE_PATH}{collisionInfo.hkx}"), DCX.Type.DCX_KRAK); + testL.Bytes = DCX.Compress(File.ReadAllBytes(Path.Combine(Const.CACHE_PATH, collisionInfo.hkx)), DCX.Type.DCX_KRAK); testL.ID = id++; bxfL.Files.Add(testL); } - bxfL.Write($"{Const.OUTPUT_PATH}map\\m{map}\\m{name}\\l{name}.hkxbhd", $"{Const.OUTPUT_PATH}map\\m{map}\\m{name}\\l{name}.hkxbdt"); + bxfL.Write(Path.Combine(Const.OUTPUT_PATH, $@"map\m{map}\m{name}\l{name}.hkxbhd"), Path.Combine(Const.OUTPUT_PATH, $@"map\m{map}\m{name}\l{name}.hkxbdt")); Lort.TaskIterate(); // Progress bar update diff --git a/JortUX/MainWindow.xaml.cs b/JortUX/MainWindow.xaml.cs index cc7ce3a..b6d85ba 100644 --- a/JortUX/MainWindow.xaml.cs +++ b/JortUX/MainWindow.xaml.cs @@ -50,10 +50,10 @@ public void ReRender() string mainText = "", debugText = ""; // top-to-bottom order - foreach (string line in JortPob.Common.Lort.mainOutput) + foreach (string line in JortPob.Common.Lort.mainOutput!) mainText += line + "\n"; - foreach (string line in JortPob.Common.Lort.debugOutput) + foreach (string line in JortPob.Common.Lort.debugOutput!) debugText += line + "\n"; main.Text = mainText;