diff --git a/Dat/Data/Constants.cs b/Dat/Data/Constants.cs index 21ed8fbd..18c62d27 100644 --- a/Dat/Data/Constants.cs +++ b/Dat/Data/Constants.cs @@ -30,8 +30,8 @@ public static class Limits public const size_t kMaxNormalEntities = kMaxEntities - kMaxMoneyEntities; // Money is not counted in this limit public const size_t kMaxMiscEntities = 4000; - public const int kMapRows = 384; - public const int kMapColumns = 384; + public const int kMapColumnsVanilla = 384; + public const int kMapRowsVanilla = 384; public const int kMaxObjectTypes = 34; public const int kMaxSawyerEncodings = 4; diff --git a/Dat/Types/SCV5/S5File.cs b/Dat/Types/SCV5/S5File.cs index 7015cc99..04d5d394 100644 --- a/Dat/Types/SCV5/S5File.cs +++ b/Dat/Types/SCV5/S5File.cs @@ -104,6 +104,25 @@ uint32_t Checksum public List[,]? TileElementMap { get; set; } byte[] OriginalTileElementData { get; set; } = []; + public (int Width, int Height) GetMapSize() + => GetMapSize(SaveDetails, ScenarioOptions); + + public static (int Width, int Height) GetMapSize(SaveDetails saveDetails, ScenarioOptions scenarioOptions) + { + if (saveDetails != null) + { + return (saveDetails.MapSizeX, saveDetails.MapSizeY); + } + else if (scenarioOptions != null) + { + return (scenarioOptions.MapSizeX, scenarioOptions.MapSizeY); + } + else + { + return (Limits.kMapColumnsVanilla, Limits.kMapRowsVanilla); + } + } + public IEnumerable Validate(ValidationContext validationContext) => []; @@ -215,6 +234,8 @@ public static S5File Read(ReadOnlySpan data) byte[] tileElementData = []; IGameState gameState; + var mapSize = GetMapSize(saveDetails, scenarioOptions); + if (header.Type == S5FileType.Scenario) { var gameStateA = SawyerStreamReader.ReadChunk(ref data); @@ -228,7 +249,7 @@ public static S5File Read(ReadOnlySpan data) if (gameStateA.GameStateFlags.HasFlag(GameStateFlags.TileManagerLoaded)) { tileElementData = SawyerStreamReader.ReadChunkCore(ref data).ToArray(); - (tileElements, tileElementMap) = ParseTileElements(tileElementData); + (tileElements, tileElementMap) = ParseTileElements(tileElementData, mapSize.Width, mapSize.Height); } } else @@ -237,7 +258,7 @@ public static S5File Read(ReadOnlySpan data) FixState(); tileElementData = SawyerStreamReader.ReadChunkCore(ref data).ToArray(); - (tileElements, tileElementMap) = ParseTileElements(tileElementData); + (tileElements, tileElementMap) = ParseTileElements(tileElementData, mapSize.Width, mapSize.Height); } var checksum = BitConverter.ToUInt32(data[0..4]); @@ -249,12 +270,12 @@ public static S5File Read(ReadOnlySpan data) static void FixState() { } - static (List, List[,]) ParseTileElements(ReadOnlySpan tileElementData) + static (List, List[,]) ParseTileElements(ReadOnlySpan tileElementData, int mapWidth, int mapHeight) { var numTileElements = tileElementData.Length / TileElement.StructLength; List tileElements = []; - var tileElementMap = new List[Limits.kMapColumns, Limits.kMapRows]; + var tileElementMap = new List[mapWidth, mapHeight]; var x = 0; var y = 0; @@ -275,12 +296,12 @@ static void FixState() if (el.IsLast()) { - if (x == Limits.kMapColumns - 1) + if (x == mapWidth - 1) { - y = (y + 1) % Limits.kMapRows; + y = (y + 1) % mapHeight; } - x = (x + 1) % Limits.kMapColumns; + x = (x + 1) % mapWidth; } // el.IsLast() indicates its the last element on that tile diff --git a/Dat/Types/SCV5/SaveDetails.cs b/Dat/Types/SCV5/SaveDetails.cs index 9fdfa533..6be59a02 100644 --- a/Dat/Types/SCV5/SaveDetails.cs +++ b/Dat/Types/SCV5/SaveDetails.cs @@ -17,7 +17,9 @@ public record SaveDetails( [property: LocoStructOffset(0x247)] byte var_247, [property: LocoStructOffset(0x248), LocoArrayLength(250 * 200), Browsable(false)] uint8_t[] Image, [property: LocoStructOffset(0xC598)] CompanyFlags ChallengeFlags, - [property: LocoStructOffset(0xC59C), LocoArrayLength(124), Browsable(false)] byte[] var_C59C) + [property: LocoStructOffset(0xC59C)] uint16_t MapSizeX, + [property: LocoStructOffset(0xC59E)] uint16_t MapSizeY, + [property: LocoStructOffset(0xC5A0), LocoArrayLength(120), Browsable(false)] byte[] var_C59C) : ILocoStruct { public const int StructLength = 0xC618; diff --git a/Dat/Types/SCV5/ScenarioOptions.cs b/Dat/Types/SCV5/ScenarioOptions.cs index 7f58ff61..c4dba6cb 100644 --- a/Dat/Types/SCV5/ScenarioOptions.cs +++ b/Dat/Types/SCV5/ScenarioOptions.cs @@ -47,7 +47,9 @@ public record ScenarioOptions( [property: LocoStructOffset(0x41C1)] uint8_t MaxRiverWidth, [property: LocoStructOffset(0x41C2)] uint8_t RiverbankWidth, [property: LocoStructOffset(0x41C3)] uint8_t RiverMeanderRate, - [property: LocoStructOffset(0x41C4), LocoArrayLength(342), Browsable(false)] byte[] var_41C4) + [property: LocoStructOffset(0x41C4)] uint16_t MapSizeX, + [property: LocoStructOffset(0x41C6)] uint16_t MapSizeY, + [property: LocoStructOffset(0x41C8), LocoArrayLength(338), Browsable(false)] byte[] var_41C8) : ILocoStruct { public const int StructLength = 0x431A; diff --git a/Gui/ViewModels/LocoTypes/SCV5ViewModel.cs b/Gui/ViewModels/LocoTypes/SCV5ViewModel.cs index 0a0c9268..8bc3b125 100644 --- a/Gui/ViewModels/LocoTypes/SCV5ViewModel.cs +++ b/Gui/ViewModels/LocoTypes/SCV5ViewModel.cs @@ -38,14 +38,14 @@ public class SCV5ViewModel : BaseFileViewModel [Reactive] public Dictionary Maps { get; set; } - [Reactive, Range(0, Limits.kMapColumns - 1)] + [Reactive, Range(0, Limits.kMapColumnsVanilla - 1)] public int TileElementX { get; set; } - [Reactive, Range(0, Limits.kMapRows - 1)] + [Reactive, Range(0, Limits.kMapRowsVanilla - 1)] public int TileElementY { get; set; } public ObservableCollection CurrentTileElements - => CurrentS5File?.TileElementMap != null && TileElementX >= 0 && TileElementX < 384 && TileElementY >= 0 && TileElementY < 384 + => CurrentS5File?.TileElementMap != null && TileElementX >= 0 && TileElementX < CurrentS5File.GetMapSize().Width && TileElementY >= 0 && TileElementY < CurrentS5File.GetMapSize().Height ? [.. CurrentS5File.TileElementMap[TileElementX, TileElementY]] : []; @@ -199,7 +199,8 @@ async Task DownloadMissingObjects(GameObjDataFolder targetFolder) void DrawMap() { - Map = new WriteableBitmap(new Avalonia.PixelSize(384, 384), new Avalonia.Vector(92, 92), Avalonia.Platform.PixelFormat.Rgba8888); + (var mapWidth, var mapHeight) = CurrentS5File.GetMapSize(); + Map = new WriteableBitmap(new Avalonia.PixelSize(mapWidth, mapHeight), new Avalonia.Vector(92, 92), Avalonia.Platform.PixelFormat.Rgba8888); using (var fb = Map.Lock()) { var teMap = CurrentS5File!.TileElementMap!; @@ -211,7 +212,7 @@ void DrawMap() unsafe { var rgba = (byte*)fb.Address; - var idx = ((x * 384) + y) * 4; // not sure why this has to be reversed to match loco + var idx = ((x * mapWidth) + y) * 4; // not sure why this has to be reversed to match loco if (el.Type == ElementType.Surface) {