Skip to content

Commit eddfdf6

Browse files
authored
support variable map sizes in saves/scenarios/landscapes (#217)
1 parent 55b0663 commit eddfdf6

5 files changed

Lines changed: 42 additions & 16 deletions

File tree

Dat/Data/Constants.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ public static class Limits
3030
public const size_t kMaxNormalEntities = kMaxEntities - kMaxMoneyEntities;
3131
// Money is not counted in this limit
3232
public const size_t kMaxMiscEntities = 4000;
33-
public const int kMapRows = 384;
34-
public const int kMapColumns = 384;
33+
public const int kMapColumnsVanilla = 384;
34+
public const int kMapRowsVanilla = 384;
3535

3636
public const int kMaxObjectTypes = 34;
3737
public const int kMaxSawyerEncodings = 4;

Dat/Types/SCV5/S5File.cs

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,25 @@ uint32_t Checksum
104104
public List<TileElement>[,]? TileElementMap { get; set; }
105105
byte[] OriginalTileElementData { get; set; } = [];
106106

107+
public (int Width, int Height) GetMapSize()
108+
=> GetMapSize(SaveDetails, ScenarioOptions);
109+
110+
public static (int Width, int Height) GetMapSize(SaveDetails saveDetails, ScenarioOptions scenarioOptions)
111+
{
112+
if (saveDetails != null)
113+
{
114+
return (saveDetails.MapSizeX, saveDetails.MapSizeY);
115+
}
116+
else if (scenarioOptions != null)
117+
{
118+
return (scenarioOptions.MapSizeX, scenarioOptions.MapSizeY);
119+
}
120+
else
121+
{
122+
return (Limits.kMapColumnsVanilla, Limits.kMapRowsVanilla);
123+
}
124+
}
125+
107126
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
108127
=> [];
109128

@@ -215,6 +234,8 @@ public static S5File Read(ReadOnlySpan<byte> data)
215234
byte[] tileElementData = [];
216235
IGameState gameState;
217236

237+
var mapSize = GetMapSize(saveDetails, scenarioOptions);
238+
218239
if (header.Type == S5FileType.Scenario)
219240
{
220241
var gameStateA = SawyerStreamReader.ReadChunk<GameStateScenarioA>(ref data);
@@ -228,7 +249,7 @@ public static S5File Read(ReadOnlySpan<byte> data)
228249
if (gameStateA.GameStateFlags.HasFlag(GameStateFlags.TileManagerLoaded))
229250
{
230251
tileElementData = SawyerStreamReader.ReadChunkCore(ref data).ToArray();
231-
(tileElements, tileElementMap) = ParseTileElements(tileElementData);
252+
(tileElements, tileElementMap) = ParseTileElements(tileElementData, mapSize.Width, mapSize.Height);
232253
}
233254
}
234255
else
@@ -237,7 +258,7 @@ public static S5File Read(ReadOnlySpan<byte> data)
237258
FixState();
238259

239260
tileElementData = SawyerStreamReader.ReadChunkCore(ref data).ToArray();
240-
(tileElements, tileElementMap) = ParseTileElements(tileElementData);
261+
(tileElements, tileElementMap) = ParseTileElements(tileElementData, mapSize.Width, mapSize.Height);
241262
}
242263

243264
var checksum = BitConverter.ToUInt32(data[0..4]);
@@ -249,12 +270,12 @@ public static S5File Read(ReadOnlySpan<byte> data)
249270
static void FixState()
250271
{ }
251272

252-
static (List<TileElement>, List<TileElement>[,]) ParseTileElements(ReadOnlySpan<byte> tileElementData)
273+
static (List<TileElement>, List<TileElement>[,]) ParseTileElements(ReadOnlySpan<byte> tileElementData, int mapWidth, int mapHeight)
253274
{
254275
var numTileElements = tileElementData.Length / TileElement.StructLength;
255276

256277
List<TileElement> tileElements = [];
257-
var tileElementMap = new List<TileElement>[Limits.kMapColumns, Limits.kMapRows];
278+
var tileElementMap = new List<TileElement>[mapWidth, mapHeight];
258279

259280
var x = 0;
260281
var y = 0;
@@ -275,12 +296,12 @@ static void FixState()
275296

276297
if (el.IsLast())
277298
{
278-
if (x == Limits.kMapColumns - 1)
299+
if (x == mapWidth - 1)
279300
{
280-
y = (y + 1) % Limits.kMapRows;
301+
y = (y + 1) % mapHeight;
281302
}
282303

283-
x = (x + 1) % Limits.kMapColumns;
304+
x = (x + 1) % mapWidth;
284305
}
285306

286307
// el.IsLast() indicates its the last element on that tile

Dat/Types/SCV5/SaveDetails.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ public record SaveDetails(
1717
[property: LocoStructOffset(0x247)] byte var_247,
1818
[property: LocoStructOffset(0x248), LocoArrayLength(250 * 200), Browsable(false)] uint8_t[] Image,
1919
[property: LocoStructOffset(0xC598)] CompanyFlags ChallengeFlags,
20-
[property: LocoStructOffset(0xC59C), LocoArrayLength(124), Browsable(false)] byte[] var_C59C)
20+
[property: LocoStructOffset(0xC59C)] uint16_t MapSizeX,
21+
[property: LocoStructOffset(0xC59E)] uint16_t MapSizeY,
22+
[property: LocoStructOffset(0xC5A0), LocoArrayLength(120), Browsable(false)] byte[] var_C59C)
2123
: ILocoStruct
2224
{
2325
public const int StructLength = 0xC618;

Dat/Types/SCV5/ScenarioOptions.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ public record ScenarioOptions(
4747
[property: LocoStructOffset(0x41C1)] uint8_t MaxRiverWidth,
4848
[property: LocoStructOffset(0x41C2)] uint8_t RiverbankWidth,
4949
[property: LocoStructOffset(0x41C3)] uint8_t RiverMeanderRate,
50-
[property: LocoStructOffset(0x41C4), LocoArrayLength(342), Browsable(false)] byte[] var_41C4)
50+
[property: LocoStructOffset(0x41C4)] uint16_t MapSizeX,
51+
[property: LocoStructOffset(0x41C6)] uint16_t MapSizeY,
52+
[property: LocoStructOffset(0x41C8), LocoArrayLength(338), Browsable(false)] byte[] var_41C8)
5153
: ILocoStruct
5254
{
5355
public const int StructLength = 0x431A;

Gui/ViewModels/LocoTypes/SCV5ViewModel.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,14 @@ public class SCV5ViewModel : BaseFileViewModel
3838
[Reactive]
3939
public Dictionary<ElementType, Bitmap> Maps { get; set; }
4040

41-
[Reactive, Range(0, Limits.kMapColumns - 1)]
41+
[Reactive, Range(0, Limits.kMapColumnsVanilla - 1)]
4242
public int TileElementX { get; set; }
4343

44-
[Reactive, Range(0, Limits.kMapRows - 1)]
44+
[Reactive, Range(0, Limits.kMapRowsVanilla - 1)]
4545
public int TileElementY { get; set; }
4646

4747
public ObservableCollection<TileElement> CurrentTileElements
48-
=> CurrentS5File?.TileElementMap != null && TileElementX >= 0 && TileElementX < 384 && TileElementY >= 0 && TileElementY < 384
48+
=> CurrentS5File?.TileElementMap != null && TileElementX >= 0 && TileElementX < CurrentS5File.GetMapSize().Width && TileElementY >= 0 && TileElementY < CurrentS5File.GetMapSize().Height
4949
? [.. CurrentS5File.TileElementMap[TileElementX, TileElementY]]
5050
: [];
5151

@@ -199,7 +199,8 @@ async Task DownloadMissingObjects(GameObjDataFolder targetFolder)
199199

200200
void DrawMap()
201201
{
202-
Map = new WriteableBitmap(new Avalonia.PixelSize(384, 384), new Avalonia.Vector(92, 92), Avalonia.Platform.PixelFormat.Rgba8888);
202+
(var mapWidth, var mapHeight) = CurrentS5File.GetMapSize();
203+
Map = new WriteableBitmap(new Avalonia.PixelSize(mapWidth, mapHeight), new Avalonia.Vector(92, 92), Avalonia.Platform.PixelFormat.Rgba8888);
203204
using (var fb = Map.Lock())
204205
{
205206
var teMap = CurrentS5File!.TileElementMap!;
@@ -211,7 +212,7 @@ void DrawMap()
211212
unsafe
212213
{
213214
var rgba = (byte*)fb.Address;
214-
var idx = ((x * 384) + y) * 4; // not sure why this has to be reversed to match loco
215+
var idx = ((x * mapWidth) + y) * 4; // not sure why this has to be reversed to match loco
215216

216217
if (el.Type == ElementType.Surface)
217218
{

0 commit comments

Comments
 (0)